# Verifier bounds width mismatch

Bounds or width tracking that disagrees with the width the access or operation actually uses

_Area: Memory corruption and native code. Seen in 4 disclosed reports; 2 confirmed bounties: $750 and $31,337; latest 2026-10-27. Updated 2026-11-08._

## How it works
A checker (verifier, JIT, emulator, parser) computes a bound or width using one representation, while the code it guards uses another. The two disagree, so the check passes on a value the access then treats differently. Common forms: unsigned subtraction in a size check underflows; a 32-bit subregister is tracked with 64-bit registers; a count is stored narrower than it is validated; a shift count is not masked to the operand width. The invariant that must hold is: the width and range the checker reasons about are exactly the width and range the consumer uses.

## Where it shows up
- Bounds checks of the form `len > LIMIT - size` where `size` is unsigned and can approach or exceed `LIMIT` [E1](https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md)
- Subregister helpers in a verifier/JIT that use full-width registers where sibling helpers use the narrow width [E3](https://bugbountylibrary.com/r/h1%3A1010340.md)
- Counts stored in `int16`/narrow types but validated only as `int32` totals [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md)
- Format spec bounds (tableLog caps) enforced in a reference implementation but not at the call site [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md)
- Emulated opcodes that shift by a full register instead of masking to the operand width, while sibling opcodes mask [E4](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AM-07.md)
- State reused across blocks/iterations that carries a wrapped or stale value past a guard [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md)

## How to review
For each check, identify the exact type and width of every operand and of the value the guarded access uses. Confirm the check cannot underflow (unsigned subtraction, wraparound) and that narrowing conversions happen before, not after, validation. For verifier/JIT code, diff each subregister or opcode helper against its siblings and against the ISA/spec: any helper using a different width than its peers is suspect [E3](https://bugbountylibrary.com/r/h1%3A1010340.md)[E4](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AM-07.md). For parsers, confirm the format's spec limits are enforced at the call site, not only in a reference port [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md). Confirm shared scratch/state cannot carry a value past a guard on the next block [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md).

## How finders spotted it
- Fuzzing with syzkaller surfaced a KASAN slab-out-of-bounds; reading the source revealed the unsigned underflow in the bound check [E1](https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md)
- Noticed the C reference caps FSE/Huffman tableLog but the Go port enforces neither; traced narrow count storage and cross-block scratch reuse [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md)
- Compared `scalar32_min_max_or()` against other 32-bit subreg helpers and saw it used 64-bit registers [E3](https://bugbountylibrary.com/r/h1%3A1010340.md)
- Code-read against the MIPS ISA spec: other shifts mask with 0x1F, SRAV does not [E4](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AM-07.md)

## Variants seen
- Unsigned subtraction underflow in a kernel size check [E1](https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md)
- 64-bit vs 32-bit subregister bounds tracking in an eBPF verifier [E3](https://bugbountylibrary.com/r/h1%3A1010340.md)
- Narrow integer storage with wide-only validation in a Go decoder [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md)
- Missing shift-count masking in an emulated VM opcode [E4](https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AM-07.md)

## Impact and severity
E1 gave unprivileged heap overflow, root, and container escape ($31337). E3 gave OOB map access, arbitrary kernel read/write, and LPE. E2 caused a panic (DoS) instead of a decode error. E4 flipped true dispute-game root claims to false, a correctness/integrity failure. Impact rises when the mismatch yields an out-of-bounds primitive that chains to privilege escalation or sandbox escape [E1](https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md)[E3](https://bugbountylibrary.com/r/h1%3A1010340.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.
- **github.com/klauspost/compress/zstd (go)** <= 1.20.1, fixed in 1.20.2: compress zstd: Panic on two-block frames (CVE-2026-116591). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md. Fix: https://github.com/klauspost/compress/commit/7f5d8528720c895a6106f201719f87eb030a565d
- **Linux kernel** (legacy_parse_param) 5.1: CVE-2022-0185 - Winning a $31337 Bounty after Pwning Ubuntu and Escaping Google's KCTF Containers (CVE-2022-0185). Report: https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md
- **Linux kernel**: [CVE-2020-27194] Linux kernel: eBPF verifier bug in `or` binary operation tracking function leads to LPE (CVE-2020-27194). Report: https://bugbountylibrary.com/r/h1%3A1010340.md. Fix: https://github.com/torvalds/linux/commit/3f50f132d8400e129fc9eb68b5020167ef80a244

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Awillsroot-io-2022-01-cve-2022-0185-html.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-fpmh-frq8-7rj8.md
- E3: https://bugbountylibrary.com/r/h1%3A1010340.md
- E4: https://bugbountylibrary.com/r/c4%3A2024-07-optimism%3AM-07.md
