# CVE-2022-22620: Use-after-free in Safari

Use-after-free in WebKit's loadInSameDocument: onblur callback calls replaceState, freeing the stateObject still used by statePopped.

- Payout: Real-world hack
- Severity: high
- Program: Apple Safari/WebKit
- Credited: Maddie Stone
- Disclosed: 2022-02-10
- CVE: CVE-2022-22620
- Write-up quality: 8/10
- Original: https://github.com/googleprojectzero/0days-in-the-wild/blob/main/0day-RCAs/2022/CVE-2022-22620.md
- Library page: https://bugbountylibrary.com/r/p0%3ACVE-2022-22620
- Tags: domain:browser impact:rce kind:incident platform:project-zero sev:high technique:incomplete-fix technique:regression technique:source-review vuln:memory-corruption

**Target:** WebKit History API / FrameLoader::loadInSameDocument
**Root cause:** loadInSameDocument takes stateObject as a raw SerializedScriptValue* without incrementing refcount; only HistoryItem holds a reference. A JS callback (onblur) during the call can replaceState, dropping the last reference, then loadInSameDocument uses the freed object in statePopped.
**Steps:**
1. Create input and anchor with id='foo' appended to body.
2. history.pushState('state1','',location+'#foo') so back() triggers loadInSameDocument and focuses #foo.
3. history.pushState('state2','').
4. Focus input; set input.onblur = () => history.replaceState('state3','').
5. Call history.back() after timeout: loadInSameDocument shifts focus to #foo, firing blur, whose handler replaceState frees stateObject.
6. loadInSameDocument then calls statePopped on the freed object -> UAF.
**How found:** Author speculates it could be found by watching commits (2013 fix reverted in 2016), code auditing, or fuzzing; fuzzing less likely since it requires navigation support many fuzzers exclude. Root cause identified by reading loadInSameDocument's raw-pointer argument and the onblur callback path.
**Exploitation:** No exploit sample available; the bug was exploited in the wild as a zero-day (per Project Zero), but the write-up does not describe the weaponized flow.
**Insight:** Grep for commits that downgrade RefPtr<T> to raw T* in lifetime-sensitive functions; any function taking a raw pointer that can re-enter JS (event callbacks) is a UAF candidate. Also mine reverted security fixes ('zombie' bugs) via git history.
