Skip to main content

Oj gem CVE-2026-54898

LOW
Use After Free (CWE-416)
2026-06-19 https://github.com/ohler55/oj GHSA-q2gm-54r6-8fwm
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
3.9 LOW

Exploitation requires in-process control of the SAJ handler code (PR:H, AV:L) and a race-like buffer-realloc condition (AC:H); UAF yields limited memory disclosure and possible crash, no scope change.

3.1 AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L
4.0 AV:L/AC:H/AT:N/PR:H/UI:N/VC:L/VI:L/VA:L/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:29 vuln.today
Analysis Generated
Jun 19, 2026 - 21:29 vuln.today

DescriptionCVE.org

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_parseparse:

c
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
}
c
// 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

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

AnalysisAI

Heap use-after-free in the Oj Ruby JSON parser allows malicious SAJ/SAJ2 callback handlers to dereference freed memory by mutating the input JSON string mid-parse, potentially leading to memory disclosure or arbitrary code execution within the Ruby process. The flaw affects the oj gem at versions below 3.17.2 and is triggered when a callback such as hash_start invokes String#replace with a larger payload, causing Ruby to reallocate the backing buffer that the C parser still references. No public exploit identified at time of analysis, but a fully working reproducer is published in the upstream GHSA advisory.

Technical ContextAI

Oj is a high-performance JSON parser written as a Ruby C extension (pkg:rubygems/oj), widely used as a drop-in replacement for the standard JSON library and by frameworks such as Rails for serialization. The vulnerability lives in ext/oj/parser.c where parser_parse extracts a raw const byte* via StringValuePtr() from the Ruby String object and passes it directly into the parse() loop at line 607. This pattern violates Ruby's C-API contract because user-controlled SAJ/SAJ2 callbacks executed mid-parse can mutate the same String (via String#replace, <<, gsub!, etc.), causing ruby_sized_xfree to release the embedded buffer and leaving the C parser with a dangling pointer - a textbook CWE-416 (Use-After-Free) caused by failing to re-fetch the buffer pointer after re-entering Ruby code.

RemediationAI

Vendor-released patch: upgrade the oj gem to version 3.17.3 or later (the GHSA advisory specifies 'fixed in: 3.17.3'); update Gemfile 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. Until upgrade is possible, the only meaningful workaround is to audit and constrain SAJ/SAJ2 handler classes so callbacks (hash_start, hash_end, array_start, array_end, add_value) never invoke mutating methods on the JSON string being parsed - specifically forbid String#replace, <<, gsub!, concat or any operation that can resize the source buffer; alternatively, route untrusted JSON through Oj.load or Oj::Doc instead of Oj::Parser with custom SAJ handlers. Refer to https://github.com/ohler55/oj/security/advisories/GHSA-q2gm-54r6-8fwm for upstream details; note that the handler-audit workaround relies on developer discipline and offers no runtime enforcement.

Share

CVE-2026-54898 vulnerability details – vuln.today

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