Skip to content

Commit e189d95

Browse files
authored
Add depth limit to GraphQL parser (#9519)
1 parent 1be748d commit e189d95

16 files changed

Lines changed: 264 additions & 37 deletions

src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services
8484
noLocations: !options.IncludeLocations,
8585
maxAllowedNodes: options.MaxAllowedNodes,
8686
maxAllowedTokens: options.MaxAllowedTokens,
87-
maxAllowedFields: options.MaxAllowedFields);
87+
maxAllowedFields: options.MaxAllowedFields,
88+
maxAllowedRecursionDepth: options.MaxAllowedRecursionDepth);
8889
});
8990

9091
return services;

src/HotChocolate/Core/src/Types/Execution/Options/RequestParserOptions.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,46 +8,59 @@ namespace HotChocolate.Execution.Options;
88
public sealed class RequestParserOptions
99
{
1010
/// <summary>
11+
/// <para>
1112
/// Specifies if locations shall be preserved in syntax nodes so that errors can
1213
/// later refer to locations of the original source text.
1314
/// These location objects will take up extra memory.
14-
///
15-
/// Default: <c>true</c>
15+
/// </para>
16+
/// <para>Default: <c>true</c></para>
1617
/// </summary>
1718
public bool IncludeLocations { get; set; } = true;
1819

1920
/// <summary>
21+
/// <para>
2022
/// Parser CPU and memory usage is linear to the number of nodes in a document
2123
/// however in extreme cases it becomes quadratic due to memory exhaustion.
2224
/// Parsing happens before validation so even invalid queries can burn lots of
2325
/// CPU time and memory.
24-
///
25-
/// To prevent this you can set a maximum number of nodes allowed within a document.
26-
///
27-
/// This limitation effects the <see cref="Utf8GraphQLParser"/>.
26+
/// </para>
27+
/// <para>To prevent this you can set a maximum number of nodes allowed within a document.</para>
28+
/// <para>This limitation affects the <see cref="Utf8GraphQLParser"/>.</para>
2829
/// </summary>
2930
public int MaxAllowedNodes { get; set; } = int.MaxValue;
3031

3132
/// <summary>
33+
/// <para>
3234
/// Parser CPU and memory usage is linear to the number of tokens in a document
3335
/// however in extreme cases it becomes quadratic due to memory exhaustion.
3436
/// Parsing happens before validation so even invalid queries can burn lots of
3537
/// CPU time and memory.
36-
///
37-
/// To prevent this you can set a maximum number of tokens allowed within a document.
38-
///
39-
/// This limitation effects the <see cref="Utf8GraphQLReader"/>.
38+
/// </para>
39+
/// <para>To prevent this you can set a maximum number of tokens allowed within a document.</para>
40+
/// <para>This limitation affects the <see cref="Utf8GraphQLReader"/>.</para>
4041
/// </summary>
4142
public int MaxAllowedTokens { get; set; } = int.MaxValue;
4243

4344
/// <summary>
45+
/// <para>
4446
/// Parser CPU and memory usage is linear to the number of nodes in a document
4547
/// however in extreme cases it becomes quadratic due to memory exhaustion.
4648
/// Parsing happens before validation so even invalid queries can burn lots of
4749
/// CPU time and memory.
48-
///
50+
/// </para>
51+
/// <para>
4952
/// To prevent this you can set a maximum number of fields allowed within a document
5053
/// as fields is an easier way to estimate query size for GraphQL requests.
54+
/// </para>
5155
/// </summary>
5256
public int MaxAllowedFields { get; set; } = 2048;
57+
58+
/// <summary>
59+
/// <para>
60+
/// The maximum allowed recursion depth when parsing a document.
61+
/// This prevents stack overflow from deeply nested queries.
62+
/// </para>
63+
/// <para>Default: <c>200</c></para>
64+
/// </summary>
65+
public int MaxAllowedRecursionDepth { get; set; } = 200;
5366
}

src/HotChocolate/Diagnostics/test/Diagnostics.Tests/ActivityTestHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ private static void SerializeActivity(Activity activity)
119119
var scrubbedStackTrace = StackTracePathRegex().Replace(stackTrace, match =>
120120
{
121121
var fileName = System.IO.Path.GetFileName(match.Groups["path"].Value);
122-
var lineNumber = match.Groups["line"].Value;
123-
return $" in {fileName}:line {lineNumber}";
122+
return $" in {fileName}";
124123
});
125124

