1. Overview

  • Unpatched Binary: mup_unpatched.sys
  • Patched Binary: mup_patched.sys
  • Overall Similarity Score: 0.9757
  • Diff Statistics: The single behaviorally interesting change is in MupiPackUncHardeningPrefixTableForFsctl. In addition, a cluster of WIL feature-staging / usage-reporting helper functions present in the unpatched build is absent from the patched build (dead-code elimination once the gate below was removed). The tool's headline "1 changed, 0 unmatched" therefore undercounts the removed library functions.
  • Verdict: No security-relevant change. The patch removes a WIL feature-staging gate (Feature_447750459__private_IsEnabledDeviceUsageNoInline) that selected between two nearly-identical code paths inside the UNC hardening prefix-table packing routine, hardwiring the formerly feature-enabled path and eliminating the now-dead feature machinery. This is staged-feature/library churn, not a fix. The gate is a cached in-memory read, so the earlier framing as "blocking I/O inside a held push lock" does not hold.

2. Change Summary

  • Severity: None (no security-relevant change)
  • Nature: WIL feature-staging gate removal + dead-code elimination of the associated feature usage-reporting helpers.
  • Affected Function: MupiPackUncHardeningPrefixTableForFsctl (0x1C001657C unpatched, 0x14001557C patched)

What actually changed: The unpatched MupiPackUncHardeningPrefixTableForFsctl calls Feature_447750459__private_IsEnabledDeviceUsageNoInline (0x1C0003CC4) once per prefix-table entry inside the enumeration loop. The return value selects between an "if" path (feature reported disabled) and an "else" path (feature reported enabled) that compute the same 16-byte-aligned output offset. The patched build deletes the call and keeps only the formerly feature-enabled path, and restructures the loop so the get-next call sits at the top of the loop with a restart flag instead of a get-first-before-loop / get-next-at-bottom shape. The two shapes are semantically equivalent.

Because that call site was the only consumer of the Feature_447750459 "DeviceUsage" feature, the patched build no longer contains the feature accessor, its fallback, or the WIL usage-reporting helpers it pulled in (see Section 8).

Why the earlier "blocking I/O in lock" framing is wrong: Feature_447750459__private_IsEnabledDeviceUsageNoInline is a compiler-generated WIL feature-staging accessor. Its steady-state path is a plain read of the cached in-memory global Feature_447750459__private_featureState, a bit test, and a return — no lock, no WNF state query, no registry access, no I/O. There is no blocking operation performed while the push lock is held, so the claimed denial-of-service / deadlock condition does not exist.

3. Call Chain

The enumeration path itself is reachable, and is described here only to show the gate's context:

  1. MupFsControl (0x1C001A0D0) — FSCTL dispatch; compares the control code against 0x100038 at 0x1C001A0F8.
  2. MupFsctlGetUncHardeningPrefixTable (0x1C00154BC) — prepares the output buffer and calls the packer at 0x1C00154F8.
  3. MupiPackUncHardeningPrefixTableForFsctl (0x1C001657C) — acquires ExAcquirePushLockExclusiveEx on qword_1C000B218, enumerates prefix-table entries via MupiGetFirstOrNextUncHardeningPrefixEntry, and (unpatched only) calls the feature gate per iteration.
  4. Feature_447750459__private_IsEnabledDeviceUsageNoInline (0x1C0003CC4) — cached feature-state read (removed in the patched build).

4. The feature gate is a cached read, not I/O

; ---- Feature_447750459__private_IsEnabledDeviceUsageNoInline @ 0x1C0003CC4 (unpatched) ----
00000001C0003CC4  sub     rsp, 28h
00000001C0003CC8  and     [rsp+28h+arg_0], 0
00000001C0003CCE  mov     eax, cs:Feature_447750459__private_featureState  ; cached in-memory global
00000001C0003CD4  mov     dword ptr [rsp+28h+arg_0], eax
00000001C0003CD8  test    al, 10h                                          ; bit 0x10 = "already cached"
00000001C0003CDA  jz      short loc_1C0003CE1
00000001C0003CDC  and     eax, 1                                           ; cached: return enabled bit
00000001C0003CDF  jmp     short loc_1C0003CF0
00000001C0003CE1  mov     rcx, [rsp+28h+arg_0]
00000001C0003CE6  mov     edx, 3
00000001C0003CEB  call    Feature_447750459__private_IsEnabledFallback     ; one-time init only
00000001C0003CF0  add     rsp, 28h
00000001C0003CF4  retn

The common path never leaves this function's stack frame. The Fallback branch runs at most once to populate the cache; it is not a per-iteration blocking operation and it acquires no push lock that the caller holds.

5. Assembly Analysis of the changed function

Unpatched loop head with the gate:

; ---- MupiPackUncHardeningPrefixTableForFsctl @ 0x1C001657C (unpatched) ----
00000001C00165CA  call    cs:__imp_ExAcquirePushLockExclusiveEx            ; acquire on qword_1C000B218
00000001C00165D6  mov     dl, 1
00000001C00165D8  lea     rcx, stru_1C000B220
00000001C00165DF  call    MupiGetFirstOrNextUncHardeningPrefixEntry        ; get first (before loop)
00000001C00165E4  mov     r15, rax
00000001C00165E7  test    rax, rax
00000001C00165EA  jz      loc_1C00166D6
00000001C00165F0  lea     ebx, [rdi+0Fh]                                   ; offset + 15 (16-byte align)
00000001C00165F3  call    Feature_447750459__private_IsEnabledDeviceUsageNoInline  ; the gate (removed)
00000001C00165F8  test    eax, eax
00000001C00165FA  jnz     short loc_1C001662E                              ; enabled -> "else" path
; "if" (disabled) path: overflow -> edi=0xFFFFFFFF then STATUS_BUFFER_TOO_SMALL
00000001C00165FC  cmp     ebx, edi
00000001C00165FE  jb      loc_1C00166C2                                    ; loc_..C2: mov edi,0FFFFFFFFh
; "else" (enabled) path: overflow -> STATUS_BUFFER_TOO_SMALL, edi untouched
00000001C001662E  cmp     ebx, edi
00000001C0016630  jb      loc_1C00166CC                                    ; loc_..CC: mov ebx,0C0000095h

Patched loop with the gate deleted and get-next hoisted to the top:

; ---- MupiPackUncHardeningPrefixTableForFsctl @ 0x14001557C (patched) ----
00000001400155C6  call    cs:__imp_ExAcquirePushLockExclusiveEx            ; acquire on qword_14000A1F8
00000001400155D2  mov     dl, 1
00000001400155D4  lea     rcx, stru_14000A200
00000001400155DB  call    MupiGetFirstOrNextUncHardeningPrefixEntry        ; get-next at loop top
00000001400155E0  mov     rcx, rax
00000001400155E3  test    rax, rax
00000001400155E6  jz      loc_14001568F
00000001400155EC  lea     eax, [rdi+0Fh]                                   ; no feature call
00000001400155EF  cmp     eax, edi
00000001400155F1  jb      loc_14001568A                                   ; overflow -> STATUS_BUFFER_TOO_SMALL
00000001400155F7  and     eax, 0FFFFFFF0h
...
0000000140015674  jmp     loc_1400155DB                                    ; restart flag now dl=0

The patched code is the unpatched "else" (feature-enabled) path with the branch and duplicate arm removed.

6. The only behavioral difference, and why it is not reachable

The "if" (feature-disabled) and "else" (feature-enabled) paths differ in exactly one place: when the 32-bit expression offset + 0xF wraps (i.e. offset is within 15 of 0xFFFFFFFF), the disabled path additionally writes *OutputSize = 0xFFFFFFFF (mov edi, 0FFFFFFFFh at 0x1C00166C2) before returning STATUS_BUFFER_TOO_SMALL, whereas the enabled/patched path leaves *OutputSize unchanged and returns the same status. Both return an error; neither reads or writes out of bounds.

Reaching that branch requires the accumulated output offset (rdi, a running sum of per-entry NextEntryOffset values) to approach 4 GB, which the 32-bit output buffer size (arg2, r12d) and the entry copy logic never allow. It is dead defensive code, not an attacker-reachable primitive. Aligning both paths is a cosmetic cleanup, not a security-relevant strictness change, and it is not a regression in either direction.

7. Trigger Conditions

The enumeration is reached from user mode by issuing FSCTL 0x100038 (via NtFsControlFile / DeviceIoControl) against the MUP device, which routes through MupFsControl to MupFsctlGetUncHardeningPrefixTable and then the packer. Doing so exercises the changed function but produces no security-relevant difference between the two builds: the gate it removes is a cached read, and the aligned-offset result is identical for any input a caller can supply.

8. Changed / Removed Functions — Full Triage

  • MupiPackUncHardeningPrefixTableForFsctl (0x1C001657C -> 0x14001557C, Change Type: WIL feature-staging churn)
  • Removed the per-iteration call to Feature_447750459__private_IsEnabledDeviceUsageNoInline.
  • Collapsed the two feature-selected arms into the single formerly-enabled path.
  • Restructured the enumeration loop from get-first-then-get-next-at-bottom to get-next-at-top with a restart flag.
  • Register allocation: the output-size pointer (arg4) moved from rsi (spilled and reloaded from stack at 0x1C00166D1 in the unpatched build) to the non-volatile r13. This is an optimizer artifact of removing the branch, not a fix.

  • Removed in the patched build (dead-code elimination, present only in unpatched): Feature_447750459__private_IsEnabledDeviceUsageNoInline (0x1C0003CC4), Feature_447750459__private_IsEnabledFallback (0x1C0003CFC), wil_details_FeatureReporting_IncrementOpportunityInCache (0x1C000414C), wil_details_FeatureReporting_IncrementUsageInCache (0x1C0004224), wil_details_FeatureReporting_RecordUsageInCache (0x1C0004308), wil_details_FeatureReporting_ReportUsageToService (0x1C0004488), wil_details_FeatureReporting_ReportUsageToServiceDirect (0x1C0004504), wil_details_FeatureStateCache_ReevaluateCachedFeatureEnabledState (0x1C00045F0), wil_details_FeatureStateCache_TryEnableDeviceUsageFastPath (0x1C0004700), wil_details_GetCurrentFeatureEnabledState (0x1C0004754), wil_details_IsEnabledFallback (0x1C00048B4), wil_details_MapReportingKind (0x1C0004944). These are WIL feature-staging / usage-reporting helpers that became unreachable once the single gate consumer was removed. None are security-relevant.

9. Independent Diff Note

The report's tool summary states "1 changed, 0 unmatched." An independent content-matched comparison shows one changed function plus the ~12 removed WIL helpers listed above. No changed function outside this WIL feature-staging cluster was found, and none of the removed helpers implements a security check; their removal is a consequence of compiling out the feature gate, not an omitted fix.

10. Confidence & Caveats

  • Confidence Level: High. The removed call target is named Feature_447750459__private_IsEnabledDeviceUsageNoInline and reads a cached feature-state global; the loop bodies match on both sides except for the flighting branch and the unreachable overflow-guard detail; the removed helpers are the WIL feature-staging support functions for that feature.
  • Result: No security-relevant change. The push lock is real and is held across the enumeration, but nothing blocking runs inside it in either build. There is no CWE-667 / CWE-400 condition, no reachable memory-safety issue, and no direction in which the patched build is meaningfully stricter.