# Exploiting an Unbounded memcpy in Parallels Desktop: A Pwn2Own 2021 Guest-to-Host Virtualization Escape

Three chained bugs in Parallels Desktop toolgate (integer-overflow memcpy, uninit leak, stack overflow) yield guest-to-host escape and code execution on macOS host.

- Payout: $40,000
- Severity: critical
- Program: Parallels
- Credited: RET2 Systems
- Disclosed: 2022-05-19
- Write-up quality: 10/10
- Original: https://blog.ret2.io/2022/05/19/pwn2own-2021-parallels-desktop-exploit/
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-ret2-io-2022-05-19-pwn2own-2021-parallels-desktop-exploit
- Tags: domain:desktop impact:privesc impact:rce kind:bounty platform:blog sev:critical technique:chained technique:race-window technique:reverse-engineering technique:source-review vuln:info-disclosure vuln:memory-corruption

**Target:** prl_vm_app toolgate (Tg2OtgImpl, CDragAndDropBase) via guest I/O port
**Root cause:** Negative size passed to QByteArray triggers integer overflow to zero-capacity singleton, so flattening buffers memcpy's 0xffffffff bytes into QtCore data section; plus uninitialized output leak and missing null-terminator check in drag-and-drop string copy.
**Steps:**
1. Guest writes physical address of TG_PAGED_REQUEST to I/O port 0x8008 to submit toolgate opcode
2. Opcode 0x8050 (Tg2OtgImpl): set header size to 0xffffffff so QByteArray(size) overflows to empty singleton, then flatten buffers -> memcpy(QtCore data, controlled, 0xffffffff)
3. Opcode 10 type 2: resize output to 0x90 but only fill 0x20 bytes, leak 0x70 bytes uninitialized heap to guest
4. Opcode 0x8304 (CDragAndDropBase): send dnd_blob type 0x107 with 1024 UTF-16 chars lacking null terminator; QString::fromUtf16(...,-1) overreads and memcpy overflows msg.qstr, corrupting msg.worker pointer
5. Use leak to get prl_vm_app and QtCore base addresses (CFRunLoopTimerCreate fn ptr; QListData::shared_null via worker spray)
6. Use bug #3 misaligned write to corrupt pointer to saved original exception ports with non-canonical address
7. Trigger megasmash to fault, exception handler faults restoring ports, both threads suspend
8. Wait for another thread to call corrupted qt_signal_spy_callback_set; stack pivot via `lea rsp,[rcx+0x30]; ret` to ROP calling `system("open -a Calculator")`
**How found:** Started from guest tools kernel module source (prl_tg) to enumerate toolgate opcodes, then located host handler via lldb breakpoint on unlink during shared-folder file ops, traced async queue with watchpoints to find toolgate entrypoint, dumped handler vtable array to map opcode ranges, then reversed each handler's virtual method for input-controlled bugs.
**Exploitation:** Attacker needs privileged code execution in guest (kernel module) to issue toolgate requests. Chain: leak ASLR bases, corrupt exception-port pointer so handler thread faults and suspends, megasmash QtCore data section to overwrite qt_signal_spy_callback_set function pointer with controlled data, then wait for normal prl_vm_app operation to invoke it, pivoting stack into corrupted data and ROP to system() on host.
**Escalation:** Chained three independent bugs: info leak for ASLR bypass, limited stack overflow to disable exception handler, unbounded memcpy to hijack function pointer; converts an otherwise-crashing wild copy into reliable RCE.
**Insight:** When a wild memcpy seems unexploitable due to guaranteed fault, check whether the target registers a task-level Mach exception port: faulting the exception-handler thread itself (by corrupting its saved original-ports pointer) suspends it forever, letting other threads use the corrupted data before the process dies.
