# VM stack pointer corruption

Stack or callinfo pointers held across realloc, negative counts, or unwinding that move them out of bounds

_Area: Memory corruption and native code. Seen in 4 disclosed reports; 3 confirmed bounties, median $800, range $200-$800; latest 2019-11-12. Updated 2026-10-23._

## How it works
A VM interpreter caches pointers into its own stack (operand stack pointer, register base, callinfo/proc) and assumes they stay valid for the duration of an opcode or an unwind. That assumption breaks when something inside the opcode re-enters user code or grows the stack: a realloc moves the backing buffer, freeing the old one, while the cached pointer still points at it. Separately, stack-pointer arithmetic that trusts a user-supplied count and checks only one bound lets a negative count move the pointer past the top of the stack into the native frame. Either way the VM later reads or writes through a stale or out-of-range pointer.

## Where to show up
- Opcodes that call back into user code (`mrb_funcall`, coercion like `to_str`, equivalence tests) between reading and writing registers [E1](https://bugbountylibrary.com/r/h1%3A213261.md)[E4](https://bugbountylibrary.com/r/h1%3A194884.md)
- Exception/ensure/rescue unwinding paths that call `stack_extend` or realloc mid-opcode [E1](https://bugbountylibrary.com/r/h1%3A213261.md)[E5](https://bugbountylibrary.com/r/h1%3A295276.md)
- Nested `ensure`/`yield`/`Class.new` blocks that desync environment and stack bookkeeping [E3](https://bugbountylibrary.com/r/h1%3A212107.md)[E5](https://bugbountylibrary.com/r/h1%3A295276.md)
- Stack-pointer arithmetic `op_sp -= n` with a user-controlled `n` and only a lower-bound check [E2](https://bugbountylibrary.com/r/p0%3ACVE-2020-27930.md)
- Legacy interpreters sharing decades-old code across vendors [E2](https://bugbountylibrary.com/r/p0%3ACVE-2020-27930.md)

## How to review
The invariant: any pointer into the VM stack, callinfo, or proc backing store must be re-derived after any call that can grow or realloc the stack, and must never be used across such a call. Check each opcode handler for a cached `regs`/`op_sp`/`ci` held across a re-entrant call. For stack arithmetic, confirm both bounds are checked and negative counts are rejected before pointer movement. For unwinding, confirm callinfo and environment pointers are refreshed after `stack_extend`.

## Variants seen
- Realloc during rescue/ensure frees callinfo while `ci->proc->body.irep` is dereferenced [E1](https://bugbountylibrary.com/r/h1%3A213261.md)
- Negative argc to `callothersubr` shifts operand stack pointer out of bounds [E2](https://bugbountylibrary.com/r/p0%3ACVE-2020-27930.md)
- Nested module/ensure/yield corrupts stack so `mrb_class` dereferences null [E3](https://bugbountylibrary.com/r/h1%3A212107.md)
- `OP_RANGE` writes result through stale `regs` after `mrb_funcall` reallocs [E4](https://bugbountylibrary.com/r/h1%3A194884.md)
- `OP_RESCUE` writes after free when nested `Class.new` triggers `stack_extend` [E5](https://bugbountylibrary.com/r/h1%3A295276.md)

## Impact and severity
Crashes in sandboxed interpreters (mruby, mirb) and, where the freed region is attacker-influenced, potential control-flow hijack [E1](https://bugbountylibrary.com/r/h1%3A213261.md); arbitrary read/write and RCE in a browser font parser [E2](https://bugbountylibrary.com/r/p0%3ACVE-2020-27930.md). Severity rises when the stale pointer is heap-sprayable or the out-of-range pointer reaches the native frame.

## Why fixes fail
Evidence shows repeated UAFs in the same interpreter across different opcodes (range, rescue, ensure), indicating fixes patched individual handlers rather than enforcing the re-derive-after-realloc invariant globally [E1](https://bugbountylibrary.com/r/h1%3A213261.md)[E4](https://bugbountylibrary.com/r/h1%3A194884.md)[E5](https://bugbountylibrary.com/r/h1%3A295276.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A213261.md
- E2: https://bugbountylibrary.com/r/p0%3ACVE-2020-27930.md
- E3: https://bugbountylibrary.com/r/h1%3A212107.md
- E4: https://bugbountylibrary.com/r/h1%3A194884.md
- E5: https://bugbountylibrary.com/r/h1%3A295276.md
