msisadrv.sys — KMDF static-stub rebuild: init-time bounds-check hardening plus stack-init/codegen refactors
KB5094128
1. Overview
- Unpatched Binary:
msisadrv_unpatched.sys - Patched Binary:
msisadrv_patched.sys - Overall Similarity Score: 0.7282
- Diff Statistics:
- Matched Functions: 18 (9 identical, 9 changed)
- Unmatched Functions: 0 added, 0 removed (one helper,
FxGetNextClassBindInfo, was split out of existing stub code) - Verdict: The delta is a Kernel-Mode Driver Framework (KMDF) static-stub library rebuild. The linked-in WDF loader stub functions (
FxStubBindClasses,FxStubUnbindClasses,FxStubInitTypes, and a new shared helperFxGetNextClassBindInfo) gained tighter bounds checking andDbgPrintExtelemetry. These stubs run only during driver load, iterating a compile-time, linker-bracketed metadata table inside the driver's own image; they are not reachable from any untrusted (user-mode IOCTL or remote) surface. The remaining changed functions are stack-initialization and code-generation refactors. No attacker-reachable vulnerability is fixed or introduced.
2. Vulnerability Summary
Finding 1: Init-time out-of-bounds read hardening in FxStubInitTypes (statically-linked KMDF stub)
- Severity: Informational (not attacker-reachable)
- Vulnerability Class: Out-of-bounds read of a callback pointer / missing full-entry bounds check (CWE-125)
- Affected Function:
FxStubInitTypes(unpatched0x1C0001334, patched0x1C000147C) - Root Cause: The unpatched loop iterates a table of 0x28-byte object-type-init records bracketed by the linker markers
__KMDF_TYPE_INIT_START/__KMDF_TYPE_INIT_END. The loop's continuation test compares only the start of the current entry against the end marker (cmp rbx, rdi), then reads a function pointer fromentry+0x20, calls it through the Control Flow Guard dispatch, and stores the result atentry+0x18. It does not verify that the full 0x28-byte record fits below the end marker before those accesses. The patched build adds an explicit full-entry check (lea rax,[rcx+0x28]; cmp rax,rdi; ja fail), a zero-entry skip loop, and a NULL check, then emitsDbgPrintExon failure. - Reachability: This code executes during driver load only. The table it walks is generated at compile time by the KMDF headers and delimited by linker-placed markers inside the driver image; there is no attacker-controlled input on this path. The added check is defensive hardening in the WDF static stub that ships linked into KMDF drivers of this build, not a fix for an attacker-reachable condition in msisadrv. Note that the sibling stub
FxStubBindClassesalready performed a full-entry check in the unpatched build; the patch generalizes the pattern across the stub family.
Finding 2: Stack-slot zero-initialization / codegen refactor in MsIsaEvtDeviceProcessQueryInterface
- Severity: Informational (no reachable impact)
- Vulnerability Class: Uninitialized stack variable (CWE-908), defense-in-depth only
- Affected Function:
MsIsaEvtDeviceProcessQueryInterface(unpatched0x1C00062A0, patched0x1C00062B0) - Root Cause: This is the driver's WDF query-interface callback for
GUID_TRANSLATOR_INTERFACE_STANDARD(registered via the interface-descriptor set up inMsIsaEvtDeviceAdd). It queries two device properties (property IDs 0xd and 0xe) through the WDF function table (WdfFunctions_01015+0x288) and then callsHalGetInterruptTranslator(HalDispatchTable+0x78). In the unpatched build the stack slot[rsp+0x44]used as the query's outputResultLengthparameter is not zeroed before the first call. The patched build zeroes it (mov [rsp+0x44], esiwithesi=0) and reorders which slot serves as buffer vs.ResultLength. - Reachability / impact:
ResultLengthis an output-only parameter; the property-query function writes to it. The uninitialized value is never read back by this function (only the property buffer slot, which is zero-initialized in both builds, is forwarded to the HAL call). There is therefore no information disclosure. This is a defense-in-depth stack-hygiene change bundled with register-allocation/codegen differences.
3. Pseudocode Diff
FxStubInitTypes (Finding 1)
// UNPATCHED FxStubInitTypes @ 0x1C0001334
if ( &__KMDF_TYPE_INIT_START > (_UNKNOWN *)__KMDF_TYPE_INIT_END )
return 3221225595LL; // 0xC000007B
for ( i = __KMDF_TYPE_INIT_END; ; i += 40 )
{
if ( i >= __KMDF_TYPE_INIT_END ) // only checks START of entry vs end marker
return 0;
if ( *(_DWORD *)i != 40 )
break;
v2 = *((__int64 (**)(void))i + 4); // read fn ptr at i+0x20 (no full-entry check)
if ( v2 != nullptr )
*((_QWORD *)i + 3) = v2(); // call via CFG, store result at i+0x18
}
return 3221225476LL; // 0xC0000004
// PATCHED FxStubInitTypes @ 0x1C000147C
for ( i = __KMDF_TYPE_INIT_END; ; i = (_QWORD *)((char *)v3 + *v3) )
{
for ( j = i + 1; j <= __KMDF_TYPE_INIT_END && *i == 0; ++j ) // NEW: skip empty 8-byte slots
i = j;
if ( i >= __KMDF_TYPE_INIT_END )
break;
if ( i + 5 > __KMDF_TYPE_INIT_END // NEW: full 0x28-byte entry must fit
|| *(_DWORD *)i != 40
|| (v3 = (unsigned int *)i, i == nullptr) ) // NEW: NULL check
{
DbgPrintEx(0x4Du, 0, "FxGetNextObjectContextTypeInfo failed\n");
return 3221225595LL;
}
v4 = (__int64 (*)(void))i[4];
if ( v4 != nullptr )
i[3] = v4();
}
return 0;
MsIsaEvtDeviceProcessQueryInterface (Finding 2)
The decompiled bodies are equivalent; the difference is stack-slot initialization and which slot is the property buffer vs. the ResultLength output. Both builds zero the property buffer; the patched build also zeroes the ResultLength slot.
// Both builds (identical decompiled logic):
if ( a4 != 2 || *a3 < 0x30u ) return 3221225659LL; // 0xC00000BB
v7 = *a2 - 0x11D0AACF6C154A92LL; // GUID_TRANSLATOR_INTERFACE_STANDARD.Data1..
if ( *a2 == 0x11D0AACF6C154A92LL ) v7 = a2[1] - 0x44B206C9A0002A8DLL;
if ( v7 != 0 ) return 3221225659LL;
// query device property 0xd (buffer + ResultLength), then property 0xe ...
// then HalGetInterruptTranslator(HalDispatchTable+0x78)(...)
4. Assembly Analysis
FxStubInitTypes (Finding 1)
Unpatched: the loop enters the body when only the entry start is below the end marker, then reads [rbx+0x20] and writes [rbx+0x18].
; UNPATCHED FxStubInitTypes @ 0x1C0001334
0x1C000133E lea rax, __KMDF_TYPE_INIT_START
0x1C0001345 lea rdi, __KMDF_TYPE_INIT_END ; end marker
0x1C000134C cmp rax, rdi
0x1C000134F jbe 0x1C0001358
0x1C0001358 lea rbx, __KMDF_TYPE_INIT_END
0x1C000135F jmp 0x1C000137D
0x1C0001361 cmp dword [rbx], 0x28 ; entry size field
0x1C0001364 jnz 0x1C000138F
0x1C0001366 mov rax, [rbx+0x20] ; read callback pointer (no full-entry check)
0x1C000136A test rax, rax
0x1C000136D jz 0x1C0001379
0x1C000136F call cs:__guard_dispatch_icall_fptr ; CFG-guarded indirect call
0x1C0001375 mov [rbx+0x18], rax ; store result
0x1C0001379 add rbx, 0x28 ; advance by fixed stride
0x1C000137D cmp rbx, rdi ; only checks entry START vs end
0x1C0001380 jb 0x1C0001361
0x1C0001382 xor eax, eax ; return 0
Patched: the new checks precede any field access.
; PATCHED FxStubInitTypes @ 0x1C000147C
0x1C00014D2 cmp qword [rcx], 0 ; zero-entry skip loop
0x1C00014DF cmp rax, rdi
0x1C00014E2 jbe 0x1C00014D2
0x1C00014E4 cmp rcx, rdi
0x1C00014E7 jnb 0x1C0001533 ; reached end -> return 0
0x1C00014E9 lea rax, [rcx+0x28] ; NEW: full-entry extent
0x1C00014ED cmp rax, rdi
0x1C00014F0 ja 0x1C0001519 ; NEW: entry must fully fit -> fail
0x1C00014F2 cmp dword [rcx], 0x28
0x1C00014F5 jnz 0x1C0001519
0x1C00014F7 mov rbx, rcx
0x1C00014FA test rcx, rcx
0x1C00014FD jz 0x1C0001519 ; NEW: NULL check
0x1C00014FF mov rax, [rcx+0x20]
0x1C0001508 call cs:__guard_dispatch_icall_fptr
0x1C000150E mov [rbx+0x18], rax
MsIsaEvtDeviceProcessQueryInterface (Finding 2)
The patched build introduces a zero register (esi) and initializes both the property buffer slot and the ResultLength slot; it also swaps which named slot is buffer vs. ResultLength.
; UNPATCHED @ 0x1C00062A0 (first property query, ID 0xd)
0x1C00062E9 mov [rsp+0x40], eax ; buffer slot = GUID-compare residual (0)
0x1C00062ED lea rcx, [rsp+0x44] ; &ResultLength slot (not initialized)
0x1C00062FF mov [rsp+0x28], rcx ; arg6 = ResultLength = &[rsp+0x44]
0x1C0006307 lea rcx, [rsp+0x40] ; &buffer slot
0x1C000630C mov [rsp+0x20], rcx ; arg5 = Buffer = &[rsp+0x40]
0x1C0006311 mov rax, [rax+0x288] ; WdfFunctions_01015+0x288 (device-property query)
0x1C0006318 lea r8d, [r9+9] ; PropertyId = 0xd
0x1C0006323 call cs:__guard_dispatch_icall_fptr
...
0x1C00063AE mov ecx, [rsp+0x40] ; forward property buffer to HAL call
0x1C00063A0 mov rax, [rax+0x78] ; HalDispatchTable+0x78 = HalGetInterruptTranslator
0x1C00063B2 call cs:__guard_dispatch_icall_fptr
; PATCHED @ 0x1C00062B0
0x1C00062BF xor esi, esi
0x1C00062C4 mov [rsp+0x40], esi ; slot zeroed
0x1C0006311 lea rcx, [rsp+0x40]
0x1C0006316 mov [rsp+0x28], rcx ; arg6 = ResultLength = &[rsp+0x40]
0x1C0006321 lea rcx, [rsp+0x44]
0x1C0006326 mov [rsp+0x44], esi ; NEW: ResultLength/buffer slot zeroed
0x1C000632A mov [rsp+0x20], rcx ; arg5 = Buffer = &[rsp+0x44]
0x1C0006344 call cs:__guard_dispatch_icall_fptr
...
0x1C00063CB mov ecx, [rsp+0x44] ; forward property buffer to HAL call
5. Trigger Conditions
Finding 1 (FxStubInitTypes)
- Executes automatically during driver load (
DriverEntry->FxDriverEntryWorker->FxStubInitTypes). - The iterated table is compile-time KMDF object-type-init metadata delimited by the linker markers
__KMDF_TYPE_INIT_START/__KMDF_TYPE_INIT_ENDinside the driver image. - There is no attacker-controlled input on this path. The unpatched missing full-entry check could only misbehave on a malformed driver image, which the toolchain that produced the binary does not emit. Not reachable from an untrusted surface.
Finding 2 (MsIsaEvtDeviceProcessQueryInterface)
- This is a WDF query-interface callback, not an IOCTL dispatch routine. It is invoked when another kernel component queries the device for
GUID_TRANSLATOR_INTERFACE_STANDARDduring PnP interface negotiation. - The callback validates the interface size (
*a3 >= 0x30), the interface-type parameter (a4 == 2), and the requested GUID againstGUID_TRANSLATOR_INTERFACE_STANDARD. - The only difference between builds is stack-slot zero-initialization plus codegen; there is no user-mode-reachable behavioral change.
6. Exploit Primitive & Development Notes
- Finding 1: No reachable primitive. The added bounds check hardens init-time stub code that walks the driver's own compile-time metadata; there is no attacker input and no observed control-flow or memory-corruption primitive reachable at runtime.
- Finding 2: No reachable primitive. The
ResultLengthslot is output-only and its value is never consumed; the forwarded property buffer is zero-initialized in both builds.HalDispatchTable+0x78resolves toHalGetInterruptTranslatorand is invoked identically in both builds. - Mitigations present: Indirect calls in both functions go through the Control Flow Guard dispatch (
__guard_dispatch_icall_fptr).
7. Debugger PoC Playbook
There is no attacker-reachable trigger for either change; the notes below are for observing the code paths in a kernel debugger (WinDbg/KD), not a user-mode exploit.
Finding 1 (FxStubInitTypes)
- Breakpoint:
bp msisadrv!FxStubInitTypes(unpatched entry0x1C0001334). - What to inspect:
rax/rdihold the__KMDF_TYPE_INIT_START/__KMDF_TYPE_INIT_ENDmarkers; watchrbxadvance by 0x28. The unpatched check at0x1C000137D(cmp rbx, rdi) validates only the entry start. - Key offsets:
0x1C0001366(read[rbx+0x20]),0x1C000136F(CFG-guarded call),0x1C0001375(write[rbx+0x18]). - Setup: Runs automatically at driver load; the table is fixed compile-time metadata, so under a normally built image the loop terminates cleanly at the end marker.
Finding 2 (MsIsaEvtDeviceProcessQueryInterface)
- Breakpoint:
bp msisadrv!MsIsaEvtDeviceProcessQueryInterface(unpatched entry0x1C00062A0). - What to inspect:
r9dmust be 2 andword [r8]must be>= 0x30to pass the guards;[rdx]/[rdx+8]must equalGUID_TRANSLATOR_INTERFACE_STANDARD. Observe[rsp+0x44]before the first property query (uninitialized in unpatched, zeroed in patched). - Key offsets:
0x1C00062ED(&ResultLengthslot loaded, unpatched, not initialized),0x1C0006323(property query),0x1C00063B2(callHalGetInterruptTranslator). - Setup: Reached only via an in-kernel PnP query for the translator interface, not from user mode.
8. Changed Functions — Full Triage
FxStubInitTypes(0x1C0001334->0x1C000147C): Added full-entry bounds check, zero-entry skip, NULL check, andDbgPrintExtelemetry. Init-time KMDF stub, not attacker-reachable. See Finding 1.FxStubBindClasses(0x1C00011F4->0x1C0001224): Refactored to call the newFxGetNextClassBindInfohelper and addedDbgPrintExtelemetry. The unpatched build already performed both a start (entry+4) and full-entry (entry+0x50) bounds check, so this is a structural refactor plus zero-entry skipping, not a newly added bounds check. Init-time stub, not attacker-reachable.FxStubUnbindClasses(0x1C00012B0->0x1C0001384): Same refactor intoFxGetNextClassBindInfoplus telemetry. Teardown-time stub, not attacker-reachable.FxGetNextClassBindInfo(new,0x1C000143C): Shared helper split out of the stub functions; skips empty slots and validates that an entry fully fits within the table.FxDriverEntryWorker(0x1C00010C4): AddedDbgPrintExon theDriverEntryfailure path; condition restructured to accommodate it. Telemetry only.memset(0x1C0001400): Code-generation difference. Cosmetic.MsIsaEvtDeviceAdd(0x1C0006010): More thorough stack initialization of the WDF I/O queue / interface descriptor structures using a zero register. Defense-in-depth stack hygiene, no behavioral security change.MsIsaEvtDeviceProcessQueryInterface(0x1C00062A0->0x1C00062B0): Zero-initializes theResultLengthstack slot and reorders buffer/ResultLengthslots; register-allocation/codegen churn. See Finding 2.MsIsaPnPIrpPreProcessingCallback(0x1C00063D0->0x1C00063F0): Reordered the bus-info structure field writes and theIoStatus.Statusassignment; net field values are equivalent. Cosmetic.
9. Unmatched Functions
No functions were added or removed as independent units. The new FxGetNextClassBindInfo helper was factored out of the existing stub functions during the KMDF static-stub rebuild.
10. Confidence & Caveats
- Confidence Level: High. Every statement above is anchored to the disassembly of the named functions at the stated addresses in both builds.
- Nature of the patch: A KMDF static-stub library rebuild (bounds-check generalization and
DbgPrintExtelemetry inFxStub*loader code) plus stack-initialization and code-generation refactors. TheFxStub*code is linked into KMDF drivers of this build and runs only at load/teardown over compile-time, linker-bracketed metadata. - Reachability: None of the changed code is reachable from a user-mode IOCTL or other untrusted surface.
MsIsaEvtDeviceProcessQueryInterfaceis a PnP interface-query callback, not an IOCTL handler, and its changed slot is an output-only value that is never read back. No exploitable primitive is present in either build.