# CVE-2026-21237 — Windows 9P Redirector `p9rdr.sys` UserCallback/Waiter Lifetime Race in the VNetRoot Create Path → Use-After-Free

---

## Summary

| | |
|---|---|
| **Product** | Windows — `p9rdr.sys` (Plan 9 / 9P redirector; WSL `\\wsl.localhost` file access) |
| **CVE ID** | CVE-2026-21237 |
| **Impact** | Elevation of Privilege |
| **MSRC severity** | Important |
| **CVSS** | 7.0 / 6.1 — `CVSS:3.1/AV:L/AC:H/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** | Local race — open `\Device\P9Rdr\...` while the user-callback / waiter is torn down |
| **KB / Fixed build** | KB5077181 — `p9rdr.sys` 10.0.26100.7840 (Win11 24H2 x64) |
| **Patch Date** | February 10, 2026 (2026-Feb) |
| **Pre-patch binary** | `p9rdr.sys` 10.0.26100.7824 — SHA256 `f52bad554e22d669e8129ce19b7eb8507bdbbd1306284e9a3afac6316bad5e97` |
| **Post-patch binary** | `p9rdr.sys` 10.0.26100.8328 — SHA256 `e532c53349f770f2ba3ce7d503d7f3e24f792c09983bb5649a93b4d3c373354d` |
| **Feature flag** | `Feature_1655910715` / `Feature_3906416953` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`p9rdr.sys` is the Windows 9P (Plan 9) network redirector used by WSL for
`\\wsl.localhost\...` access. Opening `\Device\P9Rdr\<distro>\...` drives
`p9fs::P9CreateVNetRoot` (from `rdbss!RxConstructNetRoot`), which calls
`p9fs::Device::CallUserCallback` → `p9fs::UserCallbackManager::CallUserCallback`.
When no callback is registered, it calls `NotifyUserProcess`, which signals the
`P9RdrService` (via a WNF state update), creates a `_P9RDR_USER_CALLBACK`, adds it
to the manager list, and attaches a **`_P9RDR_WAITER`** whose target is the create
IRP's **`RxContext`** (at `RxContext + 0xD0`). Control returns and
`rdbss!RxCancellableWaitSync` waits on `RxContextForCreate->ConditionEvent`.

---

## Vulnerability Summary

The `_P9RDR_USER_CALLBACK` / `_P9RDR_WAITER` objects and the create-IRP `RxContext`
they reference had an **inadequately synchronized lifetime**. The callback carries
a `PEX_TIMER` (timeout) and a waiter linked to the `RxContext`; between registering
the callback and the create thread waiting on `RxContext->ConditionEvent`, a
concurrent event — the timer firing, the user-mode `P9RdrService` responding, or a
cancel — can tear down / free the callback (and its waiter) while another path still
references it (or vice-versa, the `RxContext`), producing a **use-after-free**
(CWE-362 → CWE-416). Because the object references the create IRP's kernel
`RxContext`, the freed-object reuse is exploitable for local EoP.

```c
// _P9RDR_USER_CALLBACK / _P9RDR_WAITER (from the reporter analysis)
struct _P9RDR_USER_CALLBACK { __int64 RefNo64; _RX_CONTEXT_CREATE *RxContext; LUID AuthenticationId,
    LinkedAuthenticationId; LIST_ENTRY ManagerLink; PVOID PushLock; _P9RDR_WAITER *Waiter1, *Waiter2;
    PEX_TIMER Timer; bool bWaiterEnabled; };
struct _P9RDR_WAITER { _P9RDR_USER_CALLBACK *Callback; PVOID Handler, HandlerParam; _P9RDR_WAITER *Next; };
```

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`); `AC:H` reflects the race window.
- Race: open a `\Device\P9Rdr\...` path (creating a pending user callback + waiter
  bound to the create `RxContext`) and win the race against the callback timeout /
  service response / cancel so one side frees the object the other still uses.

---

## Vulnerability Details

### Root Cause

The user-callback / waiter object lifetime (and its reference to the create-IRP
`RxContext`) was not consistently reference-counted / lock-synchronized across the
notify → wait → timeout/response window, so concurrent teardown could free an
object still in use.

### The patch (confirmed — diff, .7824 → .8328)

Gated behind `Feature_1655910715` / `Feature_3906416953`, the fix reworks the
UserCallback registration and timeout to reference-count the callback and release
it under the manager lock. Our diff shows the changed functions
(`p9fs::UserCallbackManager::RegisterUserCallback`,
`p9fs::UserCallback::SetCallbackTimeout`,
`p9fs::UserCallbackManager::FindOrInsertCallbackWithLockHeld`) gaining explicit
reference management — e.g. `FindOrInsertCallbackWithLockHeld` now calls
`p9fs::UserCallback::ReleaseReference(...)` on the callback, and
`SetCallbackTimeout` reworks the `ExSetTimer` timeout:

```c
// p9fs::UserCallbackManager::FindOrInsertCallbackWithLockHeld (10.0.26100.8328) — PATCHED (from our diff)
...
UserCallback::ReleaseReference(*(UserCallback **)(entry + 0x58));   // *** release under the manager lock ***
// p9fs::UserCallback::SetCallbackTimeout — reworked ExSetTimer(...) for the callback timeout
```

By reference-counting the callback and releasing it only under the manager lock,
the callback (and its waiter/`RxContext` linkage) can no longer be freed while
another path still holds it, closing the race.

### Patch Completeness Assessment

**CFR-gated behind `Feature_1655910715` / `Feature_3906416953`.** The reference-
managed path runs only when the flags are enabled; the original path still ships
when disabled. Verify the flags are enabled to confirm the fix is live.

---

## Detection Guidance

**Behavioural.** Rapid open/cancel of `\\wsl.localhost\...` / `\Device\P9Rdr\...`
paths while the `P9RdrService` is slow to respond (forcing the callback timeout);
UAF / pool-corruption bugchecks in `p9rdr!p9fs::UserCallbackManager::*` /
`NotifyUserProcess` on unpatched/flag-disabled builds.

**Config.** The fix is CFR-gated — confirm `Feature_1655910715` /
`Feature_3906416953` are enabled.

---

## References

- MSRC advisory — CVE-2026-21237 (Windows 9P / WSL Redirector Elevation of Privilege), released 2026-02-10, KB5077181.
- Full binary diff: `/data/patch_diffs/p9rdr_sys-cve-2026-21237-ghidriff.md`
