# Segfault in mruby, mruby_engine and the parent MRI Ruby due to null pointer dereference

Unsafe peephole optimization in mruby codegen elides a MOVE before JMPNOT, causing a non-closure lambda to be passed as a block and a null-pointer deref segfault in mruby and parent MRI Ruby.

- Payout: $10,000
- Severity: high
- Program: shopify-scripts
- Credited: dkasak
- Disclosed: 2016-12-16
- Write-up quality: 9/10
- Original: https://hackerone.com/reports/181828
- Library page: https://bugbountylibrary.com/r/h1%3A181828
- Tags: domain:oss impact:dos kind:bounty platform:hackerone sev:high tech:ruby technique:regression technique:source-review vuln:dos vuln:memory-corruption

**Target:** mruby VM / mruby-compiler codegen (sandboxed script engine)
**Root cause:** Peephole rule in codegen.c rewrites `MOVE R3 R1; JMPNOT R3` to `JMPNOT R1`, dropping the MOVE that loads the block argument; the stale register holds a non-closure lambda whose RProc->env is NULL, dereferenced in vm.c line 1266.
**Steps:**
1. Write `crash.rb`: `def method; yield; end` then `method(&a &&= 0)`
2. Run `mruby crash.rb` or `sandbox crash.rb`
3. Observe segfault from null deref at `regs[0] = m->env->stack[0]` in vm.c
4. Inspect `mruby -v crash.rb` bytecode: OP_JMPNOT R1 followed by OP_SENDB R2 :method 0 with R3 never set
5. Confirm peephole in codegen.c line 350 elides `MOVE R3 R1` before `JMPNOT R3`
**How found:** Fuzzing/experimenting with valid Ruby programs; noticed `method(&a &&= 0)` segfaults, dumped bytecode with `mruby -v`, spotted R3 never re-set in the JMPNOT true branch, then traced to the peephole rule by comparing against a non-crashing variant where instruction shuffling prevented adjacency.
**Exploitation:** Any valid Ruby script submitted to the sandboxed mruby_engine triggers a segfault, crashing the engine and the parent MRI Ruby process — a DoS against the multi-tenant script execution service.
**Escalation:** Crash propagates from mruby VM up through mruby_engine into the parent MRI Ruby process, taking down the host interpreter rather than just the sandbox.
**Insight:** Compiler peephole optimizations that elide register moves are unsafe when the register is later consumed implicitly by calling convention (block slot A+C+1); audit peephole rules against implicit register uses, and use `-v` bytecode dumps to spot stale registers.
