# CVE-2026-58608 — Windows Print Spooler `spoolsv.exe` Printer-Handle Close vs Remote-Notification Refresh Race → Use-After-Free (RCE)

---

## Summary

| | |
|---|---|
| **Product** | Windows — `spoolsv.exe` (Print Spooler Components / remote notification router) |
| **CVE ID** | CVE-2026-58608 |
| **Impact** | Remote Code Execution (authenticated, network) |
| **MSRC severity** | Critical |
| **CVSS** | 8.8 / 7.7 — `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C` |
| **CWE** | CWE-362: Race Condition → CWE-416: Use After Free |
| **Delivery** | Network — interact with the spooler to close a printer handle while a notification refresh is in flight |
| **KB / Fixed build (diffed lineage, 26H1)** | KB5101649 — `spoolsv.exe` 10.0.28000.2525 |
| **Patch Date** | July 14, 2026 (2026-Jul) |
| **Pre-patch binary** | `spoolsv.exe` 10.0.28000.2336 — SHA256 `0acfd274865ac57e47b277a2b76e42a4ee7cddb2814a98be1db60a88ddd2beff` |
| **Post-patch binary** | `spoolsv.exe` 10.0.28000.2525 — SHA256 `dbaad571b941ef1dd39084497dc0004398b57f03defdf40817626b11741bdd84` |
| **Feature flag** | `Feature_1137672506` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

> Lineage note: this diff is the **26H1 (build 28000)** pair `.2336 → .2525` (KB5101649); the
> same fix ships across the affected spooler SKUs. Cross-checked against an independent
> writeup — functions and offsets (`+0x68`, `+0x6C`) match the ghidriff.

---

## Product Description

`spoolsv.exe` hosts the **Print Spooler** and its remote-notification router. When a client
registers for printer change notifications, the router refreshes them via
`RouterRefreshPrinterChangeNotification`, which — to avoid holding a lock across a slow
provider call — **releases the `RouterNotifySection` critical section before invoking the
provider callback**. Printer handles (`_PRINTHANDLE`) are closed by `InternalClosePrinter`,
which frees the handle from the spooler heap.

---

## Vulnerability Summary

The remote-notification refresh path intentionally drops `RouterNotifySection` before
calling the provider. Pre-patch, **no additional synchronization was held across that
call**, so `InternalClosePrinter` could **free the `_PRINTHANDLE` while a concurrent refresh
was still using it** — a race (CWE-362) yielding a use-after-free of the printer handle
(CWE-416). Per the MSRC FAQ, an attacker triggers it by **creating and then closing a
printer handle without properly invalidating the related notification handle**. Because the
spooler is reachable over the network by an authenticated user (`AV:N`, `PR:L`), the
freed-object reuse is a remote code-execution primitive (Microsoft rates it **Critical,
8.8**).

---

## Prerequisites and Constraints

- Network, authenticated low-privileged (`AV:N`, `AC:L`, `PR:L`, `UI:N`): interact with the
  spooler's remote-notification interface.
- Race a printer-handle close (`InternalClosePrinter`) against an in-flight remote refresh
  that has released `RouterNotifySection` and is calling the provider.
- Result: the `_PRINTHANDLE` is freed while the refresh still references it.

---

## Vulnerability Details

### Root Cause

The refresh path released `RouterNotifySection` before the provider callback with nothing
else keeping the `_PRINTHANDLE` alive, so a concurrent close could free it mid-use.

### The patch (confirmed — diff, .2336 → .2525)

Gated behind `Feature_1137672506`, `InternalClosePrinter` (ratio 0.68) now **drains
in-flight refreshes before freeing the handle**: it marks a close-pending flag and waits on
a new condition variable (`RemoteRefreshDrainCV`) while the in-flight refresh count is
nonzero, holding `RouterNotifySection` across the wait. The provider callback is *not* moved
back inside the section — instead the close path blocks until outstanding refreshes drain:

```c
// InternalClosePrinter (10.0.28000.2525) — PATCHED (from the diff)
if (Feature_1137672506__private_IsEnabled()) {
    *(uint*)((char*)handle + 0x6c) = 1;                 // +0x6C: close pending
    while (*(int*)((char*)handle + 0x68) != 0) {        // +0x68: in-flight refresh count
        SleepConditionVariableCS(&RemoteRefreshDrainCV, // *** drain: wait for refreshes to finish ***
                                 &RouterNotifySection, INFINITE);
    }
}
// ... free the _PRINTHANDLE (HeapFree) ...
if (Feature_1137672506__private_IsEnabled()) {
    EnterCriticalSection(&RouterNotifySection);
    *(uint*)((char*)handle + 0x6c) = 0;                 // clear close-pending
    LeaveCriticalSection(&RouterNotifySection);
}
```

The refresh path acquires the handle for the duration of the provider callback and
increments the `+0x68` in-flight count; an RAII guard decrements it and **wakes
`RemoteRefreshDrainCV`** when the refresh completes. With the close path draining
outstanding refreshes before `HeapFree`, the `_PRINTHANDLE` can no longer be freed while a
refresh still uses it, closing the race use-after-free.

### Patch Completeness Assessment

**CFR-gated behind `Feature_1137672506`.** The drain-on-close logic runs only when the flag
is enabled; the original unsynchronized close still ships when disabled. Verify the flag is
enabled to confirm the fix is live. Given network RCE, apply the July 2026 update regardless.

---

## Detection Guidance

**Behavioural.** Use-after-free / heap-corruption crashes in `spoolsv.exe`
(`InternalClosePrinter` / `RouterRefreshPrinterChangeNotification` / the
`NRemoteNotify_*` remote-notify routines) on unpatched/flag-disabled builds, correlated with
rapid printer-handle open/close churn against the remote-notification interface. Restrict
remote spooler access; keep Print Spooler exposure minimized.

**Config.** The fix is CFR-gated — confirm `Feature_1137672506` is enabled.

---

## References

- MSRC advisory — CVE-2026-58608 (Windows Print Spooler Components Remote Code Execution), released 2026-07-14, KB5101649.
- Full binary diff: `/data/patch_diffs/spoolsv_exe-cve-2026-58608-ghidriff.md`
