# Dangling pointer in stale cache

Cached pointer or metadata outlives its freed, moved, or replaced target, then gets dereferenced

_Area: Memory corruption and native code. Seen in 8 disclosed reports; 3 confirmed bounties, median $5,000, range $1,500-$22,000; latest 2026-10-22. Updated 2026-10-23._

## How it works
A pointer, index, or metadata entry is cached somewhere that outlives the object it names. The object is freed, reallocated, moved, or replaced, but the cache is not invalidated. A later operation trusts the stale entry and dereferences it, touching memory that now belongs to something else. The broken assumption is "the cache still describes the live object." That holds only if every free, realloc, replace, and error path also clears or refreshes the cache — and that is the path that gets missed.

## Where it shows up
- Error/abort paths that return early before clearing cached input pointers (next_in/avail_in) or globals [E7](https://bugbountylibrary.com/r/h1%3A172562.md)[E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md)
- Shadow/translation structures (rmap, SPTE) updated on one branch but not when replacing an existing entry [E2](https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md)
- Native storage retained across a mode/size transition, reallocated only when the handle is null [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-v9pv-5gc8-w2m6.md)
- Growable buffers reallocated mid-recursion while a local copy of the pointer stays stale [E4](https://bugbountylibrary.com/r/h1%3A1511619.md)[E8](https://bugbountylibrary.com/r/h1%3A31408.md)
- Globals or shared metadata published before init and never undefined on abnormal termination [E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md)
- A saved start pointer not refreshed when the underlying structure is reinitialized [E6](https://bugbountylibrary.com/r/c4%3A2024-09-kakarot%3AM-01.md)
- A cached length or element pointer held across a callback that can mutate or free the container [E1](https://bugbountylibrary.com/r/blog%3Atiszka-com-blog-cve-2021-21225-html.md)

## How to review
For each cached pointer/entry, enumerate every path that frees, reallocates, replaces, or reinitializes the target, and confirm the cache is cleared or refreshed on all of them, including error and abort paths. The invariant: no cached reference may outlive the object it names. Specifically:
- Early returns must invalidate caller-owned pointers before unwinding [E7](https://bugbountylibrary.com/r/h1%3A172562.md)[E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md).
- Replacement logic must remove the old entry before installing the new one [E2](https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md).
- Reallocation must be gated on a validity check of mode/size, not just a null handle [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-v9pv-5gc8-w2m6.md).
- In recursive code, pass the growable buffer by reference, not a cached local, across frames [E4](https://bugbountylibrary.com/r/h1%3A1511619.md)[E8](https://bugbountylibrary.com/r/h1%3A31408.md).
- Re-derive saved start pointers after any reinit [E6](https://bugbountylibrary.com/r/c4%3A2024-09-kakarot%3AM-01.md).
- Re-read length/element pointers after any callback that can mutate the container [E1](https://bugbountylibrary.com/r/blog%3Atiszka-com-blog-cve-2021-21225-html.md).
Grep for cache structs, free/realloc call sites, and error labels; fuzz boundary lengths and inject errors to hit the missed path.

## Variants seen
- Stale rmap entry in shadow MMU surviving page free [E2](https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md)
- Cross-request UAF from globals not cleared on fatal OOM [E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md)
- Stale native storage after mode-changing seek [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-v9pv-5gc8-w2m6.md)
- Stale local pointer after mid-recursion realloc [E4](https://bugbountylibrary.com/r/h1%3A1511619.md)[E8](https://bugbountylibrary.com/r/h1%3A31408.md)
- Dangling next_in after failed decompress [E7](https://bugbountylibrary.com/r/h1%3A172562.md)
- Saved start pointer not updated after dict reinit [E6](https://bugbountylibrary.com/r/c4%3A2024-09-kakarot%3AM-01.md)
- Cached length/element pointer across a mutating callback [E1](https://bugbountylibrary.com/r/blog%3Atiszka-com-blog-cve-2021-21225-html.md)

## Why fixes fail
Fixing only the normal path leaves the error/abort path dangling [E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md)[E7](https://bugbountylibrary.com/r/h1%3A172562.md). When the stale target is host-owned (shadow memory), guest-page activity won't reclaim it; the real allocator must be identified by instrumenting the consumer and watching rmap/zap traces [E2](https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md). Globals left dangling persist across requests, so a per-request fix does not close the cross-request window [E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md).

## Impact and severity
Led to guest-to-host OOB read, unauthenticated RCE, and memory disclosure [E2](https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md)[E3](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md)[E7](https://bugbountylibrary.com/r/h1%3A172562.md). Severity rises when the stale reference is reclaimable by the attacker or crosses a trust boundary (guest/host, request/request).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Atiszka-com-blog-cve-2021-21225-html.md
- E2: https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-kvmescape.md
- E3: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-php2shell-php-rce.md
- E4: https://bugbountylibrary.com/r/h1%3A1511619.md
- E5: https://bugbountylibrary.com/r/ghsa%3AGHSA-v9pv-5gc8-w2m6.md
- E6: https://bugbountylibrary.com/r/c4%3A2024-09-kakarot%3AM-01.md
- E7: https://bugbountylibrary.com/r/h1%3A172562.md
- E8: https://bugbountylibrary.com/r/h1%3A31408.md
