18.x port - Stop DOS attacks by making the lexer stop early on evil input by bbakerman · Pull Request #2897 · graphql-java/graphql-java · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/graphql/ParseAndValidate.java
11 changes: 6 additions & 5 deletions src/main/java/graphql/parser/GraphqlAntlrToLanguage.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,16 @@
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.collect.ImmutableKit.emptyList;
import static graphql.collect.ImmutableKit.map;
import static graphql.parser.Parser.CHANNEL_COMMENTS;
import static graphql.parser.Parser.CHANNEL_WHITESPACE;
import static graphql.parser.StringValueParsing.parseSingleQuotedString;
import static graphql.parser.StringValueParsing.parseTripleQuotedString;
import static java.util.Optional.ofNullable;

@Internal
public class GraphqlAntlrToLanguage {

private static final List<Comment> NO_COMMENTS = ImmutableKit.emptyList();
private static final int CHANNEL_COMMENTS = 2;
private static final int CHANNEL_IGNORED_CHARS = 3;
private final CommonTokenStream tokens;
private final MultiSourceReader multiSourceReader;
private final ParserOptions parserOptions;
Expand All @@ -97,7 +98,7 @@ public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiS
public GraphqlAntlrToLanguage(CommonTokenStream tokens, MultiSourceReader multiSourceReader, ParserOptions parserOptions) {
this.tokens = tokens;
this.multiSourceReader = multiSourceReader;
this.parserOptions = parserOptions == null ? ParserOptions.getDefaultParserOptions() : parserOptions;
this.parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultParserOptions());
}

public ParserOptions getParserOptions() {
Expand Down Expand Up @@ -791,12 +792,12 @@ private void addIgnoredChars(ParserRuleContext ctx, NodeBuilder nodeBuilder) {
}
Token start = ctx.getStart();
int tokenStartIndex = start.getTokenIndex();
List<Token> leftChannel = tokens.getHiddenTokensToLeft(tokenStartIndex, CHANNEL_IGNORED_CHARS);
List<Token> leftChannel = tokens.getHiddenTokensToLeft(tokenStartIndex, CHANNEL_WHITESPACE);
List<IgnoredChar> ignoredCharsLeft = mapTokenToIgnoredChar(leftChannel);

Token stop = ctx.getStop();
int tokenStopIndex = stop.getTokenIndex();
List<Token> rightChannel = tokens.getHiddenTokensToRight(tokenStopIndex, CHANNEL_IGNORED_CHARS);
List<Token> rightChannel = tokens.getHiddenTokensToRight(tokenStopIndex, CHANNEL_WHITESPACE);
List<IgnoredChar> ignoredCharsRight = mapTokenToIgnoredChar(rightChannel);

nodeBuilder.ignoredChars(new IgnoredChars(ignoredCharsLeft, ignoredCharsRight));
Expand Down
44 changes: 34 additions & 10 deletions src/main/java/graphql/parser/Parser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.parser;

import graphql.Internal;
import graphql.PublicApi;
import graphql.language.Document;
import graphql.language.Node;
Expand All @@ -24,6 +25,8 @@
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

/**
Expand All @@ -45,6 +48,11 @@
@PublicApi
public class Parser {

@Internal
public static final int CHANNEL_COMMENTS = 2;
@Internal
public static final int CHANNEL_WHITESPACE = 3;

/**
* Parses a string input into a graphql AST {@link Document}
*
Expand Down Expand Up @@ -195,7 +203,16 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
}
});

CommonTokenStream tokens = new CommonTokenStream(lexer);
// default in the parser options if they are not set
parserOptions = Optional.ofNullable(parserOptions).orElse(ParserOptions.getDefaultParserOptions());

// this lexer wrapper allows us to stop lexing when too many tokens are in place. This prevents DOS attacks.
int maxTokens = parserOptions.getMaxTokens();
int maxWhitespaceTokens = parserOptions.getMaxWhitespaceTokens();
BiConsumer<Integer, Token> onTooManyTokens = (maxTokenCount, token) -> throwCancelParseIfTooManyTokens(token, maxTokenCount, multiSourceReader);
SafeTokenSource safeTokenSource = new SafeTokenSource(lexer, maxTokens, maxWhitespaceTokens, onTooManyTokens);

CommonTokenStream tokens = new CommonTokenStream(safeTokenSource);

GraphqlParser parser = new GraphqlParser(tokens);
parser.removeErrorListeners();
Expand Down Expand Up @@ -268,21 +285,28 @@ public int getCharPositionInLine() {

count++;
if (count > maxTokens) {
String msg = String.format("More than %d parse tokens have been presented. To prevent Denial Of Service attacks, parsing has been cancelled.", maxTokens);
SourceLocation sourceLocation = null;
String offendingToken = null;
if (token != null) {
offendingToken = node.getText();
sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, token.getLine(), token.getCharPositionInLine());
}

throw new ParseCancelledException(msg, sourceLocation, offendingToken);
throwCancelParseIfTooManyTokens(token, maxTokens, multiSourceReader);
}
}
};
parser.addParseListener(listener);
}

private void throwCancelParseIfTooManyTokens(Token token, int maxTokens, MultiSourceReader multiSourceReader) throws ParseCancelledException {
String tokenType = "grammar";
SourceLocation sourceLocation = null;
String offendingToken = null;
if (token != null) {
int channel = token.getChannel();
tokenType = channel == CHANNEL_WHITESPACE ? "whitespace" : (channel == CHANNEL_COMMENTS ? "comments" : "grammar");

offendingToken = token.getText();
sourceLocation = AntlrHelper.createSourceLocation(multiSourceReader, token.getLine(), token.getCharPositionInLine());
}
String msg = String.format("More than %d %s tokens have been presented. To prevent Denial Of Service attacks, parsing has been cancelled.", maxTokens, tokenType);
throw new ParseCancelledException(msg, sourceLocation, offendingToken);
}

/**
* Allows you to override the ANTLR to AST code.
*
Expand Down
120 changes: 113 additions & 7 deletions src/main/java/graphql/parser/ParserOptions.java
Loading