mup.sys — WIL feature-staged UNICODE_STRING length adjustment in the mailslot-request telemetry path
KB5079473
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 aUNICODE_STRING.Lengthasend - (separator + 2)instead ofend - separator, trimming a 2-byte (one wide char) over-count. The old computation is retained on theelsebranch 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(0x14001AAE0unpatched,0x14001BBB0patched)
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) → MupCreate → MupiCheckForMailslotRequest, 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_IsEnabledFallback → wil_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:
- Issue an
IRP_MJ_CREATE(CreateFile/NtCreateFile) with no related (relative) file object, soMupCreatecallsMupiCheckForMailslotRequest. - The create name must match
MupiMailSlotNameunderRtlCompareUnicodeString(the leading component is compared against the mailslot name); otherwise the function returns early. - Mailslots must be disabled by policy:
MupEnableMailslotsByPolicy(or, when it equals 2,MupEnableMailslots) must be0, selecting the diagnostic/telemetry branch. - The second
MupFindMatchingCharresult (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 - separatorcomputation runs by default in the patched build. - Data flow of the over-counted length: The length is used only by
RtlCompareUnicodeStringagainst a fixed 6-entry prefix table and byRtlHashUnicodeStringinsideMupWriteTelemetryOtherRemoteMailslotFailed, 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.
* 0x14001ABD7 — lea rcx, MupiMailSlotName before the mailslot-name RtlCompareUnicodeString.
* 0x14001AC7C — sub bx, si: computes Length = end - separator for String2.
* 0x14001AC7F — lea rax, [rsi+2]: String2.Buffer = separator + 2.
* 0x14001ACB2 — call RtlCompareUnicodeString in the 6-entry prefix loop.
* 0x14001ACE2 — call MupWriteTelemetryOtherRemoteMailslotFailed (which internally calls RtlHashUnicodeString then _tlgWriteTransfer_EtwWriteTransfer).
Patched (mup_patched.sys):
* 0x14001BBB0 — entry of MupiCheckForMailslotRequest.
* 0x14001BD4D — call Feature_364057912__private_IsEnabledDeviceUsageNoInline (the WIL gate).
* 0x14001BD56 — sub bx, si (feature-off path, original behavior).
* 0x14001BD5B — sub 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(0x14001AAE0→0x14001BBB0): Not security-relevant. Adds a call to the WIL feature-staging gateFeature_364057912__private_IsEnabledDeviceUsageNoInlineand, when it reports enabled, computesString2.Lengthasend - (separator + 2)instead ofend - separator. The original computation is retained on theelsebranch 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 inMupiCheckForMailslotRequest.
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.