mup.sys — IoStatus.Information length rounding in UNC hardening prefix-table enumeration
KB5073455
1. Overview
- Unpatched Binary:
mup_unpatched.sys - Patched Binary:
mup_patched.sys - Overall Similarity: 97.89%
- Diff Statistics: 315 matched functions, 313 identical by content. The one functional change is in
MupiPackUncHardeningPrefixTableForFsctl. Separately,DriverEntrydrops its WIL feature-staging init/uninit calls and the associatedwil_details_*helper routines are removed from the patched image (staging-library teardown, not security-relevant). - Verdict: The change in
MupiPackUncHardeningPrefixTableForFsctl(unpatched0x1C001AD4C, patched0x1C001AA1C) adjusts the running offset that is reported back asIRP->IoStatus.Informationon theSTATUS_BUFFER_OVERFLOWand integer-overflow exit paths of the UNC provider enumeration loop. Because the caller zero-fills the entire output buffer before population in both builds, and because the reported length can only under-report on the unpatched path, there is no information disclosure and no out-of-bounds access. This is a length-reporting correctness change with no demonstrable security impact.
2. Change Summary
- Severity: None (informational).
- Nature: Reported output-length (
IoStatus.Information) rounding/robustness on error exit paths. - Affected Function:
MupiPackUncHardeningPrefixTableForFsctl(unpatched0x1C001AD4C, patched0x1C001AA1C).
What the function does
MupiPackUncHardeningPrefixTableForFsctl walks the MUP Unicode prefix table of registered UNC providers (RtlNextUnicodePrefix over PrefixTable) and copies each provider's record into a METHOD_BUFFERED output buffer via the per-entry helper MupiPackUncHardeningPrefixTableEntryForFsctl (unpatched 0x1C001ACA4, patched 0x1C001A974). A running byte offset is kept in edi/rdi. On return, that offset is written to *arg4, which the caller stores into IRP->IoStatus.Information.
The actual code change
There are two behavioural differences between the builds, both on non-success exit paths:
-
STATUS_BUFFER_OVERFLOW(0x80000005) top-of-loop path. When the aligned start of the next provider record is>=the buffer size and at least one prior record was written, the function returnsSTATUS_BUFFER_OVERFLOW. The unpatched build jumps straight to the exit and returnsediunchanged (the end offset of the last written record, an unaligned value). The patched build first executesmov edi, esito report the 16-byte-aligned offset instead. -
(rdi+0xF)integer-overflow path. If adding the 16-byte alignment slack wraps the 32-bit offset, the function returnsSTATUS_INVALID_BUFFER_SIZE(0xC0000095). The unpatched build leavesedistale on this specific path; the patched build setsedi = 0xFFFFFFFFfirst.
Why this is not a security issue
- The output buffer is fully zeroed before population, in both builds. The caller
MupFsctlGetUncHardeningPrefixTableexecutesmemset(SystemBuffer, 0, OutputBufferLength)(unpatched0x1C0019AE9, patched0x1C00197B9) prior to calling the packer. No uninitialized pool data is ever present in the returned buffer, so no uninitialized-memory disclosure is possible regardless of the reported length. - The reported length can only under-report on the unpatched path, never over-read. On the
STATUS_BUFFER_OVERFLOWpath the unpatched value is the end offset of the last successfully written record, which is strictly< OutputBufferLength.METHOD_BUFFEREDcompletion copiesmin(IoStatus.Information, OutputBufferLength)bytes from theOutputBufferLength-sized system buffer, so there is no out-of-bounds read in either build. The patched value is the aligned offset, which is>=the buffer size on this branch and is clamped down by the I/O Manager toOutputBufferLength. - The integer-overflow path's return code is discarded by the caller. The caller only copies the offset into
IoStatus.Informationwhen the result is>= 0,== 0x80000005, or== 0xC0000023(unpatched0x1C0019AFD–0x1C0019B13). TheSTATUS_INVALID_BUFFER_SIZE(0xC0000095) result falls through thejnzand the length is not written, so the difference on that path is not observable through this caller.
Net effect: on the buffer-overflow path the value the user sees in bytesReturned differs by up to the 16-byte alignment padding between builds. No data is leaked and no memory is corrupted.
Reachability
- A user-mode process obtains a handle to
\Device\Mup(directly or via a UNC path). - It issues
FSCTL 0x100038(device0x10FILE_DEVICE_MULTI_UNC_PROVIDER, function0x0E,METHOD_BUFFERED) viaNtDeviceIoControlFile/DeviceIoControl. - The IRP reaches
MupFsControl(0x1C000DE40), whose FSCTL switch dispatchescase 0x100038toMupFsctlGetUncHardeningPrefixTable(unpatched0x1C0019ABC, patched0x1C001978C). - That routine zero-fills the system buffer, calls
MupiPackUncHardeningPrefixTableForFsctl, and writes the returned offset toIRP->IoStatus.Information(IRP+0x38).
3. Pseudocode Diff
// UNPATCHED MupiPackUncHardeningPrefixTableForFsctl @ 0x1C001AD4C
int pack(void* unused, uint32_t buf_size, void* sys_buf, uint32_t* out_length) {
uint32_t off = 0; // edi
while ( (entry = RtlNextUnicodePrefix(PrefixTable)) != NULL ) {
uint32_t aligned = (off + 0xF) & 0xFFFFFFF0;
if ((off + 0xF) < off) goto ovf; // wrap: exit WITHOUT setting off = -1
if (aligned >= buf_size) {
if (prior_success) { // STATUS_BUFFER_OVERFLOW: exit with off UNCHANGED
status = STATUS_BUFFER_OVERFLOW;
goto exit; // off still = end of last record (unaligned)
}
// else: measure-only path (null ptr, remaining = 0)
} else {
off = aligned; // committed before packing this record
// ... pack record, off += record_bytes ...
}
}
exit:
*out_length = off;
return status;
ovf:
status = STATUS_INVALID_BUFFER_SIZE; // off left stale
goto exit;
}
// PATCHED MupiPackUncHardeningPrefixTableForFsctl @ 0x1C001AA1C
int pack(void* unused, uint32_t buf_size, void* sys_buf, uint32_t* out_length) {
uint32_t off = 0; // edi
while ( (entry = RtlNextUnicodePrefix(PrefixTable)) != NULL ) {
uint32_t aligned = (off + 0xF) & 0xFFFFFFF0; // esi
if ((off + 0xF) < off) { off = 0xFFFFFFFF; goto ovf; } // set off = -1 on wrap
if (aligned >= buf_size) {
if (prior_success) {
status = STATUS_BUFFER_OVERFLOW;
off = aligned; // report aligned offset before exit
goto exit;
}
} else {
// uses rbp as the write pointer; off committed after packing
}
}
exit:
*out_length = off;
return status;
ovf:
status = STATUS_INVALID_BUFFER_SIZE;
goto exit;
}
4. Assembly Analysis
Unpatched exit paths (MupiPackUncHardeningPrefixTableForFsctl @ 0x1C001AD4C)
; top of loop
0x1C001ADD2 lea eax, [rdi+0Fh] ; aligned candidate = off + 15
0x1C001ADD5 cmp eax, edi ; wrap check
0x1C001ADD7 jb loc_1C001AE8F ; wrap -> STATUS_INVALID_BUFFER_SIZE (off NOT set to -1)
0x1C001ADDD and eax, 0FFFFFFF0h ; align to 16
0x1C001ADE0 cmp eax, r12d ; aligned vs buffer size
0x1C001ADE3 jb short loc_1C001ADF7 ; fits -> process
0x1C001ADE5 test bpl, bpl ; prior success?
0x1C001ADE8 jnz loc_1C001AE7E ; yes -> STATUS_BUFFER_OVERFLOW
; process path (eager commit of off)
0x1C001ADF7 mov esi, eax
0x1C001ADFC add rsi, [rsp+58h+arg_10]
0x1C001AE01 mov edi, eax ; off = aligned (committed here)
0x1C001AE03 sub edx, eax
; STATUS_BUFFER_OVERFLOW exit
0x1C001AE7E mov ebx, 80000005h
0x1C001AE83 jmp short loc_1C001AE94 ; off (edi) left unchanged from prior iteration
; integer-overflow exit
0x1C001AE8F mov ebx, 0C0000095h ; STATUS_INVALID_BUFFER_SIZE (off not forced to -1 on wrap-in path)
; final write
0x1C001AEC6 mov [r13+0], edi ; *arg4 = off
Patched exit paths (MupiPackUncHardeningPrefixTableForFsctl @ 0x1C001AA1C)
; top of loop
0x1C001AAA3 lea esi, [rdi+0Fh]
0x1C001AAA6 cmp esi, edi
0x1C001AAA8 jb loc_1C001AB59 ; wrap -> handler that forces off = -1
0x1C001AAAE and esi, 0FFFFFFF0h
0x1C001AAB1 cmp esi, r12d
0x1C001AAB4 jb short loc_1C001AAC8
0x1C001AAB6 test r15b, r15b
0x1C001AAB9 jnz loc_1C001AB4B ; STATUS_BUFFER_OVERFLOW
; process path (uses rbp for the write pointer, off deferred)
0x1C001AAC8 mov ebp, esi
0x1C001AACD add rbp, [rsp+58h+arg_10]
0x1C001AAD2 sub edx, esi
; STATUS_BUFFER_OVERFLOW exit
0x1C001AB4B mov ebx, 80000005h
0x1C001AB50 mov edi, esi ; off = aligned offset
0x1C001AB52 jmp short loc_1C001AB63
; integer-overflow exit
0x1C001AB59 mov edi, 0FFFFFFFFh ; off = -1
0x1C001AB5E mov ebx, 0C0000095h
; final write
0x1C001AB9A mov [rsi], edi ; *arg4 = off
5. Trigger Conditions
- Environment: the system has at least 2 registered UNC providers so more than one prefix-table record exists.
- Handle: open
\Device\Mupdirectly or via a UNC path; standard user privileges suffice for the FSCTL dispatch (case 0x100038inMupFsControlperforms noRequestorModegate). - Request:
DeviceIoControl/NtDeviceIoControlFilewithFSCTL 0x100038and anOutputBufferLengthlarge enough for the first record but too small for the aligned second record. - Effect: the function writes the first record, returns
STATUS_BUFFER_OVERFLOW(0x80000005), and reports a length that differs between builds by up to the 16-byte alignment padding (unpatched: end offset of the last record; patched: the aligned offset). - Observation:
bytesReturned(IoStatus.Information) is an unaligned value on the unpatched build and a 16-byte-aligned value on the patched build. The bytes themselves are identical (zero-filled tail in both builds).
6. Impact Assessment
- No information disclosure. The caller
memsets the entireOutputBufferLength-sized system buffer to zero before population in both builds (unpatched0x1C0019AE9, patched0x1C00197B9). Any bytes beyond the written records are zero, so nothing from adjacent pool memory is returned regardless of the reported length. - No out-of-bounds read.
METHOD_BUFFEREDcompletion copiesmin(IoStatus.Information, OutputBufferLength)bytes from the system buffer, which is exactlyOutputBufferLengthin size. The unpatched stale value on theSTATUS_BUFFER_OVERFLOWpath is< OutputBufferLength; the patched aligned value is clamped down toOutputBufferLength. Neither reads past the allocation. - No memory corruption. The affected register is only used as the reported byte count; it does not drive any write.
- Discarded path. The integer-overflow (
0xC0000095) length difference is never propagated, because the caller only writesIoStatus.Informationfor results>= 0,0x80000005, or0xC0000023.
The change is a defensively-tidied length-reporting path; it does not deliver a security fix.
7. Verification Notes
The following breakpoints let a researcher confirm the length-reporting difference on a live system (there is no exploitable primitive to observe, only the reported byte count).
bp mup_unpatched!MupiPackUncHardeningPrefixTableForFsctl
bp 0x1C001AE7E ; STATUS_BUFFER_OVERFLOW exit; edi holds the (unaligned) reported length
bp 0x1C001AEC6 ; mov [r13], edi -> *arg4, later stored into IRP->IoStatus.Information
- At
0x1C001AE7Ethe unpatchedediequals the end offset of the last written record (unaligned). - At the equivalent patched exit
0x1C001AB50(mov edi, esi),ediequals the 16-byte-aligned offset. - At
0x1C001AEC6(unpatched) /0x1C001AB9A(patched) the value is written to*arg4;MupFsctlGetUncHardeningPrefixTablestores it intoIRP->IoStatus.Information(IRP+0x38).
User-mode probe against the unpatched target:
HANDLE hMup = CreateFileW(L"\\\\.\\Mup", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
BYTE outBuffer[64] = {0};
DWORD bytesReturned = 0;
BOOL ok = DeviceIoControl(hMup, 0x100038, NULL, 0, outBuffer, sizeof(outBuffer), &bytesReturned, NULL);
// On the STATUS_BUFFER_OVERFLOW case, bytesReturned is unaligned (unpatched) vs 16-byte-aligned (patched).
// The returned bytes are identical (zero-filled tail) in both builds.
printf("ok=%d, bytesReturned=0x%x\n", ok, bytesReturned);
8. Changed Functions — Full Triage
DriverEntry(0x1C0015884)- Change Type: WIL feature-staging teardown + WPP relocation. Not security-relevant.
- Notes: The patched build removes the
wil_InitializeFeatureStagingandwil_UninitializeFeatureStagingcalls, and the associatedwil_details_*helper routines are dropped from the image. The remaining differences are WPP trace-GUID symbol references and address relocations from the image-layout shift. No change to MUP request handling. MupiPackUncHardeningPrefixTableForFsctl(unpatched0x1C001AD4C, patched0x1C001AA1C, similarity 0.9226)- Change Type: Length-reporting robustness. Not security-relevant.
- Notes: On the
STATUS_BUFFER_OVERFLOWand(rdi+0xF)integer-overflow exit paths, the patched build sets the reported offset (edi) to the aligned value /0xFFFFFFFFrather than leaving it stale. The output buffer is zero-filled by the caller in both builds and theMETHOD_BUFFEREDcopy is clamped toOutputBufferLength, so no disclosure or out-of-bounds access results from either behaviour. - Cosmetic-only changes (
MupGetIoStatus,MupPostAcquireForSectionSynchronisation,MupStateMachine,MupiCreateProviderSymbolicLink,MupiSetProviderFlags,RfsCskvLexerStreamAdvance,WppClassicProviderCallback, theWPP_SF_*tracing stubs,memset,memmove,__memset_spec_*) - Change Type: Cosmetic / relocation.
- Notes: Differences are limited to relocated branch targets, data-symbol references, and jump-table addresses caused by the image-layout shift. No semantic logic changes.
9. Added / Removed Functions
The patched image removes the WIL feature-staging helper routines that are no longer called after DriverEntry drops its staging init/uninit: wil_InitializeFeatureStaging, wil_UninitializeFeatureStaging, wil_details_BuildFeatureStateCacheFromQueryResults, wil_details_EvaluateFeatureDependencies, wil_details_EvaluateFeatureDependencies_GetCachedFeatureEnabledState, wil_details_EvaluateFeatureDependencies_ReevaluateCachedFeatureEnabledState, wil_details_FeatureDescriptors_SkipPadding, wil_details_PopulateInitialConfiguredFeatureStates, wil_details_ReevaluateOnFeatureConfigurationChange, wil_details_UpdateFeatureConfiguredStates. This is feature-staging-library teardown and is not security-relevant. No new functions were added.
10. Confidence & Caveats
- Confidence: High. Both build-differences in
MupiPackUncHardeningPrefixTableForFsctlare present at the stated addresses. The caller'smemsetof the full output buffer, themin(Information, OutputBufferLength)METHOD_BUFFEREDclamp, and the caller's restriction of which result codes propagateIoStatus.Informationare all confirmed in both builds and together eliminate any disclosure or out-of-bounds path. - Conclusion: The patch alters the reported
IoStatus.Informationbyte count on error exit paths of the UNC provider enumeration. It is a correctness/robustness adjustment with no demonstrable security impact; it is not an information-disclosure or memory-safety fix.