# Unbounded returndata and memory growth

Caller-controlled bytes copied or cached before verification, with no size cap or expiry

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2026-09-10. Updated 2026-10-31._

## How it works
A component accepts bytes it does not yet trust and copies or stores them before it has verified them, with no cap on size and often no expiry. The assumption that breaks is "we only keep data we have validated." In practice the copy happens first: a low-level call loads returndata into memory unconditionally, a replay cache inserts an entry before the MAC check, or a tracer runs caller code with no memory ceiling. Whoever controls the bytes controls how much memory the victim spends, and can repeat it.

## Where it shows up
- Low-level `call`/`staticCall` whose returndata is copied into memory without a length check, especially inside loops over many messages [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-09.md)
- Replay or negative caches populated before authentication, created with no expiration horizon [E1](https://bugbountylibrary.com/r/h1%3A3709703.md)
- Failure paths that skip the counter or rotation that would otherwise bound the cache [E1](https://bugbountylibrary.com/r/h1%3A3709703.md)
- RPC endpoints accepting caller-supplied code (JS tracers) executed server-side with loop/recursion limits but no memory limit [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md)
- Public, unauthenticated exposure of any of the above [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md)

## How to review
Trace every path where caller-controlled bytes enter memory or a persistent store, and ask: is size bounded, and does verification happen before the copy? The invariant: no attacker-influenced buffer is allocated or cached until it has passed authentication, and every cache has a size or time bound. For low-level calls, check whether returndata is actually needed; if not, avoid copying it, and cap it if so. For caches, confirm failed-auth entries still advance rotation or expire [E1](https://bugbountylibrary.com/r/h1%3A3709703.md). For RPC, confirm memory limits exist, not just iteration caps [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md).

## Variants seen
- Replay cache grows on unauthenticated input before MAC verification [E1](https://bugbountylibrary.com/r/h1%3A3709703.md)
- Gas bomb via huge returndata from a cross-message recipient blocking checkpoint submission [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-09.md)
- Server-side JS tracer with no memory limit crashing a node [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md)

## Impact and severity
Led to unbounded memory growth and node crash [E1](https://bugbountylibrary.com/r/h1%3A3709703.md)[E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md), and to blocked checkpoint submission, a liveness failure for the gateway [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-09.md). Severity is high where the path is unauthenticated and repeatable; worse when the cache never expires [E1](https://bugbountylibrary.com/r/h1%3A3709703.md) or the endpoint is public [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md).

## Why fixes fail
A fix that only limits revert behavior or iteration count misses that returndata is still copied [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-09.md) and that memory, not loop count, is the resource exhausted [E3](https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A3709703.md
- E2: https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-09.md
- E3: https://bugbountylibrary.com/r/cantina%3Acitrea_jul2025%3A3.2.8.md