126125
yield return new KeyValuePair<string, object?>(

src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityExecutionDiagnosticListenerTests.ParsingError_InvalidGraphQLDocument_ReportsErrorStatus.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
{
2626
"Key": "exception.stacktrace",
27-
"Value": "HotChocolate.Language.SyntaxException: Expected a `RightBrace`-token, but found a `EndOfFile`-token.\n at HotChocolate.Language.Utf8GraphQLParser.ParseSelectionSet() in Utf8GraphQLParser.Operations.cs:line 221\n at HotChocolate.Language.Utf8GraphQLParser.ParseShortOperationDefinition() in Utf8GraphQLParser.Operations.cs:line 73\n at HotChocolate.Language.Utf8GraphQLParser.ParseDefinition() in Utf8GraphQLParser.cs:line 215\n at HotChocolate.Language.Utf8GraphQLParser.Parse() in Utf8GraphQLParser.cs:line 98\n at HotChocolate.Language.Utf8GraphQLParser.Parse(String sourceText, ParserOptions options) in Utf8GraphQLParser.cs:line 326\n at HotChocolate.Execution.Pipeline.DocumentParserMiddleware.InvokeAsync(RequestContext context) in DocumentParserMiddleware.cs:line 63"
27+
"Value": "HotChocolate.Language.SyntaxException: Expected a `RightBrace`-token, but found a `EndOfFile`-token.\n at HotChocolate.Language.Utf8GraphQLParser.ParseSelectionSet() in Utf8GraphQLParser.Operations.cs\n at HotChocolate.Language.Utf8GraphQLParser.ParseShortOperationDefinition() in Utf8GraphQLParser.Operations.cs\n at HotChocolate.Language.Utf8GraphQLParser.ParseDefinition() in Utf8GraphQLParser.cs\n at HotChocolate.Language.Utf8GraphQLParser.Parse() in Utf8GraphQLParser.cs\n at HotChocolate.Language.Utf8GraphQLParser.Parse(String sourceText, ParserOptions options) in Utf8GraphQLParser.cs\n at HotChocolate.Execution.Pipeline.DocumentParserMiddleware.InvokeAsync(RequestContext context) in DocumentParserMiddleware.cs"
2828
},
2929
{
3030
"Key": "exception.type",

src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityExecutionDiagnosticListenerTests.SubscriptionEventError_Records_Subscription_Event_Error.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
},
178178
{
179179
"Key": "exception.stacktrace",
180-
"Value": "System.InvalidOperationException: Subscription event failed.\n at HotChocolate.Diagnostics.ActivityExecutionDiagnosticListenerTests.SimpleSubscription.OnFailingMessage(String message) in ActivityExecutionDiagnosticListenerTests.cs:line 554\n at lambda_method(Closure, IResolverContext)\n at HotChocolate.Types.Helpers.FieldMiddlewareCompiler.<>c__DisplayClass9_0.<<CreateResolverMiddleware>b__0>d.MoveNext() in FieldMiddlewareCompiler.cs:line 127\n--- End of stack trace from previous location ---\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken) in ResolverTask.Execute.cs:line 135\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken) in ResolverTask.Execute.cs:line 81"
180+
"Value": "System.InvalidOperationException: Subscription event failed.\n at HotChocolate.Diagnostics.ActivityExecutionDiagnosticListenerTests.SimpleSubscription.OnFailingMessage(String message) in ActivityExecutionDiagnosticListenerTests.cs\n at lambda_method(Closure, IResolverContext)\n at HotChocolate.Types.Helpers.FieldMiddlewareCompiler.<>c__DisplayClass9_0.<<CreateResolverMiddleware>b__0>d.MoveNext() in FieldMiddlewareCompiler.cs\n--- End of stack trace from previous location ---\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken) in ResolverTask.Execute.cs\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken) in ResolverTask.Execute.cs"
181181
},
182182
{
183183
"Key": "exception.type",

src/HotChocolate/Fusion/src/Fusion.Execution/Execution/FusionParserOptions.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,46 @@ public sealed class FusionParserOptions
1616
public bool NoLocations { get; set; }
1717

1818
/// <summary>
19+
/// <para>
1920
/// Parser CPU and memory usage is linear to the number of tokens in a document
2021
/// however in extreme cases it becomes quadratic due to memory exhaustion.
2122
/// Parsing happens before validation so even invalid queries can burn lots of
2223
/// CPU time and memory.
23-
///
24-
/// To prevent this you can set a maximum number of tokens allowed within a document.
24+
/// </para>
25+
/// <para>To prevent this you can set a maximum number of tokens allowed within a document.</para>
2526
/// </summary>
2627
public int MaxAllowedTokens { get; set; } = int.MaxValue;
2728

2829
/// <summary>
30+
/// <para>
2931
/// Parser CPU and memory usage is linear to the number of nodes in a document
3032
/// however in extreme cases it becomes quadratic due to memory exhaustion.
3133
/// Parsing happens before validation so even invalid queries can burn lots of
3234
/// CPU time and memory.
33-
///
34-
/// To prevent this you can set a maximum number of nodes allowed within a document.
35+
/// </para>
36+
/// <para>To prevent this you can set a maximum number of nodes allowed within a document.</para>
3537
/// </summary>
3638
public int MaxAllowedNodes { get; set; } = int.MaxValue;
3739

3840
/// <summary>
41+
/// <para>
3942
/// Parser CPU and memory usage is linear to the number of nodes in a document
4043
/// however in extreme cases it becomes quadratic due to memory exhaustion.
4144
/// Parsing happens before validation so even invalid queries can burn lots of
4245
/// CPU time and memory.
43-
///
46+
/// </para>
47+
/// <para>
4448
/// To prevent this you can set a maximum number of fields allowed within a document
4549
/// as fields is an easier way to estimate query size for GraphQL requests.
50+
/// </para>
4651
/// </summary>
4752
public int MaxAllowedFields { get; set; } = 2048;
53+
54+
/// <summary>
55+
/// <para>
56+
/// The maximum allowed recursion depth when parsing a document.
57+
/// This prevents stack overflow from deeply nested queries.
58+
/// </para>
59+
/// </summary>
60+
public int MaxAllowedRecursionDepth { get; set; } = 200;
4861
}

src/HotChocolate/Fusion/src/Fusion.Execution/Execution/FusionRequestExecutorManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ private static ParserOptions CreateParserOptions(FusionGatewaySetup setup)
292292
allowFragmentVariables: false,
293293
maxAllowedNodes: options.MaxAllowedNodes,
294294
maxAllowedTokens: options.MaxAllowedTokens,
295-
maxAllowedFields: options.MaxAllowedFields);
295+
maxAllowedFields: options.MaxAllowedFields,
296+
maxAllowedRecursionDepth: options.MaxAllowedRecursionDepth);
296297
}
297298

