# OOM mid-parse dangling state

State published before a fallible allocation; a fatal skips cleanup and the next request trusts the stale pointer or count

_Area: Memory corruption and native code. Seen in 3 disclosed reports; latest 2026-10-27. Updated 2026-11-02._

## How it works
Parsers publish state before the work that can fail. A counter is incremented, a pointer stored in a global, or a request structure linked in, and only then does the code allocate memory or finish initializing. When the allocation fails, the engine raises a fatal (e.g. PHP `memory_limit`) and unwinds without running the normal cleanup. The published state stays behind: a count that no longer matches reality, a pointer to a half-built object, a flag still set. Teardown, or the next request handled by the same worker, treats that leftover as valid and dereferences or reuses it. The broken assumption is "a fatal means nothing partial survives" — in long-lived workers (mod_php, PHP-FPM) the process outlives the request [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md)[E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).

## Where it shows up
- Code that increments a visible count or stores a pointer into a global/request struct *before* a fallible allocation [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md).
- Security-relevant flags set and reset inside one call chain, where a callee can fail (headers already sent, allocation failure) and skip the reset [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md).
- Request-scoped state (serializer bookkeeping, session upload-progress, `$_POST` tables) freed or cleaned on mid-request death [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).
- Parsers that allocate per-input before finishing initialization of the structure they expose [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).
- Session upload-progress callbacks where an attacker-influenced id is copied into a global reused across requests [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md).

## How to review
Find every publish-then-allocate sequence: state made visible (count++, pointer store, list insert) followed by work that can fail. The invariant that must hold: **if any allocation between publish and commit fails, the published state is either rolled back or never trusted by teardown or the next request.** Check that fatal/shutdown paths run the same cleanup as success paths, that counts are derived rather than incremented early, and that globals reused across requests are reset on every exit path, not just the happy one. For flags set/reset in one chain, enumerate each callee's failure modes and confirm the flag cannot survive the request [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md).

## How finders spotted it
- Hypothesized siblings of an old `memory_limit` bug, enumerated OOM producers and interruptible interpreter points, then source-read ZendMM/fatal/shutdown ordering to generalize the pattern [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md).
- Read the session upload-progress `switch(event)` logic, noticed `PS(id)` is attacker-controlled via `progress->sid` and that `PS(send_cookie)=1` persists only if `php_session_send_cookie` fails (E2-specific), then enumerated that callee's failure modes (`SG(headers_sent)`, OOM in `php_url_encode`) to widen the window [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md).
- Built on public research from Calif.io and Wiz, then reproduced the trigger in lab across stock Docker images and mapped which config factors gate exploitability [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).

## Variants seen
- Dangling HashTable / unserializer bookkeeping from OOM during startup and decoding [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md).
- Dangling session `PS(id)` pointer reused when building `Set-Cookie` (mod_php only, not FPM) [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md).
- Partially initialized `$_POST` treated as valid by request cleanup [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).
- Request-scoped serializer state freed mid-request then used (UAF) [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).

## Impact and severity
Pre-auth RCE in PHP under mod_php and FPM [E1](https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md)[E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md). Impact rose with: a low-severity `phpinfo` page leaking version, SAPI, `display_errors` and `memory_limit`, removing offset guesswork [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md); `display_errors` leaks or prefork layout reuse easing ASLR (a config factor, not a demonstrated bypass) [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md); and reachability from the engine itself, independent of application features [E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md). Exploitability is conditional on SAPI, opcache, and memory settings, so mass exploitation is hard but per-target tuning works [E2](https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md)[E3](https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Acalif-io-php2shell.md
- E2: https://bugbountylibrary.com/r/blog%3Apraetorian-com-blog-php2shell.md
- E3: https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-responder-guidance-for-php2shell.md
