graphql-go / graphql-go/graphql
No limit in nested selection sets
- 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
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