Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services
noLocations: !options.IncludeLocations,
maxAllowedNodes: options.MaxAllowedNodes,
maxAllowedTokens: options.MaxAllowedTokens,
maxAllowedFields: options.MaxAllowedFields);
maxAllowedFields: options.MaxAllowedFields,
maxAllowedRecursionDepth: options.MaxAllowedRecursionDepth);
});

return services;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,59 @@ namespace HotChocolate.Execution.Options;
public sealed class RequestParserOptions
{
/// <summary>
/// <para>
/// Specifies if locations shall be preserved in syntax nodes so that errors can
/// later refer to locations of the original source text.
/// These location objects will take up extra memory.
///
/// Default: <c>true</c>
/// </para>
/// <para>Default: <c>true</c></para>
/// </summary>
public bool IncludeLocations { get; set; } = true;

/// <summary>
/// <para>
/// Parser CPU and memory usage is linear to the number of nodes in a document
/// however in extreme cases it becomes quadratic due to memory exhaustion.
/// Parsing happens before validation so even invalid queries can burn lots of
/// CPU time and memory.
///
/// To prevent this you can set a maximum number of nodes allowed within a document.
///
/// This limitation effects the <see cref="Utf8GraphQLParser"/>.
/// </para>
/// <para>To prevent this you can set a maximum number of nodes allowed within a document.</para>
/// <para>This limitation effects the <see cref="Utf8GraphQLParser"/>.</para>
/// </summary>
Comment thread
michaelstaib marked this conversation as resolved.
public int MaxAllowedNodes { get; set; } = int.MaxValue;

/// <summary>
/// <para>
/// Parser CPU and memory usage is linear to the number of tokens in a document
/// however in extreme cases it becomes quadratic due to memory exhaustion.
/// Parsing happens before validation so even invalid queries can burn lots of
/// CPU time and memory.
///
/// To prevent this you can set a maximum number of tokens allowed within a document.
///
/// This limitation effects the <see cref="Utf8GraphQLReader"/>.
/// </para>
/// <para>To prevent this you can set a maximum number of tokens allowed within a document.</para>
/// <para>This limitation effects the <see cref="Utf8GraphQLReader"/>.</para>
/// </summary>
Comment thread
michaelstaib marked this conversation as resolved.
public int MaxAllowedTokens { get; set; } = int.MaxValue;

/// <summary>
/// <para>
/// Parser CPU and memory usage is linear to the number of nodes in a document
/// however in extreme cases it becomes quadratic due to memory exhaustion.
/// Parsing happens before validation so even invalid queries can burn lots of
/// CPU time and memory.
///
/// </para>
/// <para>
/// To prevent this you can set a maximum number of fields allowed within a document
/// as fields is an easier way to estimate query size for GraphQL requests.
/// </para>
/// </summary>
public int MaxAllowedFields { get; set; } = 2048;

/// <summary>
/// <para>
/// The maximum allowed recursion depth when parsing a document.
/// This prevents stack overflow from deeply nested queries.
/// </para>
/// <para>Default: <c>200</c></para>
/// </summary>
public int MaxAllowedRecursionDepth { get; set; } = 200;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,46 @@ public sealed class FusionParserOptions
public bool NoLocations { get; set; }

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

/// <summary>
/// <para>
/// Parser CPU and memory usage is linear to the number of nodes in a document
/// however in extreme cases it becomes quadratic due to memory exhaustion.
/// Parsing happens before validation so even invalid queries can burn lots of
/// CPU time and memory.
///
/// To prevent this you can set a maximum number of nodes allowed within a document.
/// </para>
/// <para>To prevent this you can set a maximum number of nodes allowed within a document.</para>
/// </summary>
public int MaxAllowedNodes { get; set; } = int.MaxValue;

/// <summary>
/// <para>
/// Parser CPU and memory usage is linear to the number of nodes in a document
/// however in extreme cases it becomes quadratic due to memory exhaustion.
/// Parsing happens before validation so even invalid queries can burn lots of
/// CPU time and memory.
///
/// </para>
/// <para>
/// To prevent this you can set a maximum number of fields allowed within a document
/// as fields is an easier way to estimate query size for GraphQL requests.
/// </para>
/// </summary>
public int MaxAllowedFields { get; set; } = 2048;

/// <summary>
/// <para>
/// The maximum allowed recursion depth when parsing a document.
/// This prevents stack overflow from deeply nested queries.
/// </para>
/// </summary>
public int MaxAllowedRecursionDepth { get; set; } = 200;
}
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ private static ParserOptions CreateParserOptions(FusionGatewaySetup setup)
allowFragmentVariables: false,
maxAllowedNodes: options.MaxAllowedNodes,
maxAllowedTokens: options.MaxAllowedTokens,
maxAllowedFields: options.MaxAllowedFields);
maxAllowedFields: options.MaxAllowedFields,
maxAllowedRecursionDepth: options.MaxAllowedRecursionDepth);
}

private SourceSchemaClientConfigurations CreateClientConfigurations(
Expand Down
13 changes: 12 additions & 1 deletion src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,24 @@ public sealed class ParserOptions
/// <param name="maxAllowedFields">
/// The maximum number of fields allowed within a query document.
/// </param>
/// <param name="maxAllowedRecursionDepth">
/// The maximum allowed recursion depth when parsing a document.
/// This prevents stack overflow from deeply nested queries.
/// </param>
public ParserOptions(
bool noLocations = false,
bool allowFragmentVariables = false,
int maxAllowedNodes = int.MaxValue,
int maxAllowedTokens = int.MaxValue,
int maxAllowedFields = 2048)
int maxAllowedFields = 2048,
int maxAllowedRecursionDepth = 200)
Comment thread
michaelstaib marked this conversation as resolved.
{
NoLocations = noLocations;
Experimental = new(allowFragmentVariables);
MaxAllowedTokens = maxAllowedTokens;
MaxAllowedNodes = maxAllowedNodes;
MaxAllowedFields = maxAllowedFields;
MaxAllowedRecursionDepth = maxAllowedRecursionDepth;
}

