1. Overview

  • Unpatched Binary: modem_unpatched.sys
  • Patched Binary: modem_patched.sys
  • Overall Similarity Score: 0.9927
  • Diff Statistics: 99 matched functions, 1 changed function, 98 identical functions, 0 unmatched functions in either direction.
  • Verdict: The patch adds a memset that zero-initializes the second kernel pool buffer allocated in ModemHandleSymbolicLink during device-interface setup, matching the zeroing already applied to the first buffer. This is a CWE-908 (Use of Uninitialized Resource) hardening change. There is no demonstrable information disclosure: the uninitialized bytes are never read out, so the impact is defense-in-depth only. Severity: Low.

2. Vulnerability Summary

  • Severity: Low (defense-in-depth hardening; no demonstrable disclosure)
  • Vulnerability Class: Use of Uninitialized Resource (CWE-908)
  • Affected Function: ModemHandleSymbolicLink @ 0x1C00076CC

Root Cause & What Changed: ModemHandleSymbolicLink builds a \DosDevices\ symbolic link and registers the modem device interface. When invoked in create mode (a3 != 0), it allocates two 0x202-byte PagedPool buffers with tag 'UMDM': 1. Destination.Buffer for the \DosDevices\<FriendlyName> link name. This buffer IS zeroed with memset(buffer, 0, MaximumLength) immediately after allocation in both builds (at 0x1C0007736). 2. DeviceName.Buffer for the Physical Device Object name (queried with DevicePropertyPhysicalDeviceObjectName). In the unpatched build this second buffer is not zeroed before use; in the patched build a memset(buffer, 0, 0x202) is inserted immediately after the allocation (at 0x1C00077E10x1C00077EE).

IoGetDeviceProperty fills only the actual PDO-name bytes plus the terminator; the tail of the 0x202-byte buffer retains stale pool contents in the unpatched build. However, the stale tail is never exposed: DeviceName.Length is set to ResultLength - 2, IoCreateSymbolicLink consumes only Length bytes as the link target, and the buffer is freed with ExFreePoolWithTag immediately afterward. No code reads the bytes beyond Length, and nothing copies them to a caller-observable location. The patch is therefore a hardening measure (zero-before-use, consistent with the sibling buffer), not the closure of a demonstrable leak.

Reachability & Data Flow: 1. DriverEntry (0x1C000D008) sets DriverObject->DriverExtension->AddDevice = ModemAddDevice (0x1C0006B60). 2. The PnP Manager invokes ModemAddDevice when a modem device is enumerated. ModemAddDevice (and the PnP dispatch routine ModemPnP at 0x1C0006EF0) call ModemHandleSymbolicLink(PhysicalDeviceObject, DeviceExtension+0x48, create). 3. In the create path, ModemHandleSymbolicLink allocates the 0x202-byte DeviceName.Buffer. 4. The buffer is passed as the PropertyBuffer to IoGetDeviceProperty(DevicePropertyPhysicalDeviceObjectName), which partially fills it. 5. IoCreateSymbolicLink(&Destination, &DeviceName) uses only DeviceName.Length bytes; the buffer is then freed.

This code runs only during PnP device setup/enumeration. It is not reachable from an untrusted IOCTL request surface.

3. Pseudocode Diff

// ModemHandleSymbolicLink, create path (a3 != 0)
// ... first buffer (Destination) allocated and memset(...,0,MaximumLength) in BOTH builds ...

// Second buffer for the PDO name
v9 = (WCHAR *)ExAllocatePoolWithTag(PagedPool, 0x202u, 0x4D444D55u); // 'UMDM'
DeviceName.Buffer = v9;
if ( v9 == nullptr ) { DeviceProperty = -1073741670; goto LABEL_14; }

// --- PATCHED VERSION ADDS: ---
memset(v9, 0, DeviceName.MaximumLength + 2LL);   // zero entire 0x202-byte allocation

v10 = IoGetDeviceProperty(
        PhysicalDeviceObject,
        DevicePropertyPhysicalDeviceObjectName,
        DeviceName.MaximumLength,          // 0x200
        DeviceName.Buffer,                 // (unpatched: uninitialized; patched: zeroed)
        &ResultLength);
