@@ -15,9 +15,9 @@ public class ParserOptions {
1515 /**
1616 * A graphql hacking vector is to send nonsensical queries that burn lots of parsing CPU time and burn
1717 * memory representing a document that won't ever execute. To prevent this for most users, graphql-java
18- * set this value to 15000. ANTLR parsing time is linear to the number of tokens presented. The more you
18+ * sets this value to 15000. ANTLR parsing time is linear to the number of tokens presented. The more you
1919 * allow the longer it takes.
20- *
<
4D1C
/td>20+ * <p>
2121 * If you want to allow more, then {@link #setDefaultParserOptions(ParserOptions)} allows you to change this
2222 * JVM wide.
2323 */
@@ -26,18 +26,29 @@ public class ParserOptions {
2626 * Another graphql hacking vector is to send large amounts of whitespace in operations that burn lots of parsing CPU time and burn
2727 * memory representing a document. Whitespace token processing in ANTLR is 2 orders of magnitude faster than grammar token processing
2828 * however it still takes some time to happen.
29- *
29+ * <p>
3030 * If you want to allow more, then {@link #setDefaultParserOptions(ParserOptions)} allows you to change this
3131 * JVM wide.
3232 */
3333 public static final int MAX_WHITESPACE_TOKENS = 200_000 ;
3434
35+ /**
36+ * A graphql hacking vector is to send nonsensical queries that have lots of grammar rule depth to them which
37+ * can cause stack overflow exceptions during the query parsing. To prevent this for most users, graphql-java
38+ * sets this value to 500 grammar rules deep.
39+ * <p>
40+ * If you want to allow more, then {@link #setDefaultParserOptions(ParserOptions)} allows you to change this
41+ * JVM wide.
42+ */
43+ public static final int MAX_RULE_DEPTH = 500 ;
44+
3545 private static ParserOptions defaultJvmParserOptions = newParserOptions ()
3646 .captureIgnoredChars (false )
3747 .captureSourceLocation (true )
3848 .captureLineComments (true )
3949 .maxTokens (MAX_QUERY_TOKENS ) // to prevent a billion laughs style attacks, we set a default for graphql-java
4050 .maxWhitespaceTokens (MAX_WHITESPACE_TOKENS )
51+ .maxRuleDepth (MAX_RULE_DEPTH )
4152 .build ();
4253
4354 private static ParserOptions defaultJvmOperationParserOptions = newParserOptions ()
@@ -46,6 +57,7 @@ public class ParserOptions {
4657 .captureLineComments (false ) // #comments are not useful in query parsing
4758 .maxTokens (MAX_QUERY_TOKENS ) // to prevent a billion laughs style attacks, we set a default for graphql-java
4859 .maxWhitespaceTokens (MAX_WHITESPACE_TOKENS )
60+ .maxRuleDepth (MAX_RULE_DEPTH )
4961 .build ();
5062
5163 private static ParserOptions defaultJvmSdlParserOptions = newParserOptions ()
@@ -54,6 +66,7 @@ public class ParserOptions {
5466 .captureLineComments (true ) // #comments are useful in SDL parsing
5567 .maxTokens (Integer .MAX_VALUE ) // we are less worried about a billion laughs with SDL parsing since the call path is not facing attackers
5668 .maxWhitespaceTokens (Integer .MAX_VALUE )
69+ .maxRuleDepth (Integer .MAX_VALUE )
5770 .build ();
4D1C
5871
5972 /**
@@ -156,6 +169,7 @@ public static void setDefaultSdlParserOptions(ParserOptions options) {
156169 private final boolean captureLineComments ;
157170 private final int maxTokens ;
158171 private final int maxWhitespaceTokens ;
172+ private final int maxRuleDepth ;
159173 private final ParsingListener parsingListener ;
160174
161175 private ParserOptions (Builder builder ) {
@@ -164,6 +178,7 @@ private ParserOptions(Builder builder) {
164178 this .captureLineComments = builder .captureLineComments ;
165179 this .maxTokens = builder .maxTokens ;
166180 this .maxWhitespaceTokens = builder .maxWhitespaceTokens ;
181+ this .maxRuleDepth = builder .maxRuleDepth ;
167182 this .parsingListener = builder .parsingListener ;
168183 }
169184
@@ -226,6 +241,17 @@ public int getMaxWhitespaceTokens() {
226241 return maxWhitespaceTokens ;
227242 }
228243
244+ /**
245+ * A graphql hacking vector is to send nonsensical queries that have lots of rule depth to them which
246+ * can cause stack overflow exceptions during the query parsing. To prevent this you can set a value
247+ * that is the maximum depth allowed before an exception is thrown and the parsing is stopped.
248+ *
249+ * @return the maximum token depth the parser will accept, after which an exception will be thrown.
250+ */
251+ public int getMaxRuleDepth () {
252+ return maxRuleDepth ;
253+ }
254+
229255 public ParsingListener getParsingListener () {
230256 return parsingListener ;
231257 }
@@ -245,9 +271,10 @@ public static class Builder {
245271 private boolean captureIgnoredChars = false ;
246272 private boolean captureSourceLocation = true ;
247273 private boolean captureLineComments = true ;
248- private int maxTokens = MAX_QUERY_TOKENS ;
249274 private ParsingListener parsingListener = ParsingListener .NOOP ;
275+ private int maxTokens = MAX_QUERY_TOKENS ;
250276 private int maxWhitespaceTokens = MAX_WHITESPACE_TOKENS ;
277+ private int maxRuleDepth = MAX_RULE_DEPTH ;
251278
252279 Builder () {
253280 }
@@ -258,6 +285,7 @@ public static class Builder {
258285 this .captureLineComments = parserOptions .captureLineComments ;
259286 this .maxTokens = parserOptions .maxTokens ;
260287 this .maxWhitespaceTokens = parserOptions .maxWhitespaceTokens ;
288+ this .maxRuleDepth = parserOptions .maxRuleDepth ;
261289 this .parsingListener = parserOptions .parsingListener ;
262290 }
263291
@@ -286,6 +314,11 @@ public Builder maxWhitespaceTokens(int maxWhitespaceTokens) {
286314 return this ;
287315 }
288316
317+ public Builder maxRuleDepth (int maxRuleDepth ) {
318+ this .maxRuleDepth = maxRuleDepth ;
319+ return this ;
320+ }
321+
289322 public Builder parsingListener (ParsingListener parsingListener ) {
290323 this .parsingListener = assertNotNull (parsingListener );
291324 return this ;
0 commit comments