Oj (Ruby gem) CVE-2026-54592
HIGHSeverity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Remote unauthenticated JSON input crashes the worker with no C/I impact; stack canary makes the outcome a reliable abort, so A:H and AC:L.
Primary rating from Vendor (https://github.com/ohler55/oj).
CVSS VectorVendor: https://github.com/ohler55/oj
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Lifecycle Timeline
2DescriptionCVE.org
Summary
Oj::Doc#each_child, 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:
doc_each_child(~line 1501) incrementsdoc->wherepast the
where_path[MAX_STACK = 100] array with no bounds check, and never restores it (doc->where-- is missing). Calling each_child recursively from inside the yield block therefore drives doc->where beyond the array.
- On the next entry (~line 1478) the function copies the path into a
stack-local buffer:
Leaf save_path[MAX_STACK]; // 800-byte stack buffer
size_t wlen = doc->where - doc->where_path;
if (0 < wlen) {
memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
}When the previous recursive call left doc->where past where_path[100], wlen exceeds MAX_STACK and the memcpy overflows save_path 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
require 'oj'
depth = 200
payload = '[' * depth + '1' + ']' * depth
Oj::Doc.open(payload) do |doc|
r = lambda { doc.each_child { |_| r.call } }
r.call
endRecursion depth <= 99 iterates normally; depth >= 101 aborts. lldb backtrace on the affected build (ruby 3.3.8 / arm64-darwin24):
SIGABRT
#2 __abort
#3 __stack_chk_fail
#4 doc_each_child (oj.bundle, fast.c)Impact
Reliable denial of service: any endpoint that calls Oj::Doc.open(untrusted) { |d| d.each_child ... } 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: doc_each_child now bounds-checks before incrementing doc->where (raising Oj::DepthError) and restores doc->where after the loop, matching the existing each_leaf pattern. Verified on the fixed build: depth >= 101 raises a clean Oj::DepthError instead of aborting.
Credit
Reported by Zac Wang (@7a6163).
AnalysisAI
Denial of service in the Oj Ruby JSON parser (versions <3.17.3) allows remote unauthenticated attackers to crash any process that parses untrusted JSON via Oj::Doc.open followed by a recursive each_child call. A deeply nested JSON document drives doc->where past the 100-element where_path array, causing a subsequent memcpy to overflow an 800-byte stack buffer and trigger the stack canary (SIGABRT). …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | Requires the target application to (a) use the Oj::Doc API specifically - Oj::Doc.open on attacker-controlled JSON - and (b) call Oj::Doc#each_child recursively from inside its own yield block (the documented vulnerable pattern). … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The CVSS 3.1 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5) is consistent with the description: a remote unauthenticated attacker can crash a process by sending a small, deeply-nested JSON payload, with no confidentiality or integrity impact. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker posts a small JSON body such as '[' * 200 + '1' + ']' * 200 to any HTTP endpoint whose handler does Oj::Doc.open(body) { |d| recurse(d) } with a recursive each_child walk. On the 101st recursion the stack canary fires and the worker process aborts (SIGABRT), dropping in-flight requests; repeating the request keeps killing workers and causes a sustained service outage. … |
| Remediation | Vendor-released patch: upgrade the oj gem to 3.17.3 or later, where doc_each_child now bounds-checks doc->where before incrementing it (raising Oj::DepthError) and restores it after the yield loop, mirroring the existing each_leaf pattern; the fix is documented in GHSA-3m6q-jj5j-38c9. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
24 hours: Identify all systems running Oj <3.17.3 and catalog instances parsing untrusted JSON. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
Same weakness CWE-125 – Out-of-bounds Read
View allSame technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-3m6q-jj5j-38c9