CVE-2026-54897: Oj : Use-After-Free in Oj::Doc Iterators via Reentrant Close
Summary
Oj::Doc iterators (eachvalue, eachchild, eachleaf) are vulnerable to a heap use-after-free. When a Ruby block yielded during iteration calls doc.close or d.close, the document's heap memory is freed while the C iterator is still running. When control returns from the block, the iterator reads from the freed region, producing a use-after-free accessible from pure Ruby.
Version
- Software: oj gem - Affected: all versions with ext/oj/fast.c - Latest tested: 3.17.1 (confirmed present)
Details
The iterators in ext/oj/fast.c follow the pattern:
c // fast.c:1505 (doceachchild) static VALUE doceachchild(VALUE self, ...) { ... while (cur != NULL) { rbyield(...); // ← Ruby block executes here cur = cur->next; // ← cur is now freed if block called close() } }
rbyield can invoke arbitrary Ruby code, including calling close() on the Doc or any child node, which calls rubysizedxfree on the backing buffer. On return, the C code reads cur->next from the freed region. All three iterators are affected.
ASAN report (eachchild variant): ==253632==ERROR: AddressSanitizer: heap-use-after-free on address 0x5210000bd080 READ of size 8 at 0x5210000bd080 thread T0 #0 doceachchild /ext/oj/fast.c:1505 0x5210000bd080 is located 896 bytes inside of 4064-byte region [0x5210000bcd00, 0x5210000bdce0) freed by thread T0 here: #0 free #1 rubysizedxfree (libruby-3.3.so.3.3)
All three iterators trigger the same freed region (fd shadow bytes): 0x5210000bd080:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Reproduce
ruby require 'oj' eachchild Oj::Doc.open('[1,2]') { |doc| doc.eachchild { |d| d.close } } eachvalue Oj::Doc.open('[1,2]') { |doc| doc.eachvalue { |v| doc.close } } eachleaf Oj::Doc.open('[1,[2]]') { |doc| doc.eachleaf { |d| d.close } }
Other sources
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (eachvalue, eachchild, eachleaf) were vulnerable to a heap use-after-free. When a Ruby block yielded during iteration calls doc.close or d.close, the document's heap memory is freed while the C iterator is still running. When control returns from the block, the iterator reads from the freed region, producing a use-after-free accessible from pure Ruby. This issue has been fixed in version 3.17.2.
— MITRE
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
oj gemto a version that resolves this vulnerability.Fixed in 3.17.2 - Operational
If using Oj::Doc iterators (each_value, each_child, each_leaf), do not call doc.close or d.close from inside the block that yields during iteration (e.g., Oj::Doc.open(...){|doc| doc.each_child{|d| ... } }).
Event History
Frequently Asked Questions
What is the severity of CVE-2026-54897?
CVE-2026-54897 has a severity rating of 65.
What are the affected components in CVE-2026-54897?
CVE-2026-54897 affects `Oj::Doc` iterators including `each_value`, `each_child`, and `each_leaf`.
How do I fix CVE-2026-54897?
To fix CVE-2026-54897, avoid calling `doc.close` or `d.close` within the Ruby block while iterating.
What type of vulnerability is CVE-2026-54897 classified as?
CVE-2026-54897 is classified as a Use After Free vulnerability.
When was CVE-2026-54897 published?
CVE-2026-54897 was published on June 19, 2026.