CVE-2026-54592: Oj: Stack Buffer Overflow in Oj::Doc#each_child via Deeply Nested Input
Summary
Oj::Doc#eachchild, when invoked recursively over a deeply nested JSON document, overflows a fixed-size stack buffer and aborts the process. This is a denial of service reachable from untrusted JSON.
Details
Two-step chain in ext/oj/fast.c:
1. doceachchild (~line 1501) increments doc->where past the wherepath[MAXSTACK = 100] array with no bounds check, and never restores it (doc->where-- is missing). Calling eachchild recursively from inside the yield block therefore drives doc->where beyond the array.
2. On the next entry (~line 1478) the function copies the path into a stack-local buffer:
c Leaf savepath[MAXSTACK]; // 800-byte stack buffer sizet wlen = doc->where - doc->wherepath; if (0 < wlen) { memcpy(savepath, doc->wherepath, sizeof(Leaf) (wlen + 1)); }
When the previous recursive call left doc->where past wherepath[100], wlen exceeds MAXSTACK and the memcpy overflows savepath on the C stack.
The Oj::Doc parser imposes no JSON nesting-depth limit (it relies on a C-stack pressure check), so deeply nested attacker input reaches this path.
Proof of Concept
ruby require 'oj' depth = 200 payload = '[' depth + '1' + ']' depth Oj::Doc.open(payload) do |doc| r = lambda { doc.eachchild { || r.call } } r.call end
Recursion depth <= 99 iterates normally; depth >= 101 aborts. lldb backtrace on the affected build (ruby 3.3.8 / arm64-darwin24):
SIGABRT #2 abort #3 stackchkfail #4 doceachchild (oj.bundle, fast.c)
Impact
Reliable denial of service: any endpoint that calls Oj::Doc.open(untrusted) { |d| d.eachchild ... } recursively can be crashed with a small deeply-nested payload. On builds with a stack protector (the default, -fstack-protector-strong) the canary aborts the process before the saved return address is used. The Step-1 heap OOB writes into struct doc fields do occur, but are masked in practice because the Step-2 stack overflow crashes first; turning them into anything beyond a crash has not been demonstrated.
Patches
Fixed in 3.17.3: doceachchild now bounds-checks before incrementing doc->where (raising Oj::DepthError) and restores doc->where after the loop, matching the existing eachleaf pattern. Verified on the fixed build: depth >= 101 raises a clean Oj::DepthError instead of aborting.
Credit
Reported by Zac Wang (@7a6163).
Other sources
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.3, Oj::Doc#eachchild, when invoked recursively over a deeply nested JSON document, overflows a fixed-size stack buffer and aborts the process, leading to DoS. In a two-step chain in ext/oj/fast.c, doceachchild increments doc->where past the wherepath[MAXSTACK = 100] array with no bounds check and never restores it (the doc->where-- is missing), so calling eachchild recursively from inside the yield block drives doc->where beyond the array. On the next entry the function copies the path into the 800-byte stack-local buffer savepath[MAXSTACK] using wlen = doc->where - doc->wherepath, so when the previous recursive call left doc->where past wherepath[100] the wlen exceeds MAXSTACK and the memcpy overflows savepath on the C stack; because the Oj::Doc parser imposes no JSON nesting-depth limit (relying on a C-stack pressure check), deeply nested attacker input reaches this path. This issue has been fixed in version 3.17.3.
— NVD
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.3, Oj::Doc#eachchild, when invoked recursively over a deeply nested JSON document, overflows a fixed-size stack buffer and aborts the process, leading to DoS. In a two-step chain in ext/oj/fast.c, doceachchild increments doc-where past the wherepath[MAXSTACK = 100] array with no bounds check and never restores it (the doc-where-- is missing), so calling eachchild recursively from inside the yield block drives doc-where beyond the array. On the next entry the function copies the path into the 800-byte stack-local buffer savepath[MAXSTACK] using wlen = doc-where - doc-wherepath, so when the previous recursive call left doc-where past wherepath[100] the wlen exceeds MAXSTACK and the memcpy overflows savepath on the C stack; because the Oj::Doc parser imposes no JSON nesting-depth limit (relying on a C-stack pressure check), deeply nested attacker input reaches this path. This issue has been fixed in version 3.17.3.
— IBM
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
rubygems/ojto a version that resolves this vulnerability.Fixed in 3.17.3 - Upgrade
Upgrade
Ojto a version that resolves this vulnerability.Fixed in 3.17.3
Event History
Frequently Asked Questions
What is the severity of CVE-2026-54592?
CVE-2026-54592 has a severity rating of high, with a score of 7.5.
How do I fix CVE-2026-54592?
To mitigate CVE-2026-54592, update the oj gem to the latest version that addresses this vulnerability.
What type of vulnerability is CVE-2026-54592?
CVE-2026-54592 is a denial of service vulnerability caused by a stack buffer overflow in the Oj library.
What impact does CVE-2026-54592 have on my application?
CVE-2026-54592 can cause your application to abort or crash when processing deeply nested JSON documents.
Is CVE-2026-54592 exploitable remotely?
Yes, CVE-2026-54592 can be exploited remotely through untrusted JSON input.