Skip to main content

Oj gem CVE-2026-54897

LOW
Use After Free (CWE-416)
2026-06-19 https://github.com/ohler55/oj GHSA-9ppp-w3g4-fh4q
2.1
CVSS 4.0 · Vendor: https://github.com/ohler55/oj

Severity by source

Vendor (https://github.com/ohler55/oj) PRIMARY
2.1 LOW
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
vuln.today AI
5.8 MEDIUM

Requires in-process Ruby code that calls the vulnerable iterator with a close-invoking block (AV:L, PR:L, AC:H); reliable memory corruption is hard, crash impact is high (A:H), info/integrity limited (C:L/I:L).

3.1 AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:H
4.0 AV:L/AC:H/AT:N/PR:L/UI:N/VC:L/VI:L/VA:H/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/ohler55/oj).

CVSS VectorVendor: https://github.com/ohler55/oj

Attack Vector
Local
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
X

Lifecycle Timeline

3
CVSS changed
Jul 01, 2026 - 00:22 NVD
2.1 (LOW)
Source Code Evidence Fetched
Jun 19, 2026 - 21:28 vuln.today
Analysis Generated
Jun 19, 2026 - 21:28 vuln.today

DescriptionCVE.org

Summary

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.

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 (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

Reproduce

ruby
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 } }

AnalysisAI

Heap use-after-free in the Oj Ruby gem's Oj::Doc iterators (each_value, each_child, each_leaf) allows a Ruby block executed during iteration to free the underlying document buffer via doc.close, after which the native C iterator in ext/oj/fast.c dereferences the freed region. The flaw is reachable from pure Ruby and confirmed by an AddressSanitizer report against version 3.17.1, with no public exploit identified at time of analysis but a clear reproducer published in the GHSA advisory. Applications that parse attacker-influenced JSON with Oj::Doc and pass user-supplied callbacks into these iterators are most at risk.

Technical ContextAI

Oj is a widely used high-performance JSON parser for Ruby implemented as a C extension. The vulnerable code lives in ext/oj/fast.c, where iterator functions walk a linked-list of parsed nodes and invoke rb_yield inside the loop. Because rb_yield can run arbitrary Ruby - including Oj::Doc#close, which calls ruby_sized_xfree on the document's backing buffer - the iterator's cur pointer references freed heap memory when control returns from the block. The root cause is the CWE-416 (Use After Free) pattern: re-entrant invocation of native code that mutates lifetimes the caller still depends on. CPE identifier pkg:rubygems/oj covers all distributions of the affected gem.

RemediationAI

Vendor-released patch: upgrade the Oj gem to version 3.17.3 or later (the GHSA advisory at https://github.com/ohler55/oj/security/advisories/GHSA-9ppp-w3g4-fh4q lists < 3.17.2 as vulnerable and 3.17.3 as fixed). Update Gemfile constraints to gem 'oj', '>= 3.17.3' and run bundle update oj, then redeploy any long-running Ruby processes so the patched native extension is loaded. If immediate upgrade is not possible, audit code for uses of Oj::Doc#each_value, each_child, and each_leaf and ensure the yielded block does not call close on the document or any node - wrap iterators so callbacks cannot reach close, or switch parsing paths to Oj.load/Oj.compat_load which use a different code path; the trade-off is reduced streaming performance and potentially higher memory use for large documents.

Share

CVE-2026-54897 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy