irenum.sys — redundant zero-init of registry-path pool buffer
KB5078752
1. Overview
- Unpatched Binary ID:
irenum_unpatched.sys - Patched Binary ID:
irenum_patched.sys - Overall Similarity Score: 0.9927
- Diff Statistics: 36 matched functions, 1 changed function, 35 identical functions, 0 unmatched functions (both directions).
- Verdict: The single code change adds a
memsetthat zero-initializes the driver's registry-path pool buffer immediately after allocation. In the unpatched build the same buffer is already fully written by the subsequent copy plus wide null-terminator in the normal (even-length) path, and the buffer is never exposed to user mode. The change is redundant defensive hardening, not a delivered security fix. No reachable information-disclosure vulnerability is demonstrable.
2. Change Summary
- Severity: None (informational).
- Nature of Change: Redundant zero-initialization of a freshly allocated pool buffer (defense-in-depth hardening).
- Affected Function:
DriverEntry@0x1C0008008.
What the code does:
DriverEntry (0x1C0008008) receives the driver's RegistryPath (a UNICODE_STRING*) in rdx. It reads RegistryPath->Length (a byte count), rejects lengths >= 0xFFFD, computes NumberOfBytes = Length + 2, and allocates that many bytes of PagedPool via ExAllocatePoolWithTag (tag 'IrEn' = 0x6E457249). The pointer is stored in global P (0x1C0003028) and the size in global word_1C0003022. It then copies Length bytes of the registry path into the buffer with memmove and writes a wide (2-byte) null terminator.
Why this is not a reachable information leak:
The allocation is Length + 2 bytes. The memmove writes bytes [0, Length). The wide null-terminator write mov [rdi+rax*2], r14w (with rax = Length >> 1, r14w = 0) stores a 2-byte zero at offset 2 * (Length / 2). For any even Length this offset equals Length, so bytes [Length, Length+2) are zeroed and the copy plus terminator together cover the entire Length + 2 allocation. RegistryPath->Length is the byte length of a well-formed UNICODE_STRING supplied by the OS to DriverEntry; it is always even and is not attacker-controllable to an odd value. Consequently, in the unpatched build the buffer contains no uninitialized bytes in the path that actually runs.
Furthermore, the buffer is used only as the null-terminated absolute registry path argument to RtlQueryRegistryValues (to read the BreakOnEntry, DebugFlags, and EnumStaticDevice values under the service key). It is not returned through any dispatch/IOCTL path and is not copied to user mode. DriverEntry runs once at load, in system-thread context at PASSIVE_LEVEL, and is not reachable from an untrusted request surface.
The patched build's added memset zeroes the full NumberOfBytes before the copy. Because the copy plus terminator already initialize the whole buffer for even lengths, the memset is redundant. It is a defensive hardening pattern (zero-on-allocate), not a fix for a demonstrable defect.
3. Pseudocode Diff
// UNPATCHED DriverEntry (@ 0x1C0008008):
uint16_t Length = RegistryPath->Length; // byte count, always even
if (Length < 0xFFFD) {
DriverEntryRegPath = Length; // global 0x1C0003020
word_1C0003022 = Length + 2; // global NumberOfBytes
P = ExAllocatePoolWithTag(PagedPool, Length + 2, 0x6E457249); // 'IrEn'
}
if (Length >= 0xFFFD || !P)
return 0xC000009A; // STATUS_UNRECOGNIZED_VOLUME
else {
memmove(P, RegistryPath->Buffer, Length); // copies Length bytes
*((_WORD *)P + (Length >> 1)) = 0; // wide null-term at offset 2*(Length/2)
// for even Length, [0,Length) + [Length,Length+2) == full allocation
// ... memset(&QueryTable, 0, 0xE0); RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, P, ...)
}
// PATCHED DriverEntry (@ 0x1C0008008):
else {
memset(P, 0, (uint16_t)word_1C0003022); // ADDED: zero full NumberOfBytes (redundant)
memmove(P, RegistryPath->Buffer, Length);
*((_WORD *)P + (Length >> 1)) = 0;
// ... memset(&QueryTable, 0, 0xE0); RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, P, ...)
}
4. Assembly Analysis
Unpatched sequence (0x1C0008084–0x1C00080BF):
00000001C0008084 mov cs:P, rax ; global P = Path
00000001C000808B mov rdi, rax ; rdi = Path
00000001C000808E test rax, rax
00000001C0008091 jz loc_1C0008231 ; null check
00000001C0008097 movzx r8d, word ptr [rsi] ; Size = RegistryPath->Length
00000001C000809B mov rcx, rax ; dst = Path
00000001C000809E mov rdx, [rsi+8] ; src = RegistryPath->Buffer
00000001C00080A2 call memmove ; copy Length bytes
00000001C00080A7 movzx eax, word ptr [rsi] ; eax = Length
00000001C00080AF shr rax, 1 ; rax = Length/2
00000001C00080BA mov [rdi+rax*2], r14w ; Path[2*(Length/2)] = 0 (wide null-term)
00000001C00080BF call memset ; memset(&QueryTable, 0, 0xE0) -- zeroes the local RTL_QUERY_REGISTRY_TABLE, not Path
Patched sequence (0x1C0008084–0x1C00080D1):
00000001C0008084 mov cs:P, rax ; global P = Path
00000001C000808B mov rdi, rax ; rdi = Path
00000001C000808E test rax, rax
00000001C0008091 jz loc_1C0008243 ; null check
00000001C0008097 movzx r8d, cs:word_1C0003022 ; Size = NumberOfBytes (Length+2)
00000001C000809F xor edx, edx ; Val = 0
00000001C00080A1 mov rcx, rax ; dst = Path
00000001C00080A4 call memset ; memset(Path, 0, Length+2) -- ADDED
00000001C00080A9 movzx r8d, word ptr [rsi] ; Size = RegistryPath->Length
00000001C00080AD mov rcx, rdi ; dst = Path
00000001C00080B0 mov rdx, [rsi+8] ; src = RegistryPath->Buffer
00000001C00080B4 call memmove ; copy Length bytes
00000001C00080B9 movzx eax, word ptr [rsi] ; eax = Length
00000001C00080C1 shr rax, 1 ; rax = Length/2
00000001C00080CC mov [rdi+rax*2], r14w ; Path[2*(Length/2)] = 0 (wide null-term)
00000001C00080D1 call memset ; memset(&QueryTable, 0, 0xE0)
The only functional difference is the four instructions at 0x1C0008097–0x1C00080A4 that add memset(Path, 0, NumberOfBytes) before the copy. rdi already held Path in both builds; it is reused as the copy destination after the added memset, so no register-preservation change was required by the copy itself.
5. Reachability Assessment
DriverEntry(0x1C0008008) is the driver initialization routine. It is invoked once, at driver load, by the entry stubGsDriverEntry(0x1C0008260), which first calls__security_init_cookieand thenDriverEntry.- The registry-path buffer at global
P(0x1C0003028) is consumed only byRtlQueryRegistryValuesas a null-terminated absolute registry path. No dispatch routine, IOCTL handler, or WMI/PnP path in either build reads back or returns this buffer to user mode. - The routine runs in system-thread context at
PASSIVE_LEVELduring load. It is not reachable from an untrusted caller. - There is no attacker-controlled input on this path:
RegistryPathis supplied by the OS and itsLengthis a valid evenUNICODE_STRINGbyte count.
6. Impact Assessment
- No exploit primitive is demonstrable. In the normal (even-length) path the unpatched buffer is fully initialized by the copy plus the wide null-terminator, so no uninitialized pool bytes exist to disclose. Even absent that, the buffer is never surfaced to user mode.
- The added
memsetis a standard zero-on-allocate hardening pattern with no reachable behavioral effect for the inputs that actually occur.
7. Evidence Notes
word_1C0003022(0x1C0003022) holdsNumberOfBytes = Length + 2, set at0x1C0008064–0x1C0008071; the addedmemsetreads it as its size argument at0x1C0008097.- The pre-existing
memsetat the unpatched0x1C00080BF/ patched0x1C00080D1zeroes the localRTL_QUERY_REGISTRY_TABLEstructure (size0xE0), not the path buffer, in both builds. - The copy routine at
0x1C0001B00ismemmove; the fill routine at0x1C0001E40ismemset. - Image base is
0x1C0000000.
8. Changed Functions — Full Triage
DriverEntry(0x1C0008008, Similarity: 0.9798, Change Type:defensive_hardening)- Behavioral Change: Added
memset(Path, 0, NumberOfBytes)(0x1C00080A4) to zero the full registry-path allocation before the copy. - Security Relevance: None. The buffer is already fully written by the copy plus wide null-terminator for the even lengths that actually occur, and it is never exposed to user mode. The change is redundant zero-init.
9. Unmatched Functions
- Removed: None.
- Added: None.
10. Independent Diff Confirmation
An independent content-level diff of both builds (matching by content across relocations, not by reused address) confirms DriverEntry (0x1C0008008) is the only function that changed. The remaining functions are byte-identical after accounting for the address shift caused by the four inserted instructions. The entry stub GsDriverEntry moved from 0x1C0008260 to 0x1C0008280 purely as a result of that shift; its body is unchanged. No omitted security-relevant change was found.
11. Confidence & Caveats
- Confidence: High that the sole change is the added
memsetand that it is redundant defensive hardening rather than a delivered security fix. - Basis: For an even
RegistryPath->Lengththe unpatched copy plus wide null-terminator write cover the entireLength + 2allocation, leaving no uninitialized bytes; the buffer is not exposed to user mode; and the routine is init-only and not attacker-reachable.