# CVE-2026-32069 — Windows Projected File System `prjflt.sys` Stale `pDirEntry` in `PrjfStoreNotificationMappingEntries` → Double Free

---

## Summary

| | |
|---|---|
| **Product** | Windows — `prjflt.sys` (Windows Projected File System / ProjFS minifilter; notification mappings) |
| **CVE ID** | CVE-2026-32069 |
| **Impact** | Elevation of Privilege |
| **MSRC severity** | Important |
| **CVSS** | 7.8 / 6.8 — `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C` |
| **CWE** | CWE-415: Double Free |
| **Delivery** | Local — a crafted set of ProjFS notification-mapping entries |
| **KB / Fixed build** | KB5083769 — `prjflt.sys` 10.0.26100.8246 (Win11 24H2 x64) |
| **Patch Date** | April 14, 2026 (2026-Apr) |
| **Pre-patch binary** | `prjflt.sys` 10.0.26100.8036 — SHA256 `d5afe5fe1f66c520b1b49bf43d8567d80c4dd42a03621cbceb6587bc29a424dc` |
| **Post-patch binary** | `prjflt.sys` 10.0.26100.8246 — SHA256 `6eeb5961eb44704a1515a3df15bbf02ce4e63c55b46fb00158a5d65c7b4fd555` |
| **Feature flag** | `Feature_4243261755` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`PrjfStoreNotificationMappingEntries` processes a user-supplied array of
`_PRJ_NOTIFICATION_MAPPING_ENTRY` records (each with a `NotificationBitMask` and a
`NextEntry` link), validating and inserting each into the union context's mapping
set. For each record it calls `PrjfCreateMappingEntry`, which allocates a dir-entry
buffer (tag `'mnJP'`) into `MappingEntry.pDirEntry`:

```c
// PrjfCreateMappingEntry (pre) — from the reporter analysis
if (NotificationBitMask == 0xFFFFFFFF) {          // error case
    Status = 0xC000000D;
    if (pMappingEntry->pDirEntry) { ExFreePoolWithTag(pMappingEntry->pDirEntry,'mnJP'); pMappingEntry->pDirEntry = 0; }
}
...
pDirEntry = ExAllocatePool2(0x102, EntrySize, 'mnJP');
pMappingEntry->pDirEntry = pDirEntry;
memmove(pDirEntry, pInDirEntry, pInDirEntry->EntrySize);
```

---

## Vulnerability Summary

`PrjfStoreNotificationMappingEntries` frees the dir-entry at the tail of each loop
iteration but **does not null the pointer** in the `MappingEntry` structure, which
is **reused** on the next iteration:

```c
// PrjfStoreNotificationMappingEntries (pre) — from the reporter analysis
LABEL_22:
    if (MappingEntry.pDirEntry) ExFreePoolWithTag(MappingEntry.pDirEntry, 'mnJP');  // freed, NOT nulled
    continue;   // MappingEntry reused next iteration with stale pDirEntry
```

On the next iteration, if `PrjfCreateMappingEntry` is entered with
`NotificationBitMask == 0xFFFFFFFF` (an attacker-reachable error case), its
error path frees `pMappingEntry->pDirEntry` — the **stale, already-freed** `'mnJP'`
pointer — a second time. That double free corrupts the paged pool (CWE-415).
`prjflt` runs in the kernel and the mapping request is reachable locally, so the
pool corruption is a local EoP primitive.

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`, `AC:L`): submit a set of ProjFS
  notification-mapping entries.
- Shape the entries so a later `PrjfCreateMappingEntry` call sees
  `NotificationBitMask == 0xFFFFFFFF` after a prior iteration freed the reused
  `MappingEntry.pDirEntry`.
- Result: the same `'mnJP'` pool block is freed twice.

---

## Vulnerability Details

### Root Cause

After freeing `MappingEntry.pDirEntry`, the local pointer in the reused
`MappingEntry` structure was left dangling, so a subsequent
`PrjfCreateMappingEntry` error path freed the same buffer again.

### The patch (confirmed — diff, .8036 → .8246)

Gated behind `Feature_4243261755`, `PrjfStoreNotificationMappingEntries` now
**clears `MappingEntry.pDirEntry` after the free**, and `PrjfCreateMappingEntry`'s
error-path free is guarded, so the reused structure never carries a stale pointer
into the next iteration:

```c
// PrjfStoreNotificationMappingEntries (10.0.26100.8246) — PATCHED (from our diff)
if (MappingEntry.pDirEntry) ExFreePoolWithTag(MappingEntry.pDirEntry, 'mnJP');
MappingEntry.pDirEntry = 0;     // *** null after free ***  (local_40 = 0)
```

With the pointer zeroed after each free, the next `PrjfCreateMappingEntry` no
longer double-frees the `'mnJP'` block, closing the double free.

### Patch Completeness Assessment

**CFR-gated behind `Feature_4243261755`.** The null-after-free / guarded-free runs
only when the flag is enabled; the original double-free path still ships when
disabled. Verify `Feature_4243261755` is enabled to confirm the fix is live.

---

## Detection Guidance

**Behavioural.** ProjFS notification-mapping submissions with error-triggering
entries (`NotificationBitMask == 0xFFFFFFFF`) interleaved across a multi-entry list;
double-free / paged-pool-corruption bugchecks (`ExFreePoolWithTag`, tag `'mnJP'`)
reached from `prjflt!PrjfStoreNotificationMappingEntries` /
`PrjfCreateMappingEntry` on unpatched/flag-disabled builds.

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

---

## References

- MSRC advisory — CVE-2026-32069 (Windows Projected File System Elevation of Privilege), released 2026-04-14, KB5083769.
- Full binary diff: `/data/patch_diffs/prjflt_sys-cve-2026-32069-ghidriff.md`
