[TT-17945] Fix stack overflow on cyclic fragment spreads - #448
Open
ermirizio wants to merge 2 commits into
Open
Conversation
nestedDepthCalc.calculateNestedDepth and depthForFragment in
astnormalization were mutually recursive with no cycle protection. A
GraphQL document whose fragments spread each other in a cycle (e.g.
"fragment A { ...B }" / "fragment B { ...A }") made this recurse
forever, crashing the whole process with an unrecoverable
"fatal error: stack overflow" - a remotely triggerable DoS against any
gateway exposing GraphQL.
Track fragment names currently being resolved on the active call
path; if depthForFragment is asked to resolve a name it's already in
the middle of resolving, report a normal fragment-cycle error instead
of recursing. fragmentSpreadInlineVisitor.EnterDocument already stops
the walk on report errors, so this is enough to turn the crash into a
handled validation-style error.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… recursion Apply the same fix as the v1 module: FragmentSpreadDepth's depth calculation is currently unused by v2's fragment inliner (dead code), so this isn't reachable through the same crash path today, but it's exported and identical to the vulnerable v1 code, so harden it too before anything starts calling it again.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
fatal error: stack overflowinastnormalization.(*nestedDepthCalc):calculateNestedDepthanddepthForFragmentwere mutually recursive with no cycle detection, so a GraphQL document whose fragments spread each other in a cycle (e.g.fragment A { ...B }/fragment B { ...A }) recursed forever duringRequest.Normalize().recover().depthForFragment; if a name is re-entered while still being resolved, it reportsoperationreport.ErrFragmentSpreadFormsCycleinstead of recursing.fragmentSpreadInlineVisitor.EnterDocumentalready stops the walk on report errors, so no other call site needed to change.v2module (v2/pkg/astnormalization/fragmentspread_depth.go).FragmentSpreadDepthis currently dead code there (v2's fragment inliner doesn't call it, so this specific crash isn't reachable through v2 today) but it's exported, byte-for-byte identical to the vulnerable v1 code, and cheap to harden preemptively.Jira: https://tyktech.atlassian.net/browse/TT-17945
Related: TykTechnologies/tyk#8567 (defense-in-depth reorder in the gateway itself)
Test plan
TestRealDepthCalculator_CyclicFragmentSpreadto bothpkg/astnormalizationandv2/pkg/astnormalization, each building a two-fragment cycle and assertingFragmentSpreadDepth.Getreturns promptly with a "forms fragment cycle" error rather than hanging/crashing (guarded by a 5s timeout so a regression fails the test instead of hanging CI).calculateNestedDepth/depthForFragmentcall chain.go test ./pkg/astnormalization/...(both modules) — passgo test ./pkg/graphql/...— passgo vet ./pkg/astnormalization/...(both modules) — clean🤖 Generated with Claude Code