# Use-after-free on error or shutdown

Objects freed on error or shutdown paths, then reused by teardown or the next request

_Area: Memory corruption and native code. Seen in 4 disclosed reports; latest 2026-10-21. Updated 2026-10-23._

## How it works
Code frees a request, stream or pool object on an error or shutdown path, but some later code still holds a pointer or count to it and trusts it. The broken assumption is that "freed" and "no longer referenced" happen together. They don't: a fallible allocation, a failed write, or a shutdown race can free the object while a caller, a library, or the next request still believes it is live. The stale pointer or count is then read or reused.

## Where it shows up
- State published or a count incremented *before* a fallible allocation completes; a fatal or OOM between publish and commit skips cleanup [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md).
- An internal error path that frees a request wrapper but returns success to its caller [E2](https://bugbountylibrary.com/r/h1%3A988103.md).
- A library (e.g. nghttp2) retaining a reference to a host-owned stream across connection shutdown [E3](https://bugbountylibrary.com/r/h1%3A680415.md).
- Per-request pool destroyed while multiplexed or scoreboard code still reads request data [E4](https://bugbountylibrary.com/r/h1%3A527042.md).
- Teardown/shutdown ordering where free happens before all references are dropped.

## How to review
For every free on an error or shutdown path, find every holder of that object and confirm each is invalidated before or at the free. The invariant: an object is freed only after the last reference to it is dropped, and any function that frees a caller-supplied wrapper must report failure to that caller. Check that counts/pointers are committed only after the allocation they depend on succeeds, and that fatal/OOM paths run cleanup or leave state the next request cannot trust [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md). Trace the return contract of async write APIs against every early-return that frees the request [E2](https://bugbountylibrary.com/r/h1%3A988103.md).

## Variants seen
- OOM-fatal dangling interpreter state reused by the next request [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md).
- Failed write frees wrapper, returns success, caller reuses pointer [E2](https://bugbountylibrary.com/r/h1%3A988103.md).
- Shutdown race: library references host-freed stream [E3](https://bugbountylibrary.com/r/h1%3A680415.md).
- Pool destroyed, stale request data read in string formatting [E4](https://bugbountylibrary.com/r/h1%3A527042.md).

## Why fixes fail
Evidence is thin, but E1 shows the class recurs: a 2004 memory_limit bug had siblings found years later, because each fix patched one allocation site while the publish-before-commit pattern remained elsewhere.

## Impact and severity
Ranged from heap use-after-free read in worker threads [E3](https://bugbountylibrary.com/r/h1%3A680415.md)[E4](https://bugbountylibrary.com/r/h1%3A527042.md) to pre-auth RCE [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md) and memory corruption in a core runtime [E2](https://bugbountylibrary.com/r/h1%3A988103.md). Severity rises when the stale state is reachable pre-auth or reused across requests, and when aggressive pool freeing (MaxMemFree 1) or ASAN exposes the lifetime gap [E3](https://bugbountylibrary.com/r/h1%3A680415.md).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md
- E2: https://bugbountylibrary.com/r/h1%3A988103.md
- E3: https://bugbountylibrary.com/r/h1%3A680415.md
- E4: https://bugbountylibrary.com/r/h1%3A527042.md
