# Format string width index out of bounds

User-influenced string passed as printf format, or unchecked %N$ positional index in a format parser

_Area: Memory corruption and native code. Seen in 2 disclosed reports; 2 confirmed bounties: $1,000 and $1,000; latest 2016-05-01. Updated 2026-11-02._

## How it works
A format handler trusts two things it should not: that the format string is a constant, and that numeric fields inside it (width, precision, positional index) are small and valid. When an attacker-influenced string reaches the format argument itself, specifiers like `%n`/`%Z` are interpreted instead of printed. When a parser reads the number between `%` and `$` without bounds checking, that number is later used to index the argument/value array, reading or writing outside it. Both break the same invariant: the format string and its numeric fields must never be attacker-controlled.

## Where it shows up
- Error/exception paths that build a message from a user-supplied identifier (class name, function name, error string) and pass it as the format argument [E1](https://bugbountylibrary.com/r/h1%3A106548.md)[E3](https://bugbountylibrary.com/r/h1%3A127212.md).
- Calls to printf-family internals (`zend_throw_error`, `zend_throw_exception_ex`) where the message is the format, not a `%s` argument [E1](https://bugbountylibrary.com/r/h1%3A106548.md)[E3](https://bugbountylibrary.com/r/h1%3A127212.md).
- Format parsers that read a positional index `%N$` and use it to index a value array without validating against the array length [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Newly added error paths — fresh code is where the missing `%s` hides [E1](https://bugbountylibrary.com/r/h1%3A106548.md).
- Language-specific specifiers (`%Z` zval in PHP) that turn a leak into a write/RCE primitive [E3](https://bugbountylibrary.com/r/h1%3A127212.md).

## How to review
For every call into a printf-family function, confirm the format argument is a literal and every user-influenced value is passed as a `%s` argument. For format parsers, confirm the positional index is bounds-checked against the argument count before any array access, and that width/precision are clamped. The property: no attacker-controlled bytes are ever parsed as format syntax, and no parsed numeric field indexes memory without a range check.

## How finders spotted it
- Noticed PHP 7.0.0's new class-name error path, traced the call to `zend_throw_or_error` in `Zend/zend_execute_API.c:221`, and saw the message passed as the format argument rather than a `%s` argument [E1](https://bugbountylibrary.com/r/h1%3A106548.md).
- Code review of `mrb_vformat`'s parser noticed no validation of the number between `%` and `$`; a minimal sprintf with a huge positional index confirmed corruption under ASan/GDB [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Code review of `ext/snmp/snmp.c:533` spotted `snmp_errstr` passed to `zend_throw_exception_ex()` without `%s`, then tested specifiers including `%Z` to escalate [E3](https://bugbountylibrary.com/r/h1%3A127212.md).

## Variants seen
- User-controlled identifier (class name) as the format string, giving `%n` write-what-where [E1](https://bugbountylibrary.com/r/h1%3A106548.md).
- Unchecked positional index `%N$` in a format parser, giving out-of-bounds read/write of value objects [E2](https://bugbountylibrary.com/r/h1%3A192318.md).
- Attacker-influenced error string as the format string, escalated via a language-specific `%Z` specifier [E3](https://bugbountylibrary.com/r/h1%3A127212.md).

## Impact and severity
All three reach memory corruption; E1 and E3 escalate to arbitrary code execution via write primitives and zval specifiers [E1](https://bugbountylibrary.com/r/h1%3A106548.md)[E3](https://bugbountylibrary.com/r/h1%3A127212.md). E2 corrupts `mrb_value` objects, crashing and potentially enabling code execution [E2](https://bugbountylibrary.com/r/h1%3A192318.md). Severity is raised by the primitive available in the specifier set (`%n`, `%Z`) and by reachability from ordinary input such as a class name [E1](https://bugbountylibrary.com/r/h1%3A106548.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.
- **PHP**: php_snmp_error() Format String Vulnerability. Report: https://bugbountylibrary.com/r/h1%3A127212.md
- **PHP**: Format string vulnerability in zend_throw_or_error(). Report: https://bugbountylibrary.com/r/h1%3A106548.md. Fix: https://github.com/php/php-src/commit/b101a6bbd4f2181c360bd38e7683df4a03cba83e

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