# Format width precision integer overflow

Signed overflow in width/precision size math and unchecked snprintf -1 return in format handlers

_Area: Memory corruption and native code. Seen in 2 disclosed reports; latest 2017-09-22. Updated 2026-11-01._

## How it works

printf-style formatters compute a required buffer size from user-controlled width and precision, then resize and write. The assumption that breaks: that these derived lengths are positive and that the size arithmetic cannot overflow. A width near `INT_MAX` makes `need = width + constant` wrap to a negative signed int, so the size check passes (negative is "small enough") and the resize/write math underflows the buffer. Separately, `snprintf` returns `-1` on failure; code that does `blen += n` without checking turns that into a negative offset, moving the write pointer before the buffer [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md).

## Where it shows up

- `CHECK(l)`-style macros that loop or compare on a signed length without rejecting negatives [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Size math like `need = width + constant` or `width * n` in signed `int`
- `blen += snprintf(...)` or any offset advanced by an unchecked return value [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- `%G`/`%f` float formatting paths that compute width separately from the write [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Positional argument parsing: the number between `%` and `$` used as an index without bounds check [E3](https://bugbountylibrary.com/r/h1%3A192318.md)

## How to review

For every width/precision value, trace it to the size computation and confirm the arithmetic is done in a type that cannot wrap for the full input range (or is range-checked before use). The invariant: any derived length or offset must be proven `>= 0` and `<= remaining buffer` before it is used to resize or advance a pointer. Check every `snprintf`/`vsnprintf` return for `< 0` before adding it to an offset. For positional indices, confirm the parsed index is bounds-checked against the argument count before indexing [E3](https://bugbountylibrary.com/r/h1%3A192318.md).

## How finders spotted it

- Read the `CHECK` macro and `%G` handling; noticed the resize loop used a signed comparison and that `snprintf`'s return was added to `blen` unchecked, then reasoned about widths near `INT_MAX` [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Noticed width `2**31-20` makes `need` wrap to `MIN_INT` and that huge widths make `snprintf` return `-1`, both feeding unchecked values into resize/offset [E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Read `mrb_vformat`'s parser and saw no validation of the number between `%` and `$`; confirmed corruption with ASan/GDB [E3](https://bugbountylibrary.com/r/h1%3A192318.md)

## Variants seen

- Signed overflow in size math from huge width, bypassing the size check [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Unchecked `snprintf` `-1` return decrementing the write offset [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)
- Unbounded positional index (`%N$`) used as an array index, out-of-bounds read/write of value objects [E3](https://bugbountylibrary.com/r/h1%3A192318.md)

## Impact and severity

Heap underflow and corruption, memory disclosure, and in E3 corruption of interpreter value objects with potential code execution [E1](https://bugbountylibrary.com/r/h1%3A212241.md)[E2](https://bugbountylibrary.com/r/h1%3A212239.md)[E3](https://bugbountylibrary.com/r/h1%3A192318.md). Impact rises when the primitive is repeatable: repeating it N times controls underflow depth, enabling targeted corruption beyond a single byte [E2](https://bugbountylibrary.com/r/h1%3A212239.md). A single unchecked index reaching object memory (E3) is the most severe shape.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A212241.md
- E2: https://bugbountylibrary.com/r/h1%3A212239.md
- E3: https://bugbountylibrary.com/r/h1%3A192318.md
