# VM register type confusion

Interpreter opcodes and C helpers dereferencing registers or immediates as heap objects without a runtime type check

_Area: Memory corruption and native code. Seen in 9 disclosed reports; 9 confirmed bounties, median $8,000, range $800-$10,000; latest 2019-09-04. Updated 2026-10-23._

## How it works
A bytecode interpreter keeps values in registers that may hold either heap pointers (objects, arrays) or tagged immediates (fixnum, symbol, nil). Opcode handlers and C helpers assume a register holds a particular kind of object and dereference it directly. When the compiler emits a sequence that leaves a different value in that slot — an immediate, a null, or a non-array — the assumption breaks and the dereference reads attacker-influenced memory. The root cause is a missing runtime type check at the point of use, trusting the compiler to have produced the right shape [E1](https://bugbountylibrary.com/r/h1%3A182484.md)[E4](https://bugbountylibrary.com/r/h1%3A184712.md)[E5](https://bugbountylibrary.com/r/h1%3A185794.md)[E6](https://bugbountylibrary.com/r/h1%3A621308.md)[E7](https://bugbountylibrary.com/r/h1%3A218570.md).

## Where it shows up
- Opcode handlers that read `regs[N]` as an array/object when a flag (e.g. negative argc) signals a splat path [E7](https://bugbountylibrary.com/r/h1%3A218570.md)[E8](https://bugbountylibrary.com/r/h1%3A218233.md)
- C API entry points (`mrb_ary_push`, `mrb_ary_concat`, `mrb_check_frozen`, `mrb_any_to_s`) that call `mrb_ary_ptr`/`mrb_cptr` without checking the receiver type [E3](https://bugbountylibrary.com/r/h1%3A181232.md)[E4](https://bugbountylibrary.com/r/h1%3A184712.md)[E5](https://bugbountylibrary.com/r/h1%3A185794.md)[E6](https://bugbountylibrary.com/r/h1%3A621308.md)
- Generic fallback methods (`to_s`, `inspect`, `hash`) reachable on boxed immediates via `remove_method` [E5](https://bugbountylibrary.com/r/h1%3A185794.md)
- Instance-variable methods called on fixnum/symbol/nil receivers [E6](https://bugbountylibrary.com/r/h1%3A621308.md)
- Compiler codegen branches that push a value when the parent did not request one, or skip array-wrapping at a boundary count [E1](https://bugbountylibrary.com/r/h1%3A182484.md)[E2](https://bugbountylibrary.com/r/h1%3A187536.md)

## How to review
The invariant: every dereference of a register or argument as a heap object must be preceded by a check that the value is a heap pointer of the expected type. For each opcode/helper, ask: can this slot hold an immediate, null, or wrong-type value on some reachable bytecode path? Check the splat/argc<0 path validates the array register is non-null and a real array [E7](https://bugbountylibrary.com/r/h1%3A218570.md)[E8](https://bugbountylibrary.com/r/h1%3A218233.md). Check C helpers type-check `self` and `other` before `mrb_ary_ptr`/`mrb_cptr` [E4](https://bugbountylibrary.com/r/h1%3A184712.md)[E5](https://bugbountylibrary.com/r/h1%3A185794.md)[E6](https://bugbountylibrary.com/r/h1%3A621308.md). In codegen, confirm `push()` is gated on the `val` flag and array-wrapping runs at the exact boundary count [E1](https://bugbountylibrary.com/r/h1%3A182484.md)[E2](https://bugbountylibrary.com/r/h1%3A187536.md).

## Variants seen
- Off-by-one at CALL_MAXARGS leaves first arg a fixnum treated as array [E1](https://bugbountylibrary.com/r/h1%3A182484.md)
- Unconditional push desyncs VM stack, null register dereferenced [E2](https://bugbountylibrary.com/r/h1%3A187536.md)
- Splat/case/redo leaves null RArray passed to push [E3](https://bugbountylibrary.com/r/h1%3A181232.md)
- Non-array operand to concat from malformed `*case` splat [E4](https://bugbountylibrary.com/r/h1%3A184712.md)
- Fallback method dereferences boxed immediate as pointer [E5](https://bugbountylibrary.com/r/h1%3A185794.md)
- Instance-variable method passes fixnum as pointer [E6](https://bugbountylibrary.com/r/h1%3A621308.md)
- Negative argc reads register as array pointer [E7](https://bugbountylibrary.com/r/h1%3A218570.md)[E8](https://bugbountylibrary.com/r/h1%3A218233.md)

## Impact and severity
All nine were crashes (segfault/null or invalid pointer deref) in sandboxed interpreters, so severity is denial of service; where the dereferenced value is attacker-controlled [E6](https://bugbountylibrary.com/r/h1%3A621308.md) it edges toward worse. Embedded/sandboxed deployments made crashes the primary concern.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A182484.md
- E2: https://bugbountylibrary.com/r/h1%3A187536.md
- E3: https://bugbountylibrary.com/r/h1%3A181232.md
- E4: https://bugbountylibrary.com/r/h1%3A184712.md
- E5: https://bugbountylibrary.com/r/h1%3A185794.md
- E6: https://bugbountylibrary.com/r/h1%3A621308.md
- E7: https://bugbountylibrary.com/r/h1%3A218570.md
- E8: https://bugbountylibrary.com/r/h1%3A218233.md