if ( v10 >= 0 )
{
    DeviceName.Length += ResultLength - 2;         // only valid PDO-name length is used
    IoCreateSymbolicLink(&Destination, &DeviceName);  // consumes DeviceName.Length bytes only
    ExFreePoolWithTag(DeviceName.Buffer, 0);          // buffer freed; tail never read
    ...
}

The only source-level change between the two builds is the added memset(v9, 0, DeviceName.MaximumLength + 2LL); and the corresponding decompiler variable rename (v9DeviceName.Buffer).

4. Assembly Analysis

Unpatched — second allocation, no zeroing before IoGetDeviceProperty:

00000001C00077A9  mov     edx, 202h                ; NumberOfBytes
00000001C00077AE  mov     dword ptr [rbp+DeviceName.Length], 2000000h  ; Length=0, MaximumLength=0x200
00000001C00077B5  mov     ecx, 1                   ; PoolType = PagedPool
00000001C00077BA  mov     r8d, 4D444D55h           ; Tag 'UMDM'
00000001C00077C0  call    cs:__imp_ExAllocatePoolWithTag
00000001C00077CC  mov     [rbp+DeviceName.Buffer], rax
00000001C00077D0  test    rax, rax
00000001C00077D3  jnz     short loc_1C00077DF
00000001C00077D5  mov     edi, 0C000009Ah          ; STATUS_INSUFFICIENT_RESOURCES
00000001C00077DA  jmp     loc_1C00078AF
; --- no memset here: buffer tail stays uninitialized ---
00000001C00077DF  movzx   r8d, [rbp+DeviceName.MaximumLength]  ; BufferLength = 0x200
00000001C00077E4  lea     rcx, [rbp+arg_18]        ; &ResultLength
00000001C00077E8  mov     [rsp+50h+ResultLength], rcx
00000001C00077ED  mov     r9, rax                  ; PropertyBuffer (uninitialized)
00000001C00077F0  mov     rcx, rsi                 ; DeviceObject
00000001C00077F3  mov     edx, 0Bh                 ; DevicePropertyPhysicalDeviceObjectName
00000001C00077F8  call    cs:__imp_IoGetDeviceProperty

Patched — memset inserted between allocation and IoGetDeviceProperty:

00000001C00077AD  mov     edx, 202h                ; NumberOfBytes
00000001C00077B2  mov     dword ptr [rbp+DeviceName.Length], 2000000h
00000001C00077B9  lea     ecx, [rdi-1]             ; PoolType = PagedPool (rdi=2, so ecx=1)
00000001C00077BC  mov     r8d, 4D444D55h           ; Tag 'UMDM'
00000001C00077C2  call    cs:__imp_ExAllocatePoolWithTag
00000001C00077CE  mov     [rbp+DeviceName.Buffer], rax
00000001C00077D2  test    rax, rax
00000001C00077D5  jnz     short loc_1C00077E1
00000001C00077D7  mov     edi, 0C000009Ah
00000001C00077DC  jmp     loc_1C00078C3
00000001C00077E1  movzx   r8d, [rbp+DeviceName.MaximumLength]  ; 0x200
00000001C00077E6  xor     edx, edx                 ; Val = 0
00000001C00077E8  add     r8, rdi                  ; Size = MaximumLength + 2 = 0x202
00000001C00077EB  mov     rcx, rax                 ; buffer pointer
00000001C00077EE  call    memset                   ; THE FIX: zero entire 0x202 allocation
00000001C00077F3  movzx   r8d, [rbp+DeviceName.MaximumLength] ; BufferLength = 0x200
00000001C00077F8  lea     rax, [rbp+arg_18]        ; &ResultLength
00000001C00077FC  mov     r9, [rbp+DeviceName.Buffer]        ; PropertyBuffer (now zeroed)
00000001C0007800  mov     edx, 0Bh                 ; DevicePropertyPhysicalDeviceObjectName
00000001C0007805  mov     rcx, rsi                 ; DeviceObject
00000001C0007808  mov     [rsp+50h+ResultLength], rax
00000001C000780D  call    cs:__imp_IoGetDeviceProperty

memset is the driver's internal fill helper at 0x1C0001A40.

5. Trigger Conditions

  1. A modem device must be enumerated on the system so the PnP Manager invokes ModemAddDevice. This requires plugging in / presenting a modem device, or a privileged local operation such as pnputil /scan-devices or a disable/re-enable cycle in Device Manager.
  2. ModemAddDevice (or ModemPnP) calls ModemHandleSymbolicLink(..., create), which allocates the 0x202-byte DeviceName.Buffer and, in the unpatched build, passes it to IoGetDeviceProperty without zeroing.
  3. There is no untrusted request path (IOCTL, etc.) that reaches this code; the trigger is device enumeration, which is a privileged / physical action.

