Summary
I found a reproducible native crash in Oj::Doc handling of malformed/no-root inputs.
Oj::Doc.open() and Oj::Doc.open_file() can accept inputs that do not produce a valid root leaf, but still return a Doc object. Later calls to public Doc APIs such as Doc#local_key or Doc#each_leaf can then dereference invalid internal state and crash the Ruby process.
This reproduces in a normal, non-sanitized Ruby build. I am not claiming code execution.
Affected version tested
I confirmed the issue on the latest development/release commit I tested:
oj v3.17.3
commit: bbde91a679728f94c4492ebc3683f4fa3309049f
I also reproduced the main Doc#local_key crash with the installed gem version:
Reproducer
Minimal example:
require "oj"
doc = Oj::Doc.open("")
doc.local_key
This crashes the Ruby process with a native segmentation fault.
The same class of crash is also reachable through file input:
require "oj"
File.write("empty.json", "")
doc = Oj::Doc.open_file("empty.json")
doc.local_key
Inputs that trigger the invalid Doc state
I tested Oj::Doc.open and Oj::Doc.open_file across malformed/no-root inputs. Crashes were observed for inputs such as:
empty input
space
tab
newline
#
/* comment */
/* comment
// comment\n
// comment
Crashing APIs
The crash was observed through:
Doc#local_key
Doc#each_leaf
In my brute-force check across open / open_file, 21 inputs, and 14 Doc APIs, I observed 36 normal-build native crashes.
Observed behavior
The process aborts with a Ruby native crash, for example:
The relevant frame in my logs points to the Oj::Doc implementation, including:
The apparent root cause is that a Doc object can be returned with no valid root/where path state, and later API calls do not reject that invalid state before dereferencing it.
Expected behavior
Malformed or no-root inputs should not produce a Doc object that can later crash the process.
Possible safe behaviors would be:
- reject the input in
Oj::Doc.open() / Oj::Doc.open_file()
- return a
Doc object that consistently represents an empty/invalid document and raises Ruby exceptions for invalid operations
- add guards in
Doc#local_key, Doc#each_leaf, and similar APIs before dereferencing root/where path state
Additional sanitizer-only observations
The reproduction package also includes two secondary observations:
Oj.saj_parse with comment-only inputs can produce an ASan heap-buffer-overread, but I did not observe a normal-build crash.
Oj.load with a custom IO object returning "" on the second read can trigger stack smashing detected in a normal build and an ASan negative-size-param report. This may depend on IO contract assumptions, so I am treating it separately from the main Oj::Doc issue.
The main report is the normal-build Oj::Doc crash from malformed/no-root string and file inputs.
Reproduction package
I attached a minimal reproduction package without convenience binaries:
oj-maintainer-repro_deep_public_minimal.zip
SHA-256: 37225257CB7D02A94C91C90594F90F732D2ACAFC61A4EBDC63F037AF5474406A
The package contains:
README
PoCs
normal-build crash logs
ASan logs
open/open_file API matrix results
gem install reproduction logs
reproduction scripts
SHA256SUMS.txt
I can provide the full package with convenience binaries if useful.
oj-maintainer-repro_deep_public_minimal.zip
Summary
I found a reproducible native crash in
Oj::Dochandling of malformed/no-root inputs.Oj::Doc.open()andOj::Doc.open_file()can accept inputs that do not produce a valid root leaf, but still return aDocobject. Later calls to publicDocAPIs such asDoc#local_keyorDoc#each_leafcan then dereference invalid internal state and crash the Ruby process.This reproduces in a normal, non-sanitized Ruby build. I am not claiming code execution.
Affected version tested
I confirmed the issue on the latest development/release commit I tested:
I also reproduced the main
Doc#local_keycrash with the installed gem version:Reproducer
Minimal example:
This crashes the Ruby process with a native segmentation fault.
The same class of crash is also reachable through file input:
Inputs that trigger the invalid Doc state
I tested
Oj::Doc.openandOj::Doc.open_fileacross malformed/no-root inputs. Crashes were observed for inputs such as:Crashing APIs
The crash was observed through:
In my brute-force check across
open/open_file, 21 inputs, and 14DocAPIs, I observed 36 normal-build native crashes.Observed behavior
The process aborts with a Ruby native crash, for example:
The relevant frame in my logs points to the
Oj::Docimplementation, including:The apparent root cause is that a
Docobject can be returned with no valid root/where path state, and later API calls do not reject that invalid state before dereferencing it.Expected behavior
Malformed or no-root inputs should not produce a
Docobject that can later crash the process.Possible safe behaviors would be:
Oj::Doc.open()/Oj::Doc.open_file()Docobject that consistently represents an empty/invalid document and raises Ruby exceptions for invalid operationsDoc#local_key,Doc#each_leaf, and similar APIs before dereferencing root/where path stateAdditional sanitizer-only observations
The reproduction package also includes two secondary observations:
Oj.saj_parsewith comment-only inputs can produce an ASan heap-buffer-overread, but I did not observe a normal-build crash.Oj.loadwith a custom IO object returning""on the secondreadcan triggerstack smashing detectedin a normal build and an ASannegative-size-paramreport. This may depend on IO contract assumptions, so I am treating it separately from the mainOj::Docissue.The main report is the normal-build
Oj::Doccrash from malformed/no-root string and file inputs.Reproduction package
I attached a minimal reproduction package without convenience binaries:
The package contains:
I can provide the full package with convenience binaries if useful.
oj-maintainer-repro_deep_public_minimal.zip