Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash)
### Summary The JSON body processor (`internal/bodyprocessors/json.go`) can be made to crash the whole process with an unrecoverable `fatal error: stack overflow`, using a request body that is well under the recommended `SecRequestBodyLimit` and the default `SecArgumentsLimit`. ### Root cause `readJSON` (json.go:113-143) runs a bounded, best-effort flattening walk (`readItems`) and *afterwards* calls `gjson.Valid(s)` on the raw body if `readItems` returned no error: ```go json := gjson.Parse(s) ... truncated, err = readItems(json, key, maxRecursion, argumentLimit, byteBudget, &usedBytes, &argCount, res) if err != nil { return res, truncated, err } if !gjson.Valid(s) { return res, truncated, errors.New("invalid JSON") } ``` `gjson.Valid` (gjson v1.18.0, `validany` -> `validarray`/`validobject`) recurses once per nesting level with **no depth bound**. `readItems` does have a depth bound (`maxRecursion`), enforced here (json.go:163-182): ```go func readItems(json gjson.Result, objKey []byte, maxRecursion int, argumentLimit int, byteBudget int, usedBytes *int, argCount *int, res map[string][]string) (truncated bool, err error) { if byteBudget > 0 && *usedBytes >= byteBudget { return true, nil // <-- checked first } if argumentLimit > 0 && *argCount >= argumentLimit { return true, nil // <-- checked second } ... if maxRecursion <= 0 { return false, errors.New("max recursion reached while reading json object") } ``` The byte-budget and argument-limit checks run *before* the recursion-depth check, and they short-circuit the walk with `truncated=true, err=nil` instead of recursing further. If the configured `SecArgumentsLimit` (`ArgumentLimit`, default 1000, `internal/corazawaf/waf.go:359`) is reached by earlier, shallow values in the document, `readItems` stops walking *before it ever reaches* a deeply nested tail later in the same document — so the `maxRecursion` error is never produced, `err` comes back `nil`, and `readJSON` falls through to the unconditional `gjson.Valid(s)` call on the complete raw body, including the part `readItems` never visited. This is not a new interaction with the recursion limit itself: at v3.7.0, `gjson.Valid` ran unconditionally before any recursion check at all, so a plain deeply-nested body crashed the process directly. A later fix added a depth check that returns an error before `Valid` runs for the *straightforward* case (nesting reached before any other guard fires). The argument-limit / byte-budget guards added since then (GHSA-6r3q-mjv7-xr8m, GHSA-3ww9-vw83-9w5x) reopened the same crash for the case above, because they short-circuit the walk (and therefore the recursion counter) ahead of the depth check, on both the request and response body path (`ProcessResponse` calls the same `readJSON`, json.go:57-88). Because this is `fatal error: stack overflow`, not a `panic`, it is **not** recoverable by any `recover()` in the calling goroutine — the process terminates unconditionally. ### PoC ```go package bodyprocessors import ( "strings" "testing" ) func TestStackOverflowRepro(t *testing.T) { body := "[" + strings.Repeat("1,", 1000) + strings.Repeat("[", 13_000_000) // 13,002,001 bytes total: under the recommended SecRequestBodyLimit // (13107200, coraza.conf-recommended:78) and default ArgumentLimit (1000, // internal/corazawaf/waf.go:359). _, _, _ = readJSON(body, 20, 1000) } ``` ``` $ go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v runtime: goroutine stack exceeds 1000000000-byte limit fatal error: stack overflow ... github.com/tidwall/gjson.validarray(...) .../[email protected]/gjson.go:2584 github.com/tidwall/gjson.validany(...) .../[email protected]/gjson.go:2499 github.com/tidwall/gjson.validarray(...) .../[email protected]/gjson.go:2589 ... (repeats until the goroutine stack limit is hit) ``` Reproduced against commit `19b86824` (tag `v3.8.0`), both by calling `readJSON` directly and end-to-end through the recommended `coraza.conf-recommended` configuration (JSON `Content-Type`, default `SecArgumentsLimit`, recommended `SecRequestBodyLimit`). ### Impact An unauthenticated attacker who can send an HTTP request body (any endpoint protected by Coraza with the JSON body processor enabled, which is the default for `application/json`) can crash the entire host process with a single request, using a payload well within default and recommended body size and argument-count limits. There is no privilege or interaction requirement, and the crash cannot be caught or mitigated by the integrator (no `recover()` stops a stack-overflow fatal error). This is strictly worse than a CPU-exhaustion or slow-request DoS: the process must be restarted, and every in-flight request/transaction on that process is lost. ### Suggested fix Run an iterative, explicitly-bounded-depth pre-scan (or reuse `readItems`'s own recursion accounting) before calling `gjson.Valid`, and never call `gjson.Valid` on input whose nesting exceeds `maxRecursion`. The response path (`ProcessResponse`) needs the same treatment since it shares `readJSON`. ### AI involvement disclosure - **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code. - **What was generated/assisted:** the initial vulnerability hypothesis and repro shape were supplied by the reporter as an existing written finding; Claude Sonnet 5 independently re-derived the root cause by reading the current source, wrote and ran a fresh PoC test against commit `19b86824` (tag `v3.8.0`), confirmed the crash and stack trace shown above, verified the default configuration values cited (`ArgumentLimit` default, `SecRequestBodyLimit` recommended value) against the current source, and drafted this advisory text. - **Review performed:** reproduced by hand by running the PoC test above with `go test -run TestStackOverflowRepro ./internal/bodyprocessors/ -v` against a clean checkout of commit `19b86824`; observed the `fatal error: stack overflow` and stack trace through `gjson.validarray`/`validany`; traced `readJSON`/`readItems` line by line to confirm the guard ordering described above; the PoC was reviewed by a human maintainer (fzipi) before submission of this advisory.
Recommended action
Recommended action
Upgrade affected packages to a patched version: github.com/corazawaf/coraza/v3 3.8.1.
Technical details
- Vendor
- Not specified
- Product
- github.com/corazawaf/coraza/v3
- Exploitation
- none known
- Evidence
- official
Evidence and sources
This record is attributed to GitHub Advisories. Exploitation status and remediation guidance are kept separate from the vulnerability's technical severity.
Open primary source