# sprintf gem - format string combined attack

sprintf gem in mruby mishandles huge width in %G format, causing negative length, info leak and heap buffer underflow.

- Payout: hidden
- Severity: none
- Program: Shopify
- Found by: aerodudrizzt
- Disclosed: 2017-03-14
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/212239
- Library page: https://bugbountylibrary.com/r/h1%3A212239
- Tags: area:oss domain:oss impact:data-leak impact:dos kind:bounty platform:hackerone tech:ruby technique:source-review vuln:info-disclosure vuln:memory-corruption

**Target:** sprintf gem (mruby string formatting)
**Root cause:** CHECK(l) macro loops on negative l without validating sign, and snprintf return value -1 is added to blen unchecked, allowing negative resize and buffer underflow.
**Steps:**
1. Craft format `% 2147483628G` with float 1234567890.12345678 so need becomes MIN_INT and CHECK resizes to -1
2. Call sprintf with that format; on 32-bit mrb_str_resize(-1) yields length -1 with 0-size buffer
3. Print result to dump adjacent heap memory including secret strings
4. For underflow, repeat the huge-width %G primitive 10 times followed by `!!!!!!!!!!!` to decrement blen by 10 and write past buffer start
**How found:** Code review of CHECK macro and %G handling; noticed width 2**31-20 makes need MIN_INT and snprintf returns -1 on huge width, both feeding unchecked values into resize/blen.
**Exploitation:** Attacker-controlled format string with huge width triggers negative length; output string exposes heap contents (info leak) or writes before buffer via repeated snprintf -1 returns, corrupting heap and crashing.
**Escalation:** Repeating the primitive N times controls underflow depth, enabling targeted heap corruption beyond a single byte.
**Insight:** When auditing format-string implementations, check integer arithmetic around width/precision for signed overflow and always validate snprintf's negative return before advancing the write offset.

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

**Information leak PoC script**

```
secret_password = "thisismysuperdupersecretpassword"

f = 1234567890.12345678
unique = sprintf("% 2147483628G", f)

sample1 = "1" * 50
sample2 = "2" * 100
sample3 = "3" * 200

print unique.length
print unique
```

