@@ -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- *
20+ * <p>
2121 * If you want to allow more, then {@link #setDefaultParserOptions(ParserOptions)} allows you to change this
2222 * JVM wide.
2323 */
@@ -26,19 +26,30 @@ 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 1000.
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 = 1_000 ;
44+
3545 private static ParserOptions defaultJvmParserOptions = newParserOptions ()
3646 .captureIgnoredChars (false )
3747 .captureSourceLocation (true )
3848 .captureLineComments (true )
3949 .readerTrackData (true )
4050 .maxTokens (MAX_QUERY_TOKENS ) // to prevent a billion laughs style attacks, we set a default for graphql-java
4151 .maxWhitespaceTokens (MAX_WHITESPACE_TOKENS )
52+ .maxRuleDepth (MAX_RULE_DEPTH )
4253 .build ();
4354
4455 private static ParserOptions defaultJvmOperationParserOptions = newParserOptions ()
@@ -48,6 +59,7 @@ public class ParserOptions {
4859 .readerTrackData (true )
4960 .maxTokens (MAX_QUERY_TOKENS ) // to prevent a billion laughs style attacks, we set a default for graphql-java
5061 .maxWhitespaceTokens (MAX_WHITESPACE_TOKENS )
62+ .maxRuleDepth (MAX_RULE_DEPTH )
5163 .build ();
5264
5365 private static ParserOptions defaultJvmSdlParserOptions = newParserOptions ()
@@ -57,6 +69,7 @@ public class ParserOptions {
5769 .readerTrackData (true )
5870 .maxTokens (Integer .MAX_VALUE ) // we are less worried about a billion laughs with SDL parsing since the call path is not facing attackers
5971 .maxWhitespaceTokens (Integer .MAX_VALUE )
72+ .maxRuleDepth (Integer .MAX_VALUE )
6073 .build ();
6174
6275 /**
@@ -160,6 +173,7 @@ public static void setDefaultSdlParserOptions(ParserOptions options) {
160173 private final boolean readerTrackData ;
161174 private final int maxTokens ;
162175 private final int maxWhitespaceTokens ;
176+ private final int maxRuleDepth ;
163177 private final ParsingListener parsingListener ;
164178
165179 private ParserOptions (Builder builder ) {
@@ -169,6 +183,7 @@ private ParserOptions(Builder builder) {
169183 this .readerTrackData = builder .readerTrackData ;
170184 this .maxTokens = builder .maxTokens ;
171185 this .maxWhitespaceTokens = builder .maxWhitespaceTokens ;
186+ this .maxRuleDepth = builder .maxRuleDepth ;
172187 this .parsingListener = builder .parsingListener ;
173188 }
174189
@@ -240,6 +255,17 @@ public int getMaxWhitespaceTokens() {
240255 return maxWhitespaceTokens ;
241256 }
242257
258+ /**
259+ * A graphql hacking vector is to send nonsensical queries that have lots of rule depth to them which
260+ * can cause stack overflow exceptions during the query parsing. To prevent this you can set a value
261+ * that is the maximum depth allowed before an exception is thrown and the parsing is stopped.
262+ *
263+ * @return the maximum token depth the parser will accept, after which an exception will be thrown.
264+ */
265+ public int getMaxRuleDepth () {
266+ return maxRuleDepth ;
267+ }
268+
243269 public ParsingListener getParsingListener () {
244270 return parsingListener ;
245271 }
@@ -260,9 +286,10 @@ public static class Builder {
260286 private boolean captureSourceLocation = true ;
261287 private boolean captureLineComments = true ;
262288 private boolean readerTrackData = true ;
263- private int maxTokens = MAX_QUERY_TOKENS ;
264289 private ParsingListener parsingListener = ParsingListener .NOOP ;
290+ private int maxTokens = MAX_QUERY_TOKENS ;
265291 private int maxWhitespaceTokens = MAX_WHITESPACE_TOKENS ;
292+ private int maxRuleDepth = MAX_RULE_DEPTH ;
266293
267294 Builder () {
268295 }
@@ -273,6 +300,7 @@ public static class Builder {
273300 this .captureLineComments = parserOptions .captureLineComments ;
274301 this .maxTokens = parserOptions .maxTokens ;
275302 this .maxWhitespaceTokens = parserOptions .maxWhitespaceTokens ;
303+ this .maxRuleDepth = parserOptions .maxRuleDepth ;
276304 this .parsingListener = parserOptions .parsingListener ;
277305 }
278306
@@ -306,6 +334,11 @@ public Builder maxWhitespaceTokens(int maxWhitespaceTokens) {
306334 return this ;
307335 }
308336
337+ public Builder maxRuleDepth (int maxRuleDepth ) {
338+ this .maxRuleDepth = maxRuleDepth ;
339+ return this ;
340+ }
341+
309342 public Builder parsingListener (ParsingListener parsingListener ) {
310343 this .parsingListener = assertNotNull (parsingListener );
311344 return this ;
0 commit comments