# Format width and index out of bounds

User-influenced width, precision, positional index or vararg shape reaching a printf-style or variadic handler unchecked

_Area: Memory corruption and native code. Seen in 3 disclosed reports; latest 2026-10-21. Updated 2026-11-02._

## How it works
A formatting routine trusts a number or a calling convention that comes from user input. Three shapes appear: a positional index (`%N$`) parsed without bounds checking and later used to index an argument array [E2](https://bugbountylibrary.com/r/h1%3A192318.md); a padding count (decimal count, separator length) that drives a pointer walking backwards past the buffer start [E3](https://bugbountylibrary.com/r/h1%3A180572.md); and a variadic call where the caller picks the argument types from its own value shape while the callee reads them per a different, authoritative table [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md). In each case the assumption "the count/index/type I computed matches what the consumer will read" is false, so reads and writes land outside the intended object.

## Where it shows up
- Format-string parsers that read digits between `%` and `$` or after `%` into an integer used as an index or loop bound [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- C helpers with backward padding loops (`while (topad--) *t-- = '0'`) where the pad count derives from an attacker-controlled integer [E3](https://bugbountylibrary.com/r/h1%3A180572.md).
- Wrappers around variadic C APIs (TIFFSetField and similar) that choose argument types from the language-level value instead of the library's field definition [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).
- Code that registers custom field/format metadata and assumes the registration overrides built-in definitions [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).
- Any place a width, precision, count or index crosses from parsed input into pointer arithmetic without a clamp.

## How to review
For each sink, find the exact property that must hold and check it is enforced at the sink, not earlier:
- Positional index: `index < argc` before any array access; reject or clamp huge values [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Padding: prove `dec` (plus separator) fits the allocated buffer before the backward loop runs; the loop must never write before the buffer start [E3](https://bugbountylibrary.com/r/h1%3A180572.md).
- Variadic wrappers: the argument types passed must equal what the callee's *effective* definition says. Query the library after registration (`TIFFFindField`, pass-count/write-count accessors) rather than trusting your own table [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).
Treat every user-supplied integer that becomes a loop bound, index or vararg selector as hostile until clamped at the point of use.

## How finders spotted it
- Read the format parser and noticed the number between `%` and `$` was never validated; confirmed corruption with a huge index under ASan/GDB [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Read `_php_math_number_format_ex()` and spotted the unbounded backward padding loop; drove it out of bounds with maximal decimal count and a large separator [E3](https://bugbountylibrary.com/r/h1%3A180572.md).
- Noticed the encoder derived vararg shape from the Python value while assuming its own field registration governed libtiff; queried libtiff 4.5.0 after the merge call and found built-in definitions unchanged (passcount/writecount mismatches), then confirmed with crafted TIFFs [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).

## Variants seen
- Unchecked positional index in a sprintf implementation [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Unchecked padding count in a number-formatting helper [E3](https://bugbountylibrary.com/r/h1%3A180572.md).
- Caller/callee disagreement over variadic argument types in a library wrapper [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).

## Impact and severity
Out-of-bounds read/write of interpreter objects, heap overflow and potential code execution [E2](https://bugbountylibrary.com/r/h1%3A192318.md); write out of bounds and SIGSEGV [E3](https://bugbountylibrary.com/r/h1%3A180572.md); process memory disclosure and segfault, with leaked pointers defeating ASLR for chaining with other bugs [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md). Severity rises when the leak or corruption is reachable from parsed files or untrusted input and can be combined with a second memory bug [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md).

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **Pillow (pip)** <= 12.3.0, fixed in 13.0.0: Pillow: Memory disclosure and segfault in libtiff encoder via crafted TIFF tags (CVE-2026-112902). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md. Fix: https://github.com/python-pillow/Pillow/commit/af56ace297b0991d02988fcc2ea933e4824b4fb3
- **PHP**: Memory corruption due to missing check size in _php_math_number_format_ex(). Report: https://bugbountylibrary.com/r/h1%3A180572.md

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-xvh5-g6gv-559v.md
- E2: https://bugbountylibrary.com/r/h1%3A192318.md
- E3: https://bugbountylibrary.com/r/h1%3A180572.md
