CVE-2026-54897
Oj : Use-After-Free in Oj::Doc Iterators via Reentrant Close
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
2.1
Oj : Use-After-Free in Oj::Doc Iterators via Reentrant Close
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
- Record
CVE-2026-54897- CVSS 4.0
- 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 - Weakness
- CWE-416
- Updated
- 2026-11-02
EUVDEUVD-2026-40853
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
- Record
EUVD-2026-40853- CVSS 4.0
- 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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9ppp-w3g4-fh4q
8.7
Oj: Use-After-Free in Oj::Doc Iterators via Reentrant Close
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:
// 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
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 } }
- Record
GHSA-9ppp-w3g4-fh4q- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-416
- Updated
- 2026-11-02
NVD
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
- Record
CVE-2026-54897- CVSS 4.0
- 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 - Weakness
- CWE-416
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-54897
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
- Record
DEBIAN-CVE-2026-54897- CVSS 4.0
- 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 - Updated
- 2026-11-02
OSVGHSA-9ppp-w3g4-fh4q
8.7
Oj: Use-After-Free in Oj::Doc Iterators via Reentrant Close
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:
// 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
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 } }
- Record
GHSA-9ppp-w3g4-fh4q- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-416
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-54897
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. Prior to 3.17.2, Oj::Doc iterators (each_value, each_child, each_leaf) 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.
- Record
UBUNTU-CVE-2026-54897- CVSS 4.0
- 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 - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
ojRubyGems |
through 3.17.2 | 3.17.3 |
ruby-ojDebian:12 |
before 3.14.2-1+deb12u1, plus listed versions 3.14.2-1 |
3.14.2-1+deb12u1 |
ruby-ojDebian:13 |
before 3.16.3-1+deb13u1, plus listed versions 3.16.3-1 |
3.16.3-1+deb13u1 |
ruby-ojDebian:14 |
before 3.17.3-1, plus listed versions 3.16.12-13.16.15-13.16.16-13.16.3-13.17.0-13.17.1-1 |
3.17.3-1 |
ojRubyGems |
before 3.17.3, plus listed versions 0.50.5.10.5.20.6.00.7.0 Show 45 moreShow less0.8.00.9.01.0.01.0.11.0.21.0.31.0.41.0.51.0.61.1.01.1.11.2.01.2.11.2.101.2.111.2.121.2.131.2.21.2.31.2.41.2.51.2.61.2.71.2.81.2.91.3.01.3.21.3.41.3.51.3.61.3.71.4.01.4.11.4.21.4.31.4.41.4.51.4.61.4.6a21.4.72.0.02.0.12.0.102.0.112.0.12 |
3.17.3 |
ruby-ojUbuntu:14.04:LTS |
All versions, plus listed versions 2.0.10-12.1.4-12.4.0-12.5.3-1 |
None known |
ruby-ojUbuntu:16.04:LTS |
All versions, plus listed versions 2.12.10-32.12.10-3build12.12.10-42.12.10-4build1 |
None known |
ruby-ojUbuntu:18.04:LTS |
All versions, plus listed versions 2.17.4-13.4.0-13.4.0-1build1 |
None known |
ruby-ojUbuntu:20.04:LTS |
All versions, plus listed versions 3.8.1-13.9.1-13.9.2-13.10.1-13.10.1-1build1 |
None known |
ruby-ojUbuntu:22.04:LTS |
All versions, plus listed versions 3.11.0-13.13.7-13.13.7-1build13.13.9-13.13.9-1build1 |
None known |
Show 4 moreShow less
| Package | Affected | Fixed |
|---|---|---|
ruby-ojUbuntu:24.04:LTS |
All versions, plus listed versions 3.15.0-13.16.1-13.16.3-13.16.3-1build13.16.3-1build2 |
None known |
ruby-ojUbuntu:25.10 |
All versions, plus listed versions 3.16.3-1build3 |
None known |
ruby-ojUbuntu:26.10 |
All versions, plus listed versions 3.16.15-13.17.0-13.17.1-13.17.3-13.17.4-1 |
None known |
ruby-ojUbuntu:26.04:LTS |
All versions, plus listed versions 3.16.3-1build33.16.12-13.16.15-1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-54897 is a low vulnerability (CVSS 2.1) in Ohler55 Oj, published June 19, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.20%, the 10th percentile. Fixed in oj 3.17.3.
References
github.com/ohler55/oj/security/advisories/GHSA-9ppp-w3g4-fh4q
nvd.nist.gov/vuln/detail/CVE-2026-54897
github.com/rubysec/ruby-advisory-db/blob/master/gems/oj/CVE-2026-54897.yml
cve.org/CVERecord/SearchResults?query=CVE-2026-54897
github.com/advisories/GHSA-9ppp-w3g4-fh4q
security-tracker.debian.org/tracker/CVE-2026-54897
github.com/ohler55/oj
ubuntu.com/security/CVE-2026-54897
cve.org/CVERecord?id=CVE-2026-54897
Left out here: 244 versions. The JSON record has them.