# Use-after-free on error cleanup path

Error paths that free an object still reachable via back-references, chains, or external handles

_Area: Memory corruption and native code. Seen in 3 disclosed reports; 2 confirmed bounties: $2,400 and $5,000; latest 2023-03-18. Updated 2026-10-24._

## How it works
A function builds or wraps an object, then hits an error and frees it. The assumption that breaks: freeing the object also removes every reference to it. It does not. A back-reference, a caller-owned chain, or a second handle still points at the freed memory. Later code follows that stale reference and reads or calls through freed memory. The bug is not the free itself but the free happening before the reference is detached or invalidated.

## Where it shows up
- Deserializers with back-references (R:/r: style) and nested custom callbacks that can fail mid-parse [E1](https://bugbountylibrary.com/r/h1%3A159948.md)
- Explicit release()/free() APIs on objects that other live references can still reach [E2](https://bugbountylibrary.com/r/h1%3A151043.md)
- Helper functions that create a wrapper, prepend it to a caller-owned structure, then free it on error and return NULL [E3](https://bugbountylibrary.com/r/h1%3A1906897.md)
- Cleanup code that frees a partially-built value but leaves it stored in a container or slot
- Error branches that free without nulling the pointer or unlinking from the chain

## How to review
For every error/teardown path, ask: after this free, can any live reference still reach the object? The invariant that must hold: an object is freed only when no chain, container, back-reference, or external handle points to it, or all such references are cleared before the free. Check that a helper freeing its own wrapper also detaches it from the caller's structure [E3](https://bugbountylibrary.com/r/h1%3A1906897.md). Check that release()/free() invalidates all aliases [E2](https://bugbountylibrary.com/r/h1%3A151043.md). Check that a failed parse does not leave a freed value reachable via back-references [E1](https://bugbountylibrary.com/r/h1%3A159948.md).

## Variants seen
- Nested custom-unserialize callback fails, freed zval still reachable via R:/r: reference [E1](https://bugbountylibrary.com/r/h1%3A159948.md)
- Public release() frees inner memory but leaves AS3 references dangling for virtual calls [E2](https://bugbountylibrary.com/r/h1%3A151043.md)
- BIO_new_NDEF frees its filter BIO on error but leaves caller's BIO chain pointing at it [E3](https://bugbountylibrary.com/r/h1%3A1906897.md)

## Impact and severity
Led to RCE in PHP unserialize [E1](https://bugbountylibrary.com/r/h1%3A159948.md), UAF with virtual-call primitive in Flash [E2](https://bugbountylibrary.com/r/h1%3A151043.md), and UAF in OpenSSL CMS/SMIME streaming [E3](https://bugbountylibrary.com/r/h1%3A1906897.md). Severity is high because the freed object is attacker-influenced and reachable through normal API use; nested callbacks and caller-owned chains widen the window and make the dangling reference reliable.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A159948.md
- E2: https://bugbountylibrary.com/r/h1%3A151043.md
- E3: https://bugbountylibrary.com/r/h1%3A1906897.md
