# CVE-2026-32078 — Windows Projected File System `prjflt.sys` Reference-Count Underflow in `PrjfScheduleExpansionWorkItem` → Use-After-Free

---

## Summary

| | |
|---|---|
| **Product** | Windows — `prjflt.sys` (Windows Projected File System / ProjFS minifilter) |
| **CVE ID** | CVE-2026-32078 |
| **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-416: Use After Free (cleanup-asymmetry reference-count underflow) |
| **Delivery** | Local — force the work-item allocation to fail during a ProjFS placeholder expansion |
| **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.8115 — SHA256 `51d12b128e33bc0110344cea5c6374192d525c9f918666c9886a57c5486e5d3d` |
| **Post-patch binary** | `prjflt.sys` 10.0.26100.8246 — SHA256 `6eeb5961eb44704a1515a3df15bbf02ce4e63c55b46fb00158a5d65c7b4fd555` |
| **Feature flag** | `Feature_2428439865` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`prjflt.sys` is the Windows Projected File System (ProjFS) minifilter.
`PrjfScheduleExpansionWorkItem` asynchronously queues a placeholder-expansion work
item: it allocates a generic work item, takes a reference on the `FILE_OBJECT`,
and queues the work. It is reached from the `IRP_MJ_CREATE` post-callback
(`PrjfPostCreate` → `PrjfProcessOpenWithinVirtualizationRoots` → `PrjfExpandAndWait`
→ `PrjfLaunchExpansion`).

---

## Vulnerability Summary

The function has two failure points sharing **one** cleanup block (`LABEL_10` /
`LABEL_14`) that **unconditionally** calls `ObfDereferenceObject(FileObject)`:

```c
// PrjfScheduleExpansionWorkItem (10.0.26100.8115) — PRE-PATCH, from the reporter PoC
GenericWorkItem = FltAllocateGenericWorkItem();
if ( !GenericWorkItem ) { Status = 0xC000009A; goto LABEL_10; }   // *** alloc fail: no ref taken yet ***
ObfReferenceObject(FileObject);                                   // ref +1 (only on alloc success)
Status = FltQueueGenericWorkItem(..., FileObject);
if ( Status < 0 ) { FltFreeGenericWorkItem(GenericWorkItem);
LABEL_10:
LABEL_14:
    _InterlockedDecrement(&ExpansionContext->RefNo3);
    ObfDereferenceObject(FileObject);                             // *** unconditional deref ***
    return Status;
}
```

On the **queue-failure** path the reference was already taken, so the dereference
is symmetric. But on the **allocation-failure** path the code `goto`s the same
cleanup **before `ObfReferenceObject` ran**, so it dereferences a `FILE_OBJECT`
that was **never referenced here** — dropping its refcount by one too many. Forcing
the allocation to fail repeatedly (memory pressure) drives the count toward **0
while other paths still hold the object**, freeing it → kernel **use-after-free**
(the reporter's PoC reaches it directly via `NtCreateFile` on a ProjFS root). This
is the ProjFS twin of the same cleanup-asymmetry bug in `wcifs.sys`
(CVE-2026-33098).

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`, `AC:L`); the path is reached by opening a
  file under a ProjFS virtualization root (placeholder expansion).
- Induce `FltAllocateGenericWorkItem` failures (pool pressure) so the
  allocation-failure cleanup over-releases the `FILE_OBJECT` refcount.
- Underflow to zero with live references → free → UAF.

---

## Vulnerability Details

### Root Cause

Two error paths with different resource-acquisition states (reference taken vs not)
merged into one cleanup block via `goto`, so the shared unconditional
`ObfDereferenceObject` over-releases on the allocation-failure path — a
reference-count underflow.

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

Gated behind `Feature_2428439865`, the fix adds a **ref-taken flag** initialized
`false` and set `true` only immediately after `ObfReferenceObject`; the shared
cleanup dereference is guarded so it runs only when a reference was actually taken:

```c
// PrjfScheduleExpansionWorkItem (10.0.26100.8246) — PATCHED, feature-enabled branch (from our diff)
bVar13 = false;
...
ObfReferenceObject(FileObject);
bVar13 = Feature_2428439865__private_IsEnabledDeviceUsage() != 0;   // *** set true only after the ref ***
...
if ( Feature_2428439865__private_IsEnabledDeviceUsage() == 0 || bVar13 )   // *** deref only if ref taken ***
    ObfDereferenceObject(FileObject);
```

On allocation failure (`bVar13 == false`) the dereference is skipped, keeping the
`FILE_OBJECT` refcount balanced.

### Patch Completeness Assessment

**CFR-gated behind `Feature_2428439865`.** The guarded dereference runs only when the
flag is enabled; the original unconditional deref still ships when disabled. Verify
`Feature_2428439865` is enabled to confirm the fix is live.

---

## Detection Guidance

**Behavioural.** Opening files under ProjFS virtualization roots under pool
pressure to force `FltAllocateGenericWorkItem` failures; refcount-underflow / UAF
bugchecks in `prjflt!PrjfScheduleExpansionWorkItem` / `FILE_OBJECT` teardown on
unpatched/flag-disabled builds.

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

---

## References

- MSRC advisory — CVE-2026-32078 (Windows Projected File System Elevation of Privilege), released 2026-04-14, KB5083769.
- Full binary diff: `/data/patch_diffs/prjflt_sys-cve-2026-32078-ghidriff.md`
- Same cleanup-asymmetry pattern: CVE-2026-33098 (`wcifs.sys` `WcScheduleExpansionWorkItem`).
