# Path normalization buffer corruption

Normalization helpers doing memmove, truncation or pointer math on attacker-controlled paths and filenames

_Area: Memory corruption and native code. Seen in 4 disclosed reports; 2 confirmed bounties: $1,500 and $2,500; latest 2026-03-31. Updated 2026-11-08._

## How it works
Path and filename normalization code assumes things about attacker-influenced strings that the attacker can break: that one delimiter always precedes another, that a buffer is large enough for a platform path limit, that a "regular file" check rejects symlinks, or that a path variable is non-empty. When the assumption fails, the arithmetic (memmove length, pointer offset, truncation point) goes wrong and writes or reads outside the intended bounds [E1](https://bugbountylibrary.com/r/h1%3A722327.md)[E2](https://bugbountylibrary.com/r/h1%3A544096.md)[E4](https://bugbountylibrary.com/r/h1%3A73491.md), or the guard silently passes [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md).

## Where it shows up
- Normalization helpers that search for two delimiters (last `/` vs last `\`) and compute a length from one offset while writing at the other [E4](https://bugbountylibrary.com/r/h1%3A73491.md)
- Fixed-size path buffers close to but under a platform limit (256 vs MAX_PATH 260), where truncation can rewrite the extension [E2](https://bugbountylibrary.com/r/h1%3A544096.md)
- Pointer arithmetic on path variables that may be empty or absent (PATH_INFO) [E1](https://bugbountylibrary.com/r/h1%3A722327.md)
- Filesystem guards using `is_regular_file`, `stat`, or `status(p)` that follow symlinks [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md)
- Any code passing a normalized path to an executor (ShellExecute, open, exec) [E2](https://bugbountylibrary.com/r/h1%3A544096.md)

## How to review
For each normalization routine, state the invariant and test it against adversarial input:
- Delimiter ordering: does the write offset always stay within the string regardless of which delimiter comes last? [E4](https://bugbountylibrary.com/r/h1%3A73491.md)
- Buffer vs platform limit: is the buffer at least as large as the maximum path the OS will accept, and does truncation preserve the extension? [E2](https://bugbountylibrary.com/r/h1%3A544096.md)
- Emptiness: is every pointer derived from a path variable bounds-checked when that variable is empty? [E1](https://bugbountylibrary.com/r/h1%3A722327.md)
- Symlink safety: is the file opened with `O_NOFOLLOW` and `fstat`ed on the fd, not `stat`ed by path? [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md)
- Sink: does the normalized result reach an executor, and can the attacker control the extension or target? [E2](https://bugbountylibrary.com/r/h1%3A544096.md)

## How finders spotted it
- Read `fpm_main.c`, saw no bounds check when PATH_INFO is empty, then asked how a front-end proxy regex could be tricked into producing that value from ordinary HTTP input (encoded newline) [E1](https://bugbountylibrary.com/r/h1%3A722327.md)
- Noticed a 256-byte buffer against the 260 MAX_PATH limit, hypothesized truncation could swap the extension, and picked an extension the download filter allows but the OS executes [E2](https://bugbountylibrary.com/r/h1%3A544096.md)
- Code review of the symlink check: recognized `is_regular_file` calls `status(p)` and follows symlinks, making the guard ineffective [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md)
- Reviewed the filename normalization patch, saw the memmove length computed from the backslash offset regardless of the forward-slash position, then crafted a reversed-delimiter filename to test the assumption [E4](https://bugbountylibrary.com/r/h1%3A73491.md)

## Variants seen
- Pointer underflow from an empty path variable, giving a one-byte OOB write [E1](https://bugbountylibrary.com/r/h1%3A722327.md)
- Truncation at a fixed buffer size that silently changes the file extension [E2](https://bugbountylibrary.com/r/h1%3A544096.md)
- Symlink-following guard that passes any regular file target [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md)
- memmove length computed from the wrong delimiter offset, overflowing the heap [E4](https://bugbountylibrary.com/r/h1%3A73491.md)

## Impact and severity
Ranged from arbitrary host file read [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md) to heap overflow [E4](https://bugbountylibrary.com/r/h1%3A73491.md) and, when amplified, remote code execution: a single-byte OOB write repeated across requests became full code execution [E1](https://bugbountylibrary.com/r/h1%3A722327.md), and a truncated path handed to ShellExecute executed attacker-chosen code [E2](https://bugbountylibrary.com/r/h1%3A544096.md). Impact rose when the bug was combined with a front-end proxy weakness [E1](https://bugbountylibrary.com/r/h1%3A722327.md) or an OS file-association behavior [E2](https://bugbountylibrary.com/r/h1%3A544096.md), and when the primitive could be repeated to scale [E1](https://bugbountylibrary.com/r/h1%3A722327.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.
- **onnx (pip)** <= 1.20.0, fixed in 1.21.0: onnx Vulnerable to Path Traversal via Symlink (CVE-2026-27489). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md. Fix: https://github.com/onnx/onnx/commit/4755f8053928dce18a61db8fec71b69c74f786cb
- **PHP**: CVE-2019-11043: a buffer underflow in fpm_main.c can lead to RCE in php-fpm (CVE-2019-11043). Report: https://bugbountylibrary.com/r/h1%3A722327.md

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A722327.md
- E2: https://bugbountylibrary.com/r/h1%3A544096.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-3r9x-f23j-gc73.md
- E4: https://bugbountylibrary.com/r/h1%3A73491.md
