# CVE-2021-1879: Use-After-Free in QuickTimePluginReplacement

QuickTimePluginReplacement's m_scriptObject was untracked by GC, causing a use-after-free that was exploited to forge addrof/fakeobj and achieve universal XSS in Safari.

- Payout: Real-world hack
- Severity: high
- Program: Apple WebKit
- Credited: Clement Lecigne, Google Threat Analysis Group
- Disclosed: 2021-03-26
- CVE: CVE-2021-1879
- Write-up quality: 8/10
- Original: https://github.com/googleprojectzero/0days-in-the-wild/blob/main/0day-RCAs/2021/CVE-2021-1879.md
- Library page: https://bugbountylibrary.com/r/p0%3ACVE-2021-1879
- Tags: domain:browser impact:data-leak impact:rce kind:incident platform:project-zero sev:high technique:chained technique:source-review vuln:memory-corruption vuln:xs-leak

**Target:** Safari WebKit QuickTimePluginReplacement plugin (embed element)
**Root cause:** The plugin's internal JSC::JSObject* m_scriptObject was not properly tracked by the garbage collector, so external JSValues referenced from it could be freed during GC while still reachable, yielding a use-after-free.
**Steps:**
1. Load index.html with a hidden iframe and an <embed id=worker src='data:video/mp4'> plugin element.
2. In exp.html, register a DOMNodeInserted listener on the top document's #cur div and set worker.GetURL.a = 777.
3. Call window.top.f() which sets idd.onload=g and navigates the iframe to a URL with '&foo=1'.
4. g() appends a new <a> element to #cur, firing DOMNodeInserted -> requestAnimationFrame(callback).
5. callback() runs gc() allocating many ArrayBuffers, freeing the JSValue referenced by m_scriptObject, then reads worker.GetURL.a to trigger the UAF.
**How found:** Likely manual code audit; a FIXME comment on m_scriptObject ('Why is it safe to have this pointer here? What keeps it alive during GC?') hinted at the missing GC tracking.
**Exploitation:** Attacker used the UAF to create a type confusion and forge addrof/fakeobj primitives, restoring corrupted objects before GC to keep stability. With arbitrary read/write, they set SecurityOrigin::m_universalAccess=1, created a URL object for a target domain, overwrote a websocket's Document URLs and m_url with the target's, then triggered a send so the attacker received requests (including auth cookies) as if delivered to the targeted site.
**Escalation:** Arbitrary R/W was converted into universal XSS by flipping m_universalAccess and rewriting websocket URL state, bypassing same-origin policy without needing a sandbox escape.
**Insight:** Grep for FIXME/TODO comments questioning GC lifetime of raw JSC::JSObject* pointers in browser plugin bindings; then drive GC via requestAnimationFrame plus large ArrayBuffer allocations while holding references through the plugin object.
