# sprintf combined format string attack

Integer overflow in mruby sprintf CHECK macro and unchecked snprintf return let huge %G widths underflow the heap buffer, leaking memory and corrupting heap.

- Payout: hidden
- Severity: medium
- Program: Ruby
- Found by: aerodudrizzt
- Disclosed: 2017-09-22
- CVE: CVE-2017-0898
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/212241
- Library page: https://bugbountylibrary.com/r/h1%3A212241
- Tags: area:oss domain:oss impact:data-leak impact:dos kind:bounty platform:hackerone sev:medium tech:ruby technique:source-review vuln:info-disclosure vuln:memory-corruption

**Target:** sprintf format string handling in mruby (C source)
**Root cause:** CHECK(l) macro loops on signed length without rejecting negative values, so a width like 2**31-20 overflows `need` negative and bypasses size checks; separately snprintf returns -1 on failure and blen += n decrements the buffer offset.
**Steps:**
1. Craft format `% 2147483628G` with a float arg so need = width+20 overflows to a negative value, bypassing CHECK's resize loop.
2. On 32-bit, mrb_str_resize(-1) allocates a 0-size buffer but reports length -1, so printing the string dumps adjacent heap memory (leaks secret_password).
3. For underflow: build format `"% 2147483628G" * 10 + "!!!!!!!!!!!"` with 20 float args; each snprintf failure returns -1, decrementing blen by 1 per primitive.
4. After 10 decrements blen is -10, so the trailing `!` bytes write 10 bytes before the buffer, corrupting the preceding string and crashing with double free/corruption.
**How found:** Reading the CHECK macro and G-format handling, noticing the resize loop uses signed comparison and snprintf's return value is added to blen without checking for -1; then constructed widths near INT_MAX to force overflow.
**Exploitation:** Attacker-controlled format strings passed to sprintf with huge width specifiers cause out-of-bounds reads (info leak of heap contents) and out-of-bounds writes before the buffer (heap corruption leading to crash/possible code execution).
**Insight:** When auditing format-string implementations, target width/precision values near INT_MAX so `need = width + constant` overflows signed int, and always check snprintf's -1 return since it silently moves a write cursor backwards.

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

**Information leak PoC script via sprintf %G**

```
secret_password = "thisismysuperdupersecretpassword"

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

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

print unique.length
print unique
```

