parser: bound recursion depth to prevent stack exhaustion on deeply nested input - #756
parser: bound recursion depth to prevent stack exhaustion on deeply nested input#756ChrisJr404 wants to merge 1 commit into
Conversation
…ested input
The recursive-descent parser had no limit on nesting depth. A document with
deeply nested list values, object values, selection sets, or list types (for
example "{f(a:[[[[...") drives parseValueLiteral/parseSelectionSet/parseType
into unbounded recursion. On sufficiently nested input this exhausts the
goroutine stack and aborts the process with a fatal "stack overflow", and well
before that the repeated stack growth makes parsing time grow super-linearly.
Since Parse operates on untrusted client-supplied documents, this lets a small
request take down a server.
Track the current nesting depth on the parser and return a syntax error once it
exceeds a fixed limit that is far higher than any realistic document needs.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe parser now limits nesting to 500 levels across selection sets, value literals, and type definitions. Deep nesting returns a syntax error. Tests cover excessive nesting and successful parsing below the limit. ChangesParser depth limit
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The parser now rejects excessively nested documents instead of risking stack exhaustion, with focused tests covering the affected constructs; no actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
The recursive-descent parser doesn't limit how deeply it will recurse, so a single small document made of deeply nested constructs can exhaust the goroutine stack and crash the process. Since
parser.Parseruns on untrusted, client-supplied documents, this is reachable by anyone who can send a query.The affected constructs all recurse without any depth bound:
parseValueLiteral→parseList/parseObject→parseValueLiteralparseSelectionSet→parseSelection→parseField→parseSelectionSetparseType→parseTypeReproduce
Well before the hard crash, the growing stack also makes parse time climb super-linearly — a ~64KB nested input already takes several seconds — so even inputs that don't quite reach the crash threshold are an easy denial-of-service vector.
Fix
Track the current nesting depth on the parser and return an ordinary syntax error once it exceeds a fixed limit (500). The three recursive entry points increment the counter on entry and decrement it on exit via
defer, so sibling nodes don't accumulate depth and only genuine nesting counts. The limit is far higher than any realistic GraphQL document needs, so valid input is unaffected; only pathological documents are rejected, and they now get a clean error instead of taking down the process.Tests
Added
parser_depth_test.go:go test ./...passes.Summary by CodeRabbit