# Use-after-free via callback reentrancy

Raw pointer or index held across a callback that can free or reallocate the same object

_Area: Memory corruption and native code. Seen in 12 disclosed reports; 6 confirmed bounties, median $2,200, range $1,000-$5,000; latest 2026-08-05. Updated 2026-10-23._

## How it works
Code takes a raw pointer, index, or reference to a state object, then calls something that can run attacker-influenced code synchronously (a JS callback, `valueOf`/`then` getter, a nested parser, an error-path cleanup, a destructor). That callback frees or reallocates the object. Control returns and the original code keeps using the now-dangling pointer or stale index. The broken assumption: "no code runs between taking this reference and using it," or "the reference keeps the object alive." Neither holds when the reference is raw and the callback can drop the last owner.

## Where it shows up
- Functions taking a raw `T*` (not a refcounted handle) that can re-enter script: event callbacks, `valueOf`/`toString`, thennable promise resolution [E2](https://bugbountylibrary.com/r/p0%3ACVE-2022-22620.md)[E3](https://bugbountylibrary.com/r/p0%3ACVE-2021-21206.md)[E7](https://bugbountylibrary.com/r/p0%3ACVE-2021-26411.md)
- Index or pointer computed, then a user-controlled conversion invoked, then the index used [E7](https://bugbountylibrary.com/r/p0%3ACVE-2021-26411.md)
- Lifecycle phases (Paint/PrePaint) that assume no script runs but resolve promises or microtasks [E3](https://bugbountylibrary.com/r/p0%3ACVE-2021-21206.md)
- Recursive parser invocations sharing one reference table; an inner resize frees buckets the outer still holds [E4](https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-the-wordpress-chain-massacre.md)
- Error paths that free a partially-built value still reachable via back-references (`R:`/`r:`) [E5](https://bugbountylibrary.com/r/h1%3A159948.md)
- Error paths that free a wrapper but leave it linked into a caller-owned structure [E6](https://bugbountylibrary.com/r/h1%3A1906897.md)
- Destructor cleanup gated on a mutable field that can change between registration and destruction [E1](https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-patch-diff-to-system.md)
- Adjacent (not reentrancy): paint-tree mutation in flight leaves a dangling layer read later [E8](https://bugbountylibrary.com/r/blog%3Ablog-redteam-pl-2020-04-google-chrome-display-locking-fuzzing-html.md)

## How to review
For each raw pointer/index, ask: can anything between acquisition and use run script, fire a callback, recurse, or hit an error path that frees the object? The invariant: either the reference is owning (refcount incremented for the whole span) or no reentrant code can run in that span. Checklist:
- Grep for commits that downgrade `RefPtr<T>` to raw `T*` in lifetime-sensitive functions [E2](https://bugbountylibrary.com/r/p0%3ACVE-2022-22620.md).
- Audit PrePaint/Paint paths that resolve promises or microtasks [E3](https://bugbountylibrary.com/r/p0%3ACVE-2021-21206.md).
- In deserializers, check error paths where a partially-built value is freed but still reachable via back-references [E5](https://bugbountylibrary.com/r/h1%3A159948.md).
- A freed wrapper must be detached from any caller structure before return [E6](https://bugbountylibrary.com/r/h1%3A1906897.md).
- Destructor cleanup must not depend on a field an attacker can flip before release [E1](https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-patch-diff-to-system.md).
- For adjacent cases, check paint-tree mutation ordering, not just callbacks [E8](https://bugbountylibrary.com/r/blog%3Ablog-redteam-pl-2020-04-google-chrome-display-locking-fuzzing-html.md).

## Variants seen
- Raw pointer without refcount, callback drops last reference [E2](https://bugbountylibrary.com/r/p0%3ACVE-2022-22620.md)
- Synchronous promise getter frees layout object mid-paint [E3](https://bugbountylibrary.com/r/p0%3ACVE-2021-21206.md)
- Shared reference table across recursive parser invocations [E4](https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-the-wordpress-chain-massacre.md)
- Error path frees zval still referenced by nested object [E5](https://bugbountylibrary.com/r/h1%3A159948.md)
- Error path frees wrapper still in caller's chain [E6](https://bugbountylibrary.com/r/h1%3A1906897.md)
- Stale index after reentrant `valueOf` causes double free [E7](https://bugbountylibrary.com/r/p0%3ACVE-2021-26411.md)
- Destructor gate flipped by property change, leaving dangling pointer [E1](https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-patch-diff-to-system.md)
- Adjacent: in-flight invalidation leaves dangling PaintLayer [E8](https://bugbountylibrary.com/r/blog%3Ablog-redteam-pl-2020-04-google-chrome-display-locking-fuzzing-html.md)

## Impact and severity
Led to type confusion and arbitrary read/write in IE [E7](https://bugbountylibrary.com/r/p0%3ACVE-2021-26411.md), and SYSTEM code execution in Windows DWM [E1](https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-patch-diff-to-system.md). Severity rises when the freed object is reachable from a sandboxed context (browser renderer) or holds function pointers used for virtual calls. Recursive-parser and error-path variants yield read and fake-object primitives usable for ROP [E4](https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-the-wordpress-chain-massacre.md)[E5](https://bugbountylibrary.com/r/h1%3A159948.md).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-patch-diff-to-system.md
- E2: https://bugbountylibrary.com/r/p0%3ACVE-2022-22620.md
- E3: https://bugbountylibrary.com/r/p0%3ACVE-2021-21206.md
- E4: https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-the-wordpress-chain-massacre.md
- E5: https://bugbountylibrary.com/r/h1%3A159948.md
- E6: https://bugbountylibrary.com/r/h1%3A1906897.md
- E7: https://bugbountylibrary.com/r/p0%3ACVE-2021-26411.md
- E8: https://bugbountylibrary.com/r/blog%3Ablog-redteam-pl-2020-04-google-chrome-display-locking-fuzzing-html.md
