# Unbounded aggregate cost in parsing loops

Loops appending or allocating per item with no global cap; per-item limits multiplied across items

_Area: Memory corruption and native code. Seen in 4 disclosed reports; latest 2025-06-12. Updated 2026-11-08._

## How it works
A parsing, splitting, decompression or error-formatting loop bounds each item but never bounds the aggregate. The broken assumption is "each item is bounded, therefore the total is bounded" — false when the attacker controls the item count or can make every item take an exempt path. The accumulator (fixed buffer, decompression chain, copied array, formatted string) grows until it overflows, exhausts memory, or burns unbounded CPU. The unifying property is unbounded aggregate cost, not literal buffer overflow: some variants overflow a fixed buffer [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md), others amplify CPU or memory without overflowing anything [E1](https://bugbountylibrary.com/r/h1%3A2489843.md)[E3](https://bugbountylibrary.com/r/h1%3A1886139.md)[E4](https://bugbountylibrary.com/r/h1%3A2559404.md).

## Where it shows up
- Line-splitting/formatting loops with special-case `continue` branches that skip the flush or break step [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md)
- Fixed-size byte buffers (e.g. 80 bytes) filled by a loop whose only exit is a delimiter or width check [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md)
- Resource caps enforced per-header, per-field or per-item rather than per-request [E3](https://bugbountylibrary.com/r/h1%3A1886139.md)
- Opcode/getter handlers calling defensive-copy getters (`Arrays.copyOf`) inside hot loops [E1](https://bugbountylibrary.com/r/h1%3A2489843.md)
- Error/exception paths that serialize the full attacker-controlled input (hex encoding, `toString`) [E4](https://bugbountylibrary.com/r/h1%3A2559404.md)

## How to review
Find every loop that appends to a buffer or allocates per iteration. Ask: what guarantees termination, and what bounds the total? The invariant: total output/allocation is bounded by a constant or by a limit checked against the *aggregate*, not each item. For formatters, verify the flush/break path is reachable for inputs made entirely of exempt characters [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md). For caps, verify the limit is global across repeated headers/fields [E3](https://bugbountylibrary.com/r/h1%3A1886139.md). For hot loops, confirm getters return lengths or views, not full copies [E1](https://bugbountylibrary.com/r/h1%3A2489843.md). For error paths, confirm message size does not scale with input size [E4](https://bugbountylibrary.com/r/h1%3A2559404.md).

## How finders spotted it
- Read `VM.doCODESIZE`, saw `getCode()` returns a copied array, hypothesized repeated calls amplify cost, then used a genetic algorithm to build a worst-case bytecode reproducer [E1](https://bugbountylibrary.com/r/h1%3A2489843.md)
- Read the line-splitting code, noticed continuation branches `continue` without flushing, hypothesized a string of only such characters never breaks, tested with 21 repeated U+1F3FE codepoints [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md)
- Noticed a prior CVE's decompression cap was per-header, reasoned many headers each under the cap bypass it [E3](https://bugbountylibrary.com/r/h1%3A1886139.md)
- Experimented with input sizes to find the slowest payload, traced slowdown to hex-string construction by patching it out and re-measuring [E4](https://bugbountylibrary.com/r/h1%3A2559404.md)

## Variants seen
- Never-terminating split loop overflowing a fixed buffer [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md)
- Per-item cap bypassed by multiplying items across headers [E3](https://bugbountylibrary.com/r/h1%3A1886139.md)
- Defensive-copy getter in a hot opcode loop causing CPU amplification [E1](https://bugbountylibrary.com/r/h1%3A2489843.md)
- Error-path serialization scaling with attacker input size [E4](https://bugbountylibrary.com/r/h1%3A2559404.md)

## Why fixes fail
A prior fix capped the decompression chain per-header only; spreading encodings across many headers bypassed it, leaving the same OOM reachable [E3](https://bugbountylibrary.com/r/h1%3A1886139.md).

## Impact and severity
Denial of service: node stalls of ~1.5 minutes per execution [E1](https://bugbountylibrary.com/r/h1%3A2489843.md) and ~23 seconds per block [E4](https://bugbountylibrary.com/r/h1%3A2559404.md); OOM DoS in libcurl [E3](https://bugbountylibrary.com/r/h1%3A1886139.md); `tokenURI` reverting for a valid minted NFT [E2](https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md). Impact rises with how cheaply the attacker triggers it and how far one request reaches — a single crafted contract or response affecting all nodes or clients.

## 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.
- **curl**: HTTP multi-header compression denial of service (CVE-2023-23916). Report: https://bugbountylibrary.com/r/h1%3A1886139.md

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A2489843.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-08.md
- E3: https://bugbountylibrary.com/r/h1%3A1886139.md
- E4: https://bugbountylibrary.com/r/h1%3A2559404.md