298299
private SourceSchemaClientConfigurations CreateClientConfigurations(

src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,24 @@ public sealed class ParserOptions
3333
/// <param name="maxAllowedFields">
3434
/// The maximum number of fields allowed within a query document.
3535
/// </param>
36+
/// <param name="maxAllowedRecursionDepth">
37+
/// The maximum allowed recursion depth when parsing a document.
38+
/// This prevents stack overflow from deeply nested queries.
39+
/// </param>
3640
public ParserOptions(
3741
bool noLocations = false,
3842
bool allowFragmentVariables = false,
3943
int maxAllowedNodes = int.MaxValue,
4044
int maxAllowedTokens = int.MaxValue,
41-
int maxAllowedFields = 2048)
45+
int maxAllowedFields = 2048,
46+
int maxAllowedRecursionDepth = 200)
4247
{
4348
NoLocations = noLocations;
4449
Experimental = new(allowFragmentVariables);
4550
MaxAllowedTokens = maxAllowedTokens;
4651
MaxAllowedNodes = maxAllowedNodes;
4752
MaxAllowedFields = maxAllowedFields;
53+
MaxAllowedRecursionDepth = maxAllowedRecursionDepth;
4854
}
4955

5056
/// <summary>
@@ -86,6 +92,11 @@ public ParserOptions(
8692
/// </summary>
8793
public int MaxAllowedFields { get; }
8894

95+
/// <summary>
96+
/// Gets the maximum allowed recursion depth of a parsed document.
97+
/// </summary>
98+
public int MaxAllowedRecursionDepth { get; }
99+
89100
/// <summary>
90101
/// Gets the experimental parser options
91102
/// which are by default switched of.

src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.Designer.cs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,7 @@
207207
<data name="Utf8GraphQLParser_Start_MaxAllowedFieldsReached" xml:space="preserve">
208208
<value>The GraphQL request document contains more than {0} fields. Parsing aborted.</value>
209209
</data>
210+
<data name="Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached" xml:space="preserve">
211+
<value>Document exceeds the maximum allowed recursion depth of {0}. Parsing aborted.</value>
212+
</data>
210213
</root>

0 commit comments

Comments
 (0)