6. Impact Assessment

  • Class: Use of Uninitialized Resource (CWE-908). In the unpatched build the second pool buffer is not zeroed before it is handed to IoGetDeviceProperty.
  • Demonstrable impact: None established. The bytes beyond the valid PDO-name length are never read: DeviceName.Length is set from ResultLength, IoCreateSymbolicLink copies only Length bytes as the link target, and the buffer is freed immediately after. No code reads the tail and nothing returns it to a caller-observable location. The change is best characterized as defense-in-depth hardening that brings the second allocation in line with the first (which was already zeroed in both builds).
  • What the patch does: inserts memset(DeviceName.Buffer, 0, 0x202) so the entire allocation is zeroed before any use.
  • Claims of KASLR bypass, kernel-address disclosure, pool spraying/grooming, or a code-execution primitive are not supported by the binaries and are not made here.

7. Debugger Observation Notes

The difference between the two builds can be observed directly with a kernel debugger attached:

  • Breakpoints (unpatched):
  • bp modem!ModemHandleSymbolicLink+0xF4 (0x1C00077C0) — the second ExAllocatePoolWithTag call.
  • bp modem!ModemHandleSymbolicLink+0x12C (0x1C00077F8) — the following IoGetDeviceProperty call.
  • What to inspect:
  • At +0xF4: step over the allocation; rax is the new 0x202-byte buffer. db rax L202 shows the buffer contents (uninitialized pool bytes in the unpatched build).
  • At +0x12C: r9 holds the same buffer pointer; in the unpatched build no zeroing occurred between the two breakpoints, so the buffer is still uninitialized. In the patched build the memset at 0x1C00077EE has already zeroed it.
  • Trigger setup: load the driver, then force a PnP re-enumeration of the modem device (pnputil /scan-devices, devcon rescan, or a disable/re-enable cycle) so ModemAddDevice runs and calls ModemHandleSymbolicLink with the create flag.
  • Expected observation: in the unpatched build, db rax L202 at +0x12C shows non-zero pool bytes past the point where IoGetDeviceProperty will write the PDO name; in the patched build the same region is all zeros. This confirms the missing initialization, but note the tail is not exposed outside the kernel in either build.

8. Changed Functions — Full Triage

  • ModemHandleSymbolicLink @ 0x1C00076CC (Similarity: 0.986, Change Type: hardening)
  • Behavioral Change: Added a memset (0x1C0001A40) that zero-initializes the 0x202-byte DeviceName.Buffer for the PDO name before IoGetDeviceProperty, matching the zeroing already applied to the first (Destination) buffer. CWE-908 hardening; no demonstrable disclosure.
  • Cosmetic/Register Shifts: The pool-type argument for the second allocation changed from mov ecx, 1 to lea ecx, [rdi-1] (both yield 1 = PagedPool). The ResultLength - 2 computation changed from sub ax, 2 to mov edi, 2; sub ax, di. These repurpose rdi to hold the constant 2 used in the new memset size (MaximumLength + 2 = 0x202).
  • Jump Targets: Subsequent branch offsets shift by ~0x14 bytes to accommodate the inserted memset sequence. No behavioral change.

Independent function-level diff of both builds confirms this is the only function whose body changed. Every other diff line is a relocation of function-header addresses (the functions following ModemHandleSymbolicLink all shift by 0x14 bytes); no other security-relevant change was omitted.

9. Unmatched Functions

  • No functions were added or removed. The entire patch is contained within ModemHandleSymbolicLink.

10. Confidence & Caveats

  • Confidence: High that the change is a single added memset zero-initializing the second pool buffer, and that the direction is correct (patched build is stricter). The missing zero-initialization is definitely present in the unpatched binary.
  • Caveats: No information-disclosure impact is demonstrable from the binaries. The uninitialized tail is never read out or returned to any caller-observable sink; IoCreateSymbolicLink uses only the valid Length, and the buffer is freed immediately. The change is therefore defense-in-depth hardening (CWE-908), reachable only during PnP device setup/enumeration, not from an untrusted request. Severity is rated Low accordingly.