Skip to content

No limit in nested selection sets #754

Description

@hibrian827

Summary

graphql-go's public parser can be made to exhaust the Go runtime stack while parsing a syntactically valid, deeply nested GraphQL selection set. The issue is in the recursive selection-set parser path under language/parser/parser.go: the parser descends through nested fields before validation or execution depth limits can run, so a single large document can terminate the hosting process. This may have some security-relevant effects but we were not able to find any means of contact for private report.

Affected

Root cause

The exported Parse entry point accepts caller-controlled input and wraps string sources into the parser's source.Source at language/parser/parser.go:56. It then immediately calls parseDocument at language/parser/parser.go:69; a document beginning with { is treated as an anonymous operation at language/parser/parser.go:143, and parseOperationDefinition hands that selection directly to parseSelectionSet at language/parser/parser.go:178. parseSelectionSet parses { Selection+ } through reverse(..., parseSelection, ...) at language/parser/parser.go:317, while reverse repeatedly calls the supplied parse function at language/parser/parser.go:1582 without tracking nesting depth. For ordinary fields, parseSelection dispatches to parseField at language/parser/parser.go:347; when the next token is another {, parseField recursively enters parseSelectionSet at language/parser/parser.go:382. A document shaped like { a { a { ... } } } therefore grows the Go call stack once per nested selection and has no parser-level guard that converts excessive depth into an error.

Reproduction

package main
import (
"fmt"
"strings"
"github.com/graphql-go/graphql/language/parser"
)
func main() {
query := "{" + strings.Repeat("a{", 1_000_000)
_, err := parser.Parse(parser.ParseParams{Source: query})
fmt.Println("parsed without overflow, err:", err) // never reached
}

Suggested fix

diff --git a/language/parser/parser.go b/language/parser/parser.go
index 0c75906..3acfd93 100644
--- a/language/parser/parser.go
+++ b/language/parser/parser.go
@@ -17,6 +17,8 @@ type parseFn func(parser *Parser) (interface{}, error)
// parse operation, fragment, typeSystem{schema, type..., extension, directives} definition
type parseDefinitionFn func(parser *Parser) (ast.Node, error)

+const maxSelectionSetDepth = 1000
+
var tokenDefinitionFn map[string]parseDefinitionFn

func init() {
@@ -47,11 +49,12 @@ type ParseParams struct {
}

type Parser struct {
- LexToken lexer.Lexer
- Source *source.Source
- Options ParseOptions
- PrevEnd int
- Token lexer.Token
+ LexToken lexer.Lexer
+ Source *source.Source
+ Options ParseOptions
+ PrevEnd int
+ Token lexer.Token
+ SelectionSetDepth int
}

func Parse(p ParseParams) (*ast.Document, error) {
@@ -315,6 +318,14 @@ func parseVariable(parser *Parser) (*ast.Variable, error) {
* SelectionSet : { Selection+ }
*/
func parseSelectionSet(parser *Parser) (*ast.SelectionSet, error) {
+ if parser.SelectionSetDepth >= maxSelectionSetDepth {
+ return nil, gqlerrors.NewSyntaxError(parser.Source, parser.Token.Start, "Selection set nesting is too deep.")
+ }
+ parser.SelectionSetDepth++
+ defer func() {
+ parser.SelectionSetDepth--
+ }()
+
start := parser.Token.Start
selections := []ast.Selection{}
if iSelections, err := reverse(parser,

Reported by Team Atlanta.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions