{"id":"CVE-2026-54901","title":"Oj: Use-After-Free in Oj::Parser array_class/hash_class GC Marking","summary":"Oj: Use-After-Free in Oj::Parser array_class/hash_class GC Marking","severity":"high","cwe":["CWE-416"],"vendor":"oj","product":"oj","ecosystem":"rubygems","affected":["oj < 3.17.2"],"patched":["oj 3.17.3"],"published":"2026-06-19","updated":"2026-06-19","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-vwm4-62gf-x745","references":[{"url":"https://github.com/ohler55/oj/security/advisories/GHSA-vwm4-62gf-x745"},{"url":"https://github.com/advisories/GHSA-vwm4-62gf-x745"}],"tags":["ghsa","rubygems"],"ingestedAt":"2026-06-22T13:35:24.410Z","epss":0.00428,"epssPercentile":0.36532,"slug":"CVE-2026-54901","body":"## Overview\n\n### Summary\n\n`Oj::Parser` in usual mode does not mark `array_class` and `hash_class` references during garbage collection. If GC runs after the class is assigned but before a parse, the class object is reclaimed, leaving the parser holding a dangling VALUE. The subsequent `parse` call dereferences the freed object, producing a segfault.\n\n### Version\n\n- **Software**: oj gem\n- **Affected**: all versions with `ext/oj/usual.c` / `ext/oj/parser.c`\n- **Latest tested**: 3.17.1 (confirmed present)\n\n### Details\n\nThe `parser_mark` function in `ext/oj/parser.c` is registered as the GC mark callback for the parser's `TypedData`. If `array_class` (stored as `d->array_class` in the `Usual` struct) is not passed to `rb_gc_mark`, the GC does not know it is referenced and may collect it.\n\nWhen `close_array_class` (`usual.c:405`) later calls `rb_funcallv` on the collected class VALUE, it accesses freed memory, crashing at `RIP: 0x7f... / 0x0000000000000000`.\n\nCrash output:\n```\narray_class finalized\nabout to parse\n[BUG] Segmentation fault at 0x0000000000000000\n    close_array_class+0x194  /ext/oj/usual.c:405\n    parse+0x17b3             /ext/oj/parser.c:715\n    parser_parse+0x10b       /ext/oj/parser.c:1408\nRIP: 0x7fd1b46d68b7  RBP: 0x0000000000000000\n```\n\n### Reproduce\n\n```ruby\nrequire 'oj'\np = Oj::Parser.new(:usual,\n  array_class: (ac = Class.new { def <<(_x); end }))\nObjectSpace.define_finalizer(ac, proc { warn 'array_class finalized' })\nac = nil\nGC.start(full_mark: true, immediate_sweep: true)  # collect the class\np.parse('[1]')  # segfault\n```\n\n## Affected packages\n\n- `oj < 3.17.2`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `oj 3.17.3`","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}