/// <summary>
Expand Down Expand Up @@ -86,6 +92,11 @@ public ParserOptions(
/// </summary>
public int MaxAllowedFields { get; }

/// <summary>
/// Gets the maximum allowed recursion depth of a parsed document.
/// </summary>
public int MaxAllowedRecursionDepth { get; }

/// <summary>
/// Gets the experimental parser options
/// which are by default switched of.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,7 @@
<data name="Utf8GraphQLParser_Start_MaxAllowedFieldsReached" xml:space="preserve">
<value>The GraphQL request document contains more than {0} fields. Parsing aborted.</value>
</data>
<data name="Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached" xml:space="preserve">
<value>Document exceeds the maximum allowed recursion depth of {0}. Parsing aborted.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ private VariableNode ParseVariable()
/// </summary>
private SelectionSetNode ParseSelectionSet()
{
IncreaseDepth();

var start = Start();

if (_reader.Kind != TokenKind.LeftBrace)
Expand Down Expand Up @@ -222,6 +224,7 @@ private SelectionSetNode ParseSelectionSet()

var location = CreateLocation(in start);

DecreaseDepth();
return new SelectionSetNode(
location,
selections);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public ref partial struct Utf8GraphQLParser
/// </summary>
private ITypeNode ParseTypeReference()
{
IncreaseDepth();

ITypeNode type;
Location? location;

Expand Down Expand Up @@ -40,6 +42,7 @@ private ITypeNode ParseTypeReference()
MoveNext();
location = CreateLocation(in start);

DecreaseDepth();
return new NonNullTypeNode
(
location,
Expand All @@ -50,6 +53,7 @@ private ITypeNode ParseTypeReference()
Unexpected(TokenKind.Bang);
}

DecreaseDepth();
return type;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ private NameNode ParseName()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool MoveNext() => _reader.MoveNext();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IncreaseDepth()
{
if (++_recursionDepth > _maxAllowedRecursionDepth)
{
throw new SyntaxException(
_reader,
string.Format(
Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached,
_maxAllowedRecursionDepth));
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecreaseDepth()
{
--_recursionDepth;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TokenInfo Start()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,37 @@ public ref partial struct Utf8GraphQLParser
/// </param>
private IValueNode ParseValueLiteral(bool isConstant)
{
IncreaseDepth();

IValueNode node;

if (_reader.Kind == TokenKind.LeftBracket)
{
return ParseList(isConstant);
node = ParseList(isConstant);
}

if (_reader.Kind == TokenKind.LeftBrace)
else if (_reader.Kind == TokenKind.LeftBrace)
{
return ParseObject(isConstant);
node = ParseObject(isConstant);
}

if (TokenHelper.IsScalarValue(ref _reader))
else if (TokenHelper.IsScalarValue(ref _reader))
{
return ParseScalarValue();
node = ParseScalarValue();
}

if (_reader.Kind == TokenKind.Name)
else if (_reader.Kind == TokenKind.Name)
{
return ParseEnumValue();
node = ParseEnumValue();
}

if (_reader.Kind == TokenKind.Dollar && !isConstant)
else if (_reader.Kind == TokenKind.Dollar && !isConstant)
{
node = ParseVariable();
}
else
{
return ParseVariable();
throw Unexpected(_reader.Kind);
}

throw Unexpected(_reader.Kind);
DecreaseDepth();
return node;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ public ref partial struct Utf8GraphQLParser
private readonly bool _allowFragmentVars;
private readonly int _maxAllowedNodes;
private readonly int _maxAllowedFields;
private readonly int _maxAllowedRecursionDepth;
private Utf8GraphQLReader _reader;
private StringValueNode? _description;
private int _parsedNodes;
private int _parsedFields;
private int _recursionDepth;
private Utf8MemoryBuilder? _memory;

public Utf8GraphQLParser(
Expand All @@ -32,6 +34,7 @@ public Utf8GraphQLParser(
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_maxAllowedNodes = options.MaxAllowedNodes;
_maxAllowedFields = options.MaxAllowedFields;
_maxAllowedRecursionDepth = options.MaxAllowedRecursionDepth;
_reader = new Utf8GraphQLReader(sourceText, options.MaxAllowedTokens);
_description = null;
}
Expand All @@ -50,6 +53,7 @@ public Utf8GraphQLParser(
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_maxAllowedNodes = options.MaxAllowedNodes;
_maxAllowedFields = options.MaxAllowedFields;
_maxAllowedRecursionDepth = options.MaxAllowedRecursionDepth;
_reader = new Utf8GraphQLReader(sourceText, options.MaxAllowedTokens);
_description = null;
}
Expand All @@ -68,6 +72,7 @@ internal Utf8GraphQLParser(
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_maxAllowedNodes = options.MaxAllowedNodes;
_maxAllowedFields = options.MaxAllowedFields;
_maxAllowedRecursionDepth = options.MaxAllowedRecursionDepth;
_reader = reader;
_description = null;
}
Expand All @@ -87,6 +92,7 @@ public DocumentNode Parse()
try
{
_parsedNodes = 0;
_recursionDepth = 0;
var definitions = new List<IDefinitionNode>(16);

var start = Start();
Expand Down
Loading
Loading