CVE-2022-21708 (Go, published 2022-01-21T23:15Z)
Description: value: graphql-go is a GraphQL server with a focus on ease of use. In versions prior to 1.3.0 there exists a DoS vulnerability that is possible due to a bug in the library that would allow an attacker with specifically designed queries to cause stack overflow panics. Any user with access to the GraphQL handler can send these queries and cause stack overflows. This in turn could potentially compromise the ability of the server to serve data to its users. The issue has been patched in version `v1.3.0`. The only known workaround for this issue is to disable the `graphql.MaxDepth` option from your schema which is not recommended.
Severity: LOW (CVSS v3 base score: 6.5)
Weakness: CWE-674 \u2014 Uncontrolled Recursion
The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
Repository: https://github.com/graph-gophers/graphql-go
Files changed:
graphql_test.go
internal/validation/validate_max_depth_test.go
internal/validation/validation.go
Commit message: validation: fix bug in maxDepth fragment spread logic (#492)



Vulnerable code:
	type Starship {}
			actual := validateMaxDepth(opc, op.Selections, 1)
		if validateMaxDepth(opc, op.Selections, 1) {
func validateMaxDepth(c *opContext, sels []types.Selection, depth int) bool {
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, depth+1)
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, depth)
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, depth)

Fixed code:

func TestCircularFragmentMaxDepth(t *testing.T) {
	withMaxDepth := graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.MaxDepth(2))
	gqltesting.RunTests(t, []*gqltesting.Test{
		{
			Schema: withMaxDepth,
			Query: `
	              query {
	                  ...X
	              }

	              fragment X on Query {
	                  ...Y
	              }
	              fragment Y on Query {
	                  ...X
	              }
	          `,
			ExpectedErrors: []*gqlerrors.QueryError{{
				Message: `Cannot spread fragment "X" within itself via Y.`,
				Rule:    "NoFragmentCycles",
				Locations: []gqlerrors.Location{
					{Line: 7, Column: 20},
					{Line: 10, Column: 20},
				},
			}},
		},
	})
}
		enemies: [Character]
	type Starship {
		id: ID!
	}
		enemies: [Character]
		enemies: [Character]
		{
			name: "spreadAtDifferentDepths",
			query: `
			fragment character on Character {
				name # depth + 0
				friends { # depth + 0
					name # depth + 1
				}
			}

			query laterDepthValidated {
				...character # depth 1 (+1)
				enemies { # depth 1
					friends { # depth 2
						...character # depth 2 (+1), should error!
					}
				}
			}
			`,
			depth:   2,
			failure: true,
		},
		{
			name: "spreadAtSameDepth",
			query: `
			fragment character on Character {
				name # depth + 0
				friends { # depth + 0
					name # depth + 1
				}
			}
			query {
				characters { # depth 1
					friends { # depth 2
						...character # depth 3 (+1)
					}
					enemies { # depth 2
						...character # depth 3 (+1)
					}
				}
			}
			`,
			depth: 4,
		},
		{
			name: "fragmentCycle",
			query: `
			fragment X on Query { ...Y }
			fragment Y on Query { ...Z }
			fragment Z on Query { ...X }

			query {
				...X
			}
			`,
			depth:   10,
			failure: true,
		},
			actual := validateMaxDepth(opc, op.Selections, nil, 1)
		if validateMaxDepth(opc, op.Selections, nil, 1) {
//
// The visited map is necessary to ensure that max depth validation does not get stuck in cyclical
// fragment spreads.
func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool {
	if visited == nil {
		visited = map[*types.FragmentDefinition]struct{}{}
	}
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1)

			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth)

			if _, ok := visited[frag]; ok {
				// we've already seen this fragment, don't check depth again.
				continue
			}
			visited[frag] = struct{}{}

			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth)