1. Overview

  • Unpatched Binary ID: mup_unpatched.sys
  • Patched Binary ID: mup_patched.sys
  • Overall Similarity Score: 0.9762
  • Diff Statistics: Of the 345 functions present in both builds, exactly one (MupiCheckForMailslotRequest) has a changed instruction sequence; the other 344 differ only by relocation. 13 functions are added in the patched build (all WIL feature-staging infrastructure); 0 functions are removed.
  • Verdict: The only behavioral delta is a WIL (Windows Implementation Library) feature-staged change inside MupiCheckForMailslotRequest. When a newly added, default-off feature flag is enabled, the function computes a UNICODE_STRING.Length as end - (separator + 2) instead of end - separator, trimming a 2-byte (one wide char) over-count. The old computation is retained on the else branch and runs by default, so no delivered behavior changes. The construct is confined to a diagnostic/telemetry code path for failed remote-mailslot create attempts; no reachable out-of-bounds read to user-mode data, information disclosure, or crash is demonstrable from the binaries. This is staged-rollout / defense-in-depth churn, not a delivered security fix.

2. Change Summary

  • Severity: None (informational)
  • Change Class: Feature-staged length-computation adjustment (WIL feature-gating churn)
  • Affected Function: MupiCheckForMailslotRequest (0x14001AAE0 unpatched, 0x14001BBB0 patched)

What the function does: MupiCheckForMailslotRequest runs on the IRP_MJ_CREATE path. It is called from MupCreate when the create has no related file object and CurrentStackLocation->MajorFunction == 0. It inspects the create name (a UNICODE_STRING-shaped descriptor: Length at +0x58, Buffer at +0x60), locates path separators via MupFindMatchingChar, and compares the leading component against MupiMailSlotName with RtlCompareUnicodeString. If the name is a mailslot request and mailslots are disabled by policy (MupEnableMailslotsByPolicy / MupEnableMailslots are 0), it takes a diagnostic branch: it builds a second UNICODE_STRING (String2) from the remaining path segment, compares it against a 6-entry known-provider prefix table (unk_140007080 unpatched / unk_140008080 patched) in a loop, and calls the telemetry loggers MupWriteTelemetryKnownRemoteMailslotFailed or MupWriteTelemetryOtherRemoteMailslotFailed.

The length discrepancy: In this diagnostic branch the code sets String2.Buffer = separator + 2 (one wide char past the second separator) but computes String2.Length = end_of_name - separator, where end_of_name = name.Buffer + name.Length. Because the buffer starts 2 bytes after separator, this length spans [separator+2, end_of_name+2) — 2 bytes past the end of the name content. The patched build adds a conditional: when the WIL feature Feature_364057912__private_IsEnabledDeviceUsageNoInline reports enabled, the length is computed as end_of_name - (separator + 2) (trimming the 2 bytes); when the feature is not enabled — the default — the original end_of_name - separator computation is used unchanged.

Why this is not a delivered security fix: 1. The corrected computation is behind a WIL feature-staging gate that is off by default, with the original code retained on the else branch (staged rollout). 2. The over-counted length is consumed only inside a telemetry/diagnostic path: RtlCompareUnicodeString against a fixed prefix table, and RtlHashUnicodeString inside MupWriteTelemetryOtherRemoteMailslotFailed, whose hash is emitted to ETW telemetry. No bytes are returned to the calling user-mode process. 3. Whether the 2-byte read is actually out of bounds depends on slack in the create-name allocation (create-name buffers are commonly null-terminated, which would place the extra wide char inside the allocation). No out-of-bounds access is demonstrable from the static binaries.

Reachability: The branch is reached from CreateFile/NtCreateFile (IRP_MJ_CREATE) → MupCreateMupiCheckForMailslotRequest, but only when the create name matches MupiMailSlotName and mailslots are disabled by policy. Reaching it therefore requires a mailslot-shaped name and a specific policy configuration.

3. Decompiled Diff

The relevant diagnostic branch, taken directly from the decompilation of both builds.

// UNPATCHED — MupiCheckForMailslotRequest @ 0x14001AAE0
if ( v18 != nullptr )                         // v18 = second separator pointer
{
    RtlInitUnicodeString(&String2, nullptr);
    String2.Length        = v21 - (_WORD)v18; // v21 = name.Buffer + name.Length (end)
    String2.MaximumLength = v21 - (_WORD)v18;  // Length spans 2 bytes past end
    v22 = 0;
    String2.Buffer = v18 + 1;                  // Buffer = separator + 2 bytes
    while ( RtlCompareUnicodeString((PCUNICODE_STRING)&unk_140007080 + v22, &String2, 1u) != 0 )
    {
        if ( ++v22 >= 6u )
            goto LABEL_23;
    }
    MupWriteTelemetryKnownRemoteMailslotFailed();
LABEL_23:
    if ( v22 == 6 )
        MupWriteTelemetryOtherRemoteMailslotFailed(&String2);
}

// PATCHED — MupiCheckForMailslotRequest @ 0x14001BBB0
if ( v18 != nullptr )
{
    RtlInitUnicodeString(&String2, nullptr);
    if ( (unsigned int)Feature_364057912__private_IsEnabledDeviceUsageNoInline() != 0 )
        v22 = v21 - ((_WORD)v18 + 2);          // feature ON: end - (separator + 2)
    else
        v22 = v21 - (_WORD)v18;                // feature OFF (default): original behavior
    String2.Length        = v22;
    String2.MaximumLength = v22;
    v23 = 0;
    String2.Buffer = v18 + 1;                  // Buffer = separator + 2 bytes
    while ( RtlCompareUnicodeString((PCUNICODE_STRING)&unk_140008080 + v23, &String2, 1u) != 0 )
    {
        if ( ++v23 >= 6u )
            goto LABEL_26;
    }
    MupWriteTelemetryKnownRemoteMailslotFailed();
LABEL_26:
    if ( v23 == 6 )
        MupWriteTelemetryOtherRemoteMailslotFailed(&String2);
}

4. Assembly Analysis

Unpatched sequence (MupiCheckForMailslotRequest):

000000014001AC06  movzx   ebx, word ptr [r13+58h]   ; ebx = name.Length (16-bit)
000000014001AC0B  add     rbx, [r13+60h]            ; rbx = name.Buffer + name.Length = end pointer
...
000000014001AC7C  sub     bx, si                    ; bx = end - separator  (2 bytes over the buffer span)
000000014001AC7F  lea     rax, [rsi+2]              ; rax = separator + 2 = Buffer start
000000014001AC83  mov     [rsp+78h+String2.Length], bx        ; Length stored (over-counted by 2)
000000014001AC88  lea     rsi, unk_140007080        ; 6-entry known-provider prefix table
000000014001AC8F  mov     [rsp+78h+String2.MaximumLength], bx
000000014001AC94  movzx   ebx, bp
000000014001AC97  mov     [rsp+78h+String2.Buffer], rax       ; Buffer = separator + 2
...
000000014001ACB2  call    cs:__imp_RtlCompareUnicodeString    ; prefix-table comparison loop
...
000000014001ACE2  call    MupWriteTelemetryOtherRemoteMailslotFailed   ; ETW telemetry logger

Patched sequence (MupiCheckForMailslotRequest):

000000014001BCD3  movzx   ebx, word ptr [r13+58h]
000000014001BCD8  add     rbx, [r13+60h]            ; rbx = end pointer
...
000000014001BD3D  lea     r14, [rsi+2]              ; r14 = separator + 2 (Buffer, pre-computed)
000000014001BD41  call    cs:__imp_RtlInitUnicodeString
000000014001BD4D  call    Feature_364057912__private_IsEnabledDeviceUsageNoInline   ; WIL feature-staging gate
000000014001BD52  test    eax, eax
000000014001BD54  jnz     short loc_14001BD5B       ; feature enabled -> corrected path
000000014001BD56  sub     bx, si                    ; feature OFF (default): end - separator (original)
000000014001BD59  jmp     short loc_14001BD5F
000000014001BD5B  sub     bx, r14w                  ; feature ON: end - (separator + 2)
000000014001BD5F  mov     [rsp+78h+String2.Length], bx
000000014001BD64  lea     rsi, unk_140008080        ; 6-entry known-provider prefix table
000000014001BD6B  mov     [rsp+78h+String2.MaximumLength], bx
000000014001BD73  mov     [rsp+78h+String2.Buffer], r14       ; Buffer = separator + 2

Feature_364057912__private_IsEnabledDeviceUsageNoInline (0x140002494, patched build only) reads Feature_364057912__private_featureState and, if not overridden, defers to Feature_364057912__private_IsEnabledFallbackwil_details_IsEnabledFallback with the default feature state. This is the standard WIL feature-staging query, not a registry/configuration read.

5. Trigger Conditions

To reach the changed branch at all:

  1. Issue an IRP_MJ_CREATE (CreateFile/NtCreateFile) with no related (relative) file object, so MupCreate calls MupiCheckForMailslotRequest.
  2. The create name must match MupiMailSlotName under RtlCompareUnicodeString (the leading component is compared against the mailslot name); otherwise the function returns early.
  3. Mailslots must be disabled by policy: MupEnableMailslotsByPolicy (or, when it equals 2, MupEnableMailslots) must be 0, selecting the diagnostic/telemetry branch.
  4. The second MupFindMatchingChar result (v18) must be non-NULL, i.e., there is a further separator after the mailslot component.

Under these conditions the diagnostic branch builds String2 and runs the prefix-comparison loop and telemetry logging. The 2-byte length difference between builds only manifests here, and only affects the length passed to RtlCompareUnicodeString/RtlHashUnicodeString inside this telemetry path.

6. Impact Assessment

  • Delivered behavior: Unchanged. The corrected length is gated behind a default-off WIL feature; the original end - separator computation runs by default in the patched build.
  • Data flow of the over-counted length: The length is used only by RtlCompareUnicodeString against a fixed 6-entry prefix table and by RtlHashUnicodeString inside MupWriteTelemetryOtherRemoteMailslotFailed, which emits a hash to ETW telemetry. No bytes are copied back to the calling user-mode process.
  • Out-of-bounds claim: Not demonstrable from the binaries. Whether reading the extra wide char at [end, end+2) leaves the allocation depends on create-name buffer slack (commonly a null terminator is present). No crash, information leak, or KASLR primitive is provable.
  • Conclusion: At most a latent, telemetry-confined length over-count whose correction is staged behind a disabled-by-default feature flag. Not a delivered, reachable security fix.

7. Verification Playbook

The following anchors let a reviewer confirm the change directly in the two builds.

Unpatched (mup_unpatched.sys): * 0x14001AAE0 — entry of MupiCheckForMailslotRequest. word [rcx+0x58] = name Length, qword [rcx+0x60] = name Buffer. * 0x14001ABD7lea rcx, MupiMailSlotName before the mailslot-name RtlCompareUnicodeString. * 0x14001AC7Csub bx, si: computes Length = end - separator for String2. * 0x14001AC7Flea rax, [rsi+2]: String2.Buffer = separator + 2. * 0x14001ACB2call RtlCompareUnicodeString in the 6-entry prefix loop. * 0x14001ACE2call MupWriteTelemetryOtherRemoteMailslotFailed (which internally calls RtlHashUnicodeString then _tlgWriteTransfer_EtwWriteTransfer).

Patched (mup_patched.sys): * 0x14001BBB0 — entry of MupiCheckForMailslotRequest. * 0x14001BD4Dcall Feature_364057912__private_IsEnabledDeviceUsageNoInline (the WIL gate). * 0x14001BD56sub bx, si (feature-off path, original behavior). * 0x14001BD5Bsub bx, r14w where r14 = separator + 2 (feature-on path, corrected length).

Reference open (reaches the branch only under the policy conditions above):

HANDLE h = CreateFileW(
    L"\\\\server\\mailslot\\...",   // must match MupiMailSlotName as the leading component
    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

8. Changed Functions — Full Triage

  • MupiCheckForMailslotRequest (0x14001AAE00x14001BBB0): Not security-relevant. Adds a call to the WIL feature-staging gate Feature_364057912__private_IsEnabledDeviceUsageNoInline and, when it reports enabled, computes String2.Length as end - (separator + 2) instead of end - separator. The original computation is retained on the else branch and runs by default. The construct is confined to the failed-remote-mailslot telemetry path. Every other pre-existing function is byte-for-byte equivalent modulo relocation.

9. Unmatched Functions

  • Removed: None.
  • Added (13): Feature_364057912__private_IsEnabledDeviceUsageNoInline, Feature_364057912__private_IsEnabledFallback, wil_details_FeatureReporting_IncrementOpportunityInCache, wil_details_FeatureReporting_IncrementUsageInCache, 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_MapReportingKind, wil_RtlStagingConfig_QueryFeatureState. All are WIL feature-staging infrastructure supporting the gate introduced in MupiCheckForMailslotRequest.

10. Confidence & Caveats

  • Confidence Level: High. Both the assembly and the decompilation of both builds confirm the sole logic change is the WIL-feature-gated length computation, and an independent structural diff of all 345 shared functions shows no other function changed.
  • Direction: The corrected computation (feature enabled) is the stricter one; the default (feature disabled) preserves the original behavior. There is no regression.
  • CWE: If characterized at all, the length over-count is a read-side over-count (CWE-125 family). No write-side (CWE-787) behavior is present.
  • Bottom line: No delivered, reachable security fix is present. The change is WIL feature-staging churn plus a default-off, telemetry-confined 2-byte length adjustment.