# CVE-2021-21206: Chrome Use-After-Free in Animations

Chrome Paint phase triggered a synchronous JS callback via a thennable animation promise, freeing a LayoutObject mid-paint and causing a use-after-free dereferenced in ClearPropertyTreeChangedState.

- Payout: Real-world hack
- Severity: high
- Program: Google Chrome
- Credited: Brendon Tiszka, Chrome
- Disclosed: 2021-04-07
- CVE: CVE-2021-21206
- Write-up quality: 8/10
- Original: https://github.com/googleprojectzero/0days-in-the-wild/blob/main/0day-RCAs/2021/CVE-2021-21206.md
- Library page: https://bugbountylibrary.com/r/p0%3ACVE-2021-21206
- Tags: domain:browser impact:rce kind:incident platform:project-zero sev:high tech:chromium technique:chained technique:source-review vuln:memory-corruption vuln:rce

**Target:** Blink renderer paint lifecycle (LocalFrameView::RunPaintLifecyclePhase / DocumentAnimations::UpdateAnimations)
**Root cause:** UpdateAnimations during the Paint lifecycle phase can synchronously resolve an animation promise, invoking a JS getter on Object.prototype.then that removes the DOM node and frees its LayoutObject/ObjectPaintProperties while Paint still holds raw pointers to them.
**Steps:**
1. Create a div, append to body, call `div.animate([{opacity:0.1}], 1)` to get an Animation.
2. Define `Object.prototype.then` getter that calls `div.remove()`.
3. Call `animation.ready.then(...)` to queue a microtask, then `animation.pause()` to set pending_pause_.
4. On next paint, UpdateAnimations -> CommitPendingPause -> ResolvePromiseMaybeAsync resolves the promise, invoking the then getter synchronously and freeing the LayoutObject.
5. ClearPropertyTreeChangedState then dereferences the dangling ObjectPaintProperties::Transform() pointer -> UAF.
**How found:** Likely variant analysis: 'unexpected JS callback via thennable' is a known Chrome bug class causing UAF/iterator invalidation; prior bugs crbug.com/678706 and 708887 pointed at this code area.
**Exploitation:** Exploit modifies PartitionPage metadata to convert the UAF into arbitrary read/write, then leaks the address of a WASM RWX page and writes shellcode to it for code execution.
**Escalation:** UAF -> PartitionPage metadata corruption -> arbitrary read/write -> WASM RWX page leak and overwrite -> RCE in renderer.
**Insight:** Promise resolution can fire synchronous JS getters (thennable) inside lifecycle phases that assume no script runs; audit any PrePaint/Paint path that resolves promises or microtasks and frees DOM-owned LayoutObjects still referenced by raw pointers.
