graphql-go / graphql-go/graphql

No limit in nested selection sets

Open
#754 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
10.1k
Forks
845
PR merge metrics
No merged PRs in 30d

Description

## 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
- Project: graphql
- Repo: https://github.com/graphql-go/graphql
- Pinned ref: 7cd8e416df5584b863584033651b658e0098b3a7

## 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
```go
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
```001-fix.diff
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.*

Contributor guide

Open the contributing guide

Research direction

Read language/parser/parser.go from the exported Parse entry point through parseSelectionSet and parseField, then run the provided deeply nested selection-set reproduction. Done means excessive nesting returns a parser syntax error instead of exhausting the Go runtime stack, while ordinary selection sets continue to parse.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, graphql
Domain
backend-api-design, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.