CVE-2026-54901 — oj
CVE-2026-54901 is a Use After Free vulnerability in oj. A fix is available for oj — see the affected versions and patch details below.
Oj: Use-After-Free in Oj::Parser array_class/hash_class GC Marking
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-54901.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Real-World Exposure
ojReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Oj::Parser in usual mode does not mark array_class and hash_class references during garbage collection. If GC runs after the class is assigned but before a parse, the class object is reclaimed, leaving the parser holding a dangling VALUE. The subsequent parse call dereferences the freed object, producing a segfault.
Version
- Software: oj gem
- Affected: all versions with
ext/oj/usual.c/ext/oj/parser.c - Latest tested: 3.17.1 (confirmed present)
Details
The parser_mark function in ext/oj/parser.c is registered as the GC mark callback for the parser's TypedData. If array_class (stored as d->array_class in the Usual struct) is not passed to rb_gc_mark, the GC does not know it is referenced and may collect it.
When close_array_class (usual.c:405) later calls rb_funcallv on the collected class VALUE, it accesses freed memory, crashing at RIP: 0x7f... / 0x0000000000000000.
Crash output:
array_class finalized
about to parse
[BUG] Segmentation fault at 0x0000000000000000
close_array_class+0x194 /ext/oj/usual.c:405
parse+0x17b3 /ext/oj/parser.c:715
parser_parse+0x10b /ext/oj/parser.c:1408
RIP: 0x7fd1b46d68b7 RBP: 0x0000000000000000
Reproduce
require 'oj'
p = Oj::Parser.new(:usual,
array_class: (ac = Class.new { def <<(_x); end }))
ObjectSpace.define_finalizer(ac, proc { warn 'array_class finalized' })
ac = nil
GC.start(full_mark: true, immediate_sweep: true) # collect the class
p.parse('[1]') # segfault
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💎RubyGems | oj | all versions | 3.17.3bundle update oj --conservative |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for oj, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update oj to 3.17.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-54901 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-54901 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-54901. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-54901 in your dependencies?
O3 Security finds CVE-2026-54901 across RubyGems dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.