pcw.sys — WIL feature-staging finalization in PcwpCloseNotifier
KB5094127
1. Overview
| Field | Value |
|---|---|
| Unpatched binary | pcw_unpatched.sys |
| Patched binary | pcw_patched.sys |
| Overall similarity | 0.9329 |
| Matched functions | 135 |
| Changed functions | 1 with a real logic change (PcwpCloseNotifier); the remainder is relocation/infrastructure churn |
| Removed functions | 11 (all WIL feature-staging / telemetry helpers) |
| Added functions | 0 |
Verdict: The patch finalizes a staged feature (Windows Internal Library feature ID 1222059323, name DeviceUsage). In PcwpCloseNotifier the runtime feature gate Feature_1222059323__private_IsEnabledDeviceUsage is removed and the feature-enabled code path is made permanent. All supporting WIL staging/telemetry helpers and the feature's descriptor-table entry are removed, which shifts the read-only data section and relocates several unrelated functions without changing their logic. No security-relevant change is present. There is no double-completion, no use-after-free, and no reachable exploit primitive.
2. Vulnerability Summary
Finding 1 — No security-relevant change
| Attribute | Detail |
|---|---|
| Severity | None (informational) |
| Class | Feature-staging finalization (WIL Feature_*__private_IsEnabled* gate removal) |
| Function | PcwpCloseNotifier (unpatched 0x1C000C614, patched 0x1C000B614) |
| Removed call | Feature_1222059323__private_IsEnabledDeviceUsage (unpatched 0x1C000C763) |
What actually changed
PcwpCloseNotifier iterates the consumer list during notifier close/rundown and, for each consumer that could not be notified normally, decides whether to run the fallback teardown routine PcwpDeliverGenericDisconnectNotification.
In the unpatched build this decision is gated by a Windows Internal Library (WIL) feature check. Feature_1222059323__private_IsEnabledDeviceUsage reads the staging variable Feature_1222059323__private_featureState: if bit 0x10 (the "state is cached/determined" bit) is set it returns bit 0 (feature enabled/disabled), otherwise it calls Feature_1222059323__private_IsEnabledFallback. This is a standard staged-feature gate, not a runtime/per-CPU processor-state check.
The gate selects between two behaviors for the fallback teardown:
- Feature enabled: run teardown when
!(flags & 2)— i.e. deliver the fallback disconnect only to consumers that are still connected. - Feature disabled: run teardown when
(flags & 2)— i.e. the legacy behavior, delivering to consumers already marked disconnected.
The patched build removes the gate and keeps the feature-enabled behavior unconditionally: teardown when !(flags & 2). The WIL staging infrastructure that supported this feature (Feature_1222059323__private_IsEnabledFallback, the wil_details_FeatureReporting_*, wil_details_FeatureStateCache_*, wil_details_StagingConfig_*, wil_details_GetCurrentFeatureEnabledState, wil_details_IsEnabledFallback helpers) and the feature's entry in the WIL feature-descriptor table are removed as well.
This is the normal end state of a WIL staged rollout: a feature that shipped behind a runtime flag is graduated to always-on and the flag machinery is deleted. It is not a security fix.
Why the earlier "double-completion / UAF" reading does not hold
- The removed call is a WIL feature-staging gate, not a per-CPU/global runtime-state check and not a read of any
data_1c0005468global. That global and the "KeIsExecutingDpc-style" description do not correspond to the real function body. - The consumer flags bit
0x2(at consumer object+0x68) means "disconnected / not fully connected." It is set inPcwpCreateUserRegistrationbeforeObInsertObject(0x1C000C902) and inPcwpDisconnectUserRegistration(0x1C000CAB5), and cleared on a successful insert (0x1C000C9B9). It is not a "teardown already ran" marker. The teardown sinkPcwpDeliverGenericDisconnectNotificationORs the flags with9(bits0and3) and never sets bit0x2, so a prior teardown does not put a consumer into theflags & 2state. - Within a single close/rundown pass each consumer node is visited exactly once, and the "notify, then optionally fall back to teardown" structure is identical in both builds. There is no path that calls
PcwpDeliverGenericDisconnectNotificationtwice on the same consumer, so there is no double-completion to fix. The only difference between the builds is which set of consumers (still-connected vs. already-disconnected) receives the fallback disconnect.
3. Pseudocode Diff
Unpatched — PcwpCloseNotifier teardown decision (feature-gated)
KeEnterCriticalRegion();
ExAcquirePushLockExclusiveEx(&rsi[8], 0); // consumer lock at consumer+0x48
int enabled = Feature_1222059323__private_IsEnabledDeviceUsage();
char flags = *(rsi + 0x60); // consumer+0x68 flags
if (enabled) {
if (!(flags & 2)) // still connected -> fallback teardown
goto teardown;
} else {
if (flags & 2) // legacy: already-disconnected -> teardown
goto teardown;
}
goto unlock;
teardown:
if (flags & 8) { flags |= 4; *(rsi + 0x60) = flags; }
else PcwpDeliverGenericDisconnectNotification(rsi - 8);
unlock:
ExReleasePushLockExclusiveEx(&rsi[8], 0);
KeLeaveCriticalRegion();
Patched — PcwpCloseNotifier teardown decision (gate removed)
KeEnterCriticalRegion();
ExAcquirePushLockExclusiveEx(&rsi[8], 0);
char flags = *(rsi + 0x60);
if (!(flags & 2)) { // feature-enabled path made permanent
if (flags & 8) *(rsi + 0x60) = flags | 4;
else PcwpDeliverGenericDisconnectNotification(rsi - 8);
}
ExReleasePushLockExclusiveEx(&rsi[8], 0);
KeLeaveCriticalRegion();
Fallback teardown sink PcwpDeliverGenericDisconnectNotification (unchanged between builds)
int64_t status = *(consumer + 0x38);
*(consumer + 0x68) |= 9; // set bits 0 and 3 (NOT bit 2)
*(consumer + 0x60) = 0; // clear in-flight notification pointer
return IoSetIoCompletionEx(
*(consumer + 0x20), // IoCompletion handle
*(consumer + 0x28), // KeyContext
0, 0, 0xffffffff, 0, status);
The sink is called at most once per consumer per pass in both builds. Its body is byte-for-byte identical (only its address moved: unpatched 0x1C000BA60, patched 0x1C000AA60).
4. Assembly Analysis
Unpatched PcwpCloseNotifier — teardown decision block
00000001C000C745 call cs:__imp_KeEnterCriticalRegion
00000001C000C751 xor edx, edx
00000001C000C753 lea rcx, [rsi+40h]
00000001C000C757 call cs:__imp_ExAcquirePushLockExclusiveEx
00000001C000C763 call Feature_1222059323__private_IsEnabledDeviceUsage ; WIL feature gate (removed in patch)
00000001C000C768 test eax, eax
00000001C000C76A mov al, [rsi+60h] ; consumer flags (consumer+0x68)
00000001C000C76D jz short loc_1C000C775 ; feature disabled -> legacy leg
00000001C000C76F test al, 2 ; feature enabled: skip if disconnected
00000001C000C771 jnz short loc_1C000C78D
00000001C000C773 jmp short loc_1C000C779 ; connected -> teardown
00000001C000C775 test al, 2 ; feature disabled: teardown if disconnected
00000001C000C777 jz short loc_1C000C78D
00000001C000C779 test al, 8 ; teardown path (deferred flag?)
00000001C000C77B jnz short loc_1C000C788
00000001C000C77D lea rcx, [rsi-8]
00000001C000C781 call PcwpDeliverGenericDisconnectNotification ; fallback teardown (once per consumer)
00000001C000C786 jmp short loc_1C000C78D
00000001C000C788 or al, 4
00000001C000C78A mov [rsi+60h], al
00000001C000C78D xor edx, edx
00000001C000C78F lea rcx, [rsi+40h]
00000001C000C793 call cs:__imp_ExReleasePushLockExclusiveEx
00000001C000C79F call cs:__imp_KeLeaveCriticalRegion
The removed WIL gate Feature_1222059323__private_IsEnabledDeviceUsage (unpatched 0x1C0001E9C)
00000001C0001E9C sub rsp, 28h
00000001C0001EA6 mov eax, cs:Feature_1222059323__private_featureState ; WIL feature state variable
00000001C0001EB0 test al, 10h ; is the state cached/determined?
00000001C0001EB2 jz short loc_1C0001EB9
00000001C0001EB4 and eax, 1 ; yes -> return enabled bit
00000001C0001EB7 jmp short loc_1C0001EC8
00000001C0001EB9 mov rcx, [rsp+28h+arg_0]
00000001C0001EBE mov edx, 3
00000001C0001EC3 call Feature_1222059323__private_IsEnabledFallback ; no -> fallback evaluation
00000001C0001EC8 add rsp, 28h
00000001C0001ECC retn
Patched PcwpCloseNotifier — teardown decision block (gate gone)
00000001C000B742 call cs:__imp_KeEnterCriticalRegion
00000001C000B74E xor edx, edx
00000001C000B750 lea rcx, [rsi+40h]
00000001C000B754 call cs:__imp_ExAcquirePushLockExclusiveEx
00000001C000B760 mov al, [rsi+60h] ; consumer flags
00000001C000B763 test al, 2
00000001C000B765 jnz short loc_1C000B77B ; disconnected -> skip (no gate)
00000001C000B767 test al, 8
00000001C000B769 jnz short loc_1C000B776
00000001C000B76B lea rcx, [rsi-8]
00000001C000B76F call PcwpDeliverGenericDisconnectNotification ; teardown (at most once)
00000001C000B774 jmp short loc_1C000B77B
00000001C000B776 or al, 4
00000001C000B778 mov [rsi+60h], al
00000001C000B77B xor edx, edx
00000001C000B77D lea rcx, [rsi+40h]
00000001C000B781 call cs:__imp_ExReleasePushLockExclusiveEx
00000001C000B78D call cs:__imp_KeLeaveCriticalRegion
There is no call Feature_1222059323__private_IsEnabledDeviceUsage anywhere in the patched binary; the feature gate and its supporting functions are gone.
Fallback teardown sink PcwpDeliverGenericDisconnectNotification (unpatched 0x1C000BA60, unchanged logic)
00000001C000BA64 mov rax, [rcx+38h]
00000001C000BA6B or byte ptr [rcx+68h], 9 ; sets bits 0 and 3 (not bit 2)
00000001C000BA72 and qword ptr [rcx+60h], 0 ; clear in-flight notification pointer
00000001C000BA77 mov rdx, [rcx+28h] ; KeyContext
00000001C000BA7B mov rcx, [rcx+20h] ; IoCompletion handle
00000001C000BA93 call cs:__imp_IoSetIoCompletionEx
5. Trigger Conditions
There is no vulnerability to trigger. The change is a WIL staged-feature graduation.
For reference, PcwpCloseNotifier is reached from the PcwObject delete procedure PcwpDeleteObject (0x1C000CCB0), which dispatches on *(obj+0): value 0 frees the list, value 1 calls PcwpCloseUserRegistration, value 2 calls PcwpCloseNotifier (0x1C000CCD5). This dispatch and the surrounding notifier-close logic are identical in both builds; only the feature-gate branch inside PcwpCloseNotifier differs.
6. Exploit Primitive & Development Notes
No exploit primitive exists. The claimed double-completion / use-after-free is not present:
- The fallback teardown sink
PcwpDeliverGenericDisconnectNotificationis invoked at most once per consumer per close/rundown pass in both builds. - The consumer flags bit
0x2marks "disconnected / not connected," not "teardown already performed," so a consumer that has been torn down is not re-selected by any branch. - The
IoSetIoCompletionExhandle/KeyContext values are consumer-registration state and are not double-completed.
Because there is no memory-safety or completion-lifecycle defect, there is nothing to develop into a primitive — no pool grooming, no information leak, and no mitigation-bypass considerations apply.
7. Debugger PoC Playbook
Not applicable — there is no vulnerability to reproduce.
If verifying the behavioral difference itself, the observable change is confined to PcwpCloseNotifier:
; Unpatched: the feature gate exists
bp pcw_unpatched!PcwpCloseNotifier+0x14f ; 0x1C000C763 = call Feature_1222059323__private_IsEnabledDeviceUsage
; eax after the call = the feature's enabled state
; al = byte ptr [rsi+0x60] = consumer flags (bit 0x2 = disconnected)
; Patched: no such call; the flags&2 test directly gates teardown
bp pcw_patched!PcwpCloseNotifier+0x14f ; 0x1C000B760 = mov al,[rsi+0x60]
Both builds call PcwpDeliverGenericDisconnectNotification at most once per consumer; a second call on the same consumer within one pass does not occur.
8. Changed Functions — Full Triage
PcwpCloseNotifier (unpatched 0x1C000C614 → patched 0x1C000B614)
| Attribute | Value |
|---|---|
| Similarity | 0.9293 |
| Change type | Feature-staging finalization (not security-relevant) |
| Size | 534 bytes → 512 bytes |
What changed: The call Feature_1222059323__private_IsEnabledDeviceUsage at 0x1C000C763 was removed and the two-leg feature-gated branch collapsed to a single if (!(flags & 2)) guard — the feature-enabled behavior made permanent. No memory-safety change.
Removed functions (WIL staging/telemetry infrastructure)
The following functions exist only in the unpatched build and were removed when the feature was graduated:
Feature_1222059323__private_IsEnabledDeviceUsage, Feature_1222059323__private_IsEnabledFallback, wil_details_FeatureReporting_RecordUsageInCache, wil_details_FeatureReporting_ReportUsageToService, wil_details_FeatureReporting_ReportUsageToServiceDirect, wil_details_FeatureStateCache_ReevaluateCachedFeatureEnabledState, wil_details_FeatureStateCache_TryEnableDeviceUsageFastPath, wil_details_GetCurrentFeatureEnabledState, wil_details_IsEnabledFallback, wil_details_StagingConfig_Load, wil_details_StagingConfig_QueryFeatureState.
Other functions that differ (relocation/infrastructure only)
wil_InitializeFeatureStaging,wil_details_EvaluateFeatureDependencies,wil_details_PopulateInitialConfiguredFeatureStates,wil_details_ReevaluateOnFeatureConfigurationChange: differ only because the WIL feature-descriptor table lost feature1222059323's entry (loop bounds change from&Feature_1222059323__private_descriptor/featureDescriptors_ztofeatureDescriptors_a). Staging-infrastructure churn.McGenEventWrite_EtwWriteTransfer,PcwpFastIoDeviceControl,PcwpInitDefaultSecurityDescriptor,SepSddlGetAclForString,SepSddlGetSidForString: byte-identical logic; only referenced data-section addresses shifted (e.g.unk_1C0003000→unk_1C0002000,off_1C0005160→off_1C0004160) because removing the WIL code/data moved the read-only section by0x1000. No behavioral change.
9. Unmatched Functions
Added: none.
Removed: the 11 WIL feature-staging / telemetry helpers listed in section 8. Their removal is the mechanical consequence of finalizing feature 1222059323 and is not a security change.
10. Confidence & Caveats
Confidence: High
Rationale:
- The removed call target is named Feature_1222059323__private_IsEnabledDeviceUsage, and its body is a textbook WIL feature-state read (Feature_1222059323__private_featureState, bit 0x10 cache check, bit 0 result, fallback call). This is a staged-feature gate, not a runtime processor-state check.
- The patched build keeps the feature-enabled branch (if (!(flags & 2))) and deletes the gate plus the entire WIL staging/telemetry support set and the feature's descriptor entry — the standard shape of a graduated staged feature.
- The fallback teardown sink PcwpDeliverGenericDisconnectNotification is byte-identical between builds, is invoked at most once per consumer per pass, and sets flags bits 0/3 (never bit 0x2), so there is no re-entrant teardown and no double-completion.
- An independent function-by-function comparison of both decompilations shows every other differing function is either WIL staging-table churn or pure data-address relocation, with no memory-safety or access-control change.
Net assessment: No security-relevant change. Severity: none. No CWE applies.