# But You Told Me You Were Safe: Attacking The Mozilla Firefox Renderer (Part 1)

Prototype pollution in SpiderMonkey's top-level await leaks an internal Module object, enabling UnsafeSetReservedSlot OOB write and renderer RCE.

- Payout: unconfirmed
- Severity: high
- Program: Mozilla
- Credited: Hossein Lotfi, Manfred Paul
- Disclosed: 2022-08-23
- CVE: CVE-2022-1802
- Write-up quality: 9/10
- Original: https://www.zerodayinitiative.com/blog/2022/8/17/but-you-told-me-you-were-safe-attacking-the-mozilla-firefox-renderer-part-1
- Library page: https://bugbountylibrary.com/r/blog%3Azerodayinitiative-com-blog-2022-8-17-but-you-told-me-you-were-safe-attacking-the-mozilla-firefox-renderer-part-1
- Tags: domain:browser impact:rce kind:bounty platform:blog sev:high tech:firefox technique:chained technique:source-review vuln:memory-corruption vuln:prototype-pollution vuln:rce

**Target:** SpiderMonkey JS engine builtin Module.js (GatherAsyncParentCompletions)
**Root cause:** GatherAsyncParentCompletions uses std_Array_push on a default [] argument, so a setter on Array.prototype[0] intercepts the push and leaks the internal Module object; its gatherAsyncParentCompletions then calls UnsafeSetReservedSlot without bounds checks.
**Steps:**
1. Define a setter on Array.prototype[0] that captures the assigned value (the Module object).
2. Import an async module (with top-level await) from another file so GatherAsyncParentCompletions runs and triggers the setter.
3. Call mod.gatherAsyncParentCompletions({asyncParentModules:[obj]}) to invoke UnsafeSetReservedSlot writing obj.pendingAsyncDependencies-1 to slot 20.
4. Create an array, add named props including pendingAsyncDependencies to allocate slots_, and numbered elements to allocate elements_.
5. Align nursery bump allocations so slots_[4] overlaps elements_ capacity, overwrite capacity for OOB write.
6. Corrupt a typed array data pointer for arbitrary read/write; pivot to tenured heap via ArrayBuffer corruption.
7. Embed first-stage shellcode in WebAssembly f64 constants, leak PEB pointer, locate kernel32/VirtualProtect, mark ArrayBuffer executable, run stage 2.
**How found:** Noticed builtin Module.js runs in same JS context as user code and uses std_Array_push on a default [] argument, which follows prototype chain semantics; hypothesized a setter on Array.prototype[0] would intercept and leak the internal Module value.
**Exploitation:** Full chain at Pwn2Own: leak Module via prototype setter, use UnsafeSetReservedSlot for OOB write in nursery heap, build arbitrary R/W via typed array pointer overwrite, escalate to tenured heap, then W^X-bypassing shellcode embedded in WASM float constants to call VirtualProtect and execute stage-2 shellcode in renderer.
**Escalation:** Nursery-heap corruption used only as first stage to corrupt tenured-heap ArrayBuffers; WASM-embedded shellcode bypasses W^X JIT to gain renderer code execution, setting up sandbox escape in part 2.
**Insight:** Audit JS-implemented builtins that use ordinary array operations (push/assignment) on default-constructed arrays: prototype setters can intercept and leak internal engine objects whose prototypes expose unsafe methods like UnsafeSetReservedSlot.
