1. Overview

Item Value
Unpatched Binary pci_unpatched.sys
Patched Binary pci_patched.sys
Overall Similarity 0.559 (substantial divergence — major version rebuild)
Matched Functions 1145
Changed Functions 898
Identical Functions 247
Added (patched-only) Numerous (major version rebuild — new DOE/CXL/TDISP/AER handlers, etc.)

Verdict: The patch makes two defensive changes in the PCI bus driver's MSI-X interrupt-resource processing path. (1) PciProcessInterruptDescriptor now validates the device-supplied MSI-X capability read from PCI configuration space (the capability ID byte must be 0x11 and the table BIR field must be in range) before setting up any BAR-to-vector mapping, rejecting a bad device with STATUS_INVALID_DEVICE_REQUEST. (2) PciProcessStartResources now clears the stored IO-space mapping pointer after MmUnmapIoSpace, preventing a later double unmap of the same system-PTE range during device stop/remove. Both code paths run only during PnP device start / enumeration against the device's own config space, so they are reachable by a malicious or malfunctioning PCIe device (including a VM PCI-passthrough / SR-IOV guest), not from an untrusted user-mode IOCTL. There is no integer-overflow or pool-overflow fix here: the allocation-size multiply and the copy length are byte-for-byte identical in both builds.


2. Vulnerability Summary

Finding 1 — Missing MSI-X Capability Validation (PciProcessInterruptDescriptor)

Field Value
Severity Medium
Class Improper validation of device-controlled config data (CWE-20)
Function PciProcessInterruptDescriptor (unpatched 0x1C004D10C / patched 0x14009C810)
Entry Point IRP_MJ_PNP / IRP_MN_START_DEVICE via PnP Manager

Root Cause: On the MSI-X path (device-extension MSI type field == 3), the unpatched function reads the MSI-X capability from config space after allocating and copying, and then uses the device-supplied table BIR (BAR index, low 3 bits) and table offset without checking that the capability is genuinely MSI-X or that the BIR is in range:

PciReadDeviceConfig(a1, &v17, *(a1 + 57), 0xC);   // read MSI-X cap AFTER alloc
*(a5 + 12) = HIDWORD(v17) & 0xFFFFFFF8;            // table offset, stored unchecked
*(a5 + 8)  = BYTE4(v17) & 7;                       // BIR, stored unchecked (0..7)

The patch moves the read before the allocation and validates it:

PciReadDeviceConfig(a1, &v19, *(a1 + 61), 0xC);
if ( (_BYTE)v19 == 0x11 && (BYTE4(v19) & 7) != 7 ) { ... proceed ... }
else return STATUS_INVALID_DEVICE_REQUEST;         // 0xC0000184
  • Capability ID byte must be 0x11 (PCI_CAP_ID_MSIX).
  • Table BIR field (& 7) must not be 7 (i.e. must be ≤ 6).

The config-read offset changed from device-extension +0x39 (unpatched) to +0x3D (patched) only because the device-extension layout shifted between builds; both hold the stored MSI-X capability offset. The 32-bit size computation 88 * count + 8 and the matched memmove length are unchanged between builds — there is no integer-overflow fix in this function.

Impact: Without validation, a device that presents a crafted or mutated MSI-X capability (for example by changing its config space between enumeration and device start) can drive an out-of-range BIR and table offset into the BAR-to-vector setup that PciProcessStartResources later feeds to MmMapIoSpaceEx. The consequence is a mis-directed IO-space mapping within the device's assigned resource set — not a pool overflow. Reachable only via a malicious/malformed PCIe device or a PCI-passthrough guest during PnP start.

Call Chain (attacker data flow): 1. PnP Manager sends IRP_MN_START_DEVICE for an enumerating PCI device. 2. PciDeviceStartWorker (0x1C004C480) — PCI device-start / resource processing handler. 3. PciProcessStartResources (0x1C004CE40) — iterates CmResourceList descriptors. 4. PciProcessInterruptDescriptor (0x1C004D10C) — MSI/MSI-X interrupt descriptor processor (the validated target).


Finding 2 — Dangling IO-Mapping Pointer / Double MmUnmapIoSpace (PciProcessStartResources)

Field Value
Severity Medium
Class Expired pointer dereference leading to double unmap (CWE-825)
Function PciProcessStartResources (unpatched 0x1C004CE40 / patched 0x14009C500)
Entry Point Same PnP path as Finding 1

Root Cause: During MSI-X BAR table processing, PciProcessStartResources maps the table with MmMapIoSpaceEx (0x1C004D0DF) and stores the returned VA into the persistent MSI-X descriptor at device_ext+0x398. The descriptor structure passed as arg5 is device_ext+0x388, and its +0x10 field (+0x398) is the mapped BaseAddress. On the error/cleanup path the function calls MmUnmapIoSpace (0x1C0063839) on that VA but, in the unpatched build, never clears device_ext+0x398.

Because the descriptor lives in the device extension, the stale VA survives the failed start. The device teardown paths PciDevice_Remove (0x1C0010340) and PciDevice_Stop (0x1C006CAE0) later re-check the descriptor type and unmap device_ext+0x398 if it is non-NULL:

if ( *(_DWORD *)(a3 + 904) == 3 ) {               // device_ext+0x388 == MSI-X
    v11 = *(void **)(a3 + 920);                    // device_ext+0x398 (BaseAddress)
    if ( v11 != nullptr ) {
        MmUnmapIoSpace(v11, 16 * *(_DWORD *)(a3 + 1044));
        *(_QWORD *)(a3 + 920) = 0;
    }
}

With the pointer left stale from the failed start, teardown performs a second MmUnmapIoSpace on the same, already-unmapped system-PTE range. The patch inserts the fix:

mov qword [rsi+0x10], 0        ; device_ext+0x398 = 0, at 0x14009C72D

immediately after MmUnmapIoSpace, so teardown skips the second unmap.

Impact: Double unmap of an IO-space system-PTE mapping → bugcheck / kernel system-PTE accounting corruption. Reachable when MSI-X resource processing fails partway during device start (a device presents an invalid interrupt descriptor, or a subsequent MmMapIoSpaceEx fails), followed by PnP stop/remove. Malicious or faulty-device threat model.

Call Chain: Same PnP start path as Finding 1; the second unmap occurs during a later IRP_MN_REMOVE_DEVICE / IRP_MN_STOP_DEVICE on the same device object.


3. Pseudocode Diff

Finding 1 — PciProcessInterruptDescriptor

// ============ UNPATCHED (0x1C004D10C) — MSI-X path, no validation ============
if ( a4 < *(_DWORD *)a2 ) {
    *(_DWORD *)a5 = 3;                                 // type = MSI-X
    if ( a4 != 0 ) goto LABEL_15;
    PoolWithTag = *(PVOID *)(a5 + 32);
    if ( PoolWithTag == nullptr ) {
        PoolWithTag = ExAllocatePoolWithTag(NonPagedPoolNx, 4LL * *(unsigned int *)(a1 + 1044), 'PciB');
        if ( PoolWithTag == nullptr ) return STATUS_INSUFFICIENT_RESOURCES;
        *(_QWORD *)(a5 + 32) = PoolWithTag;
    }
    memset(PoolWithTag, 0, 4LL * *(unsigned int *)(a1 + 1044));
    v14 = (unsigned int)(88 * *(_DWORD *)a2 + 8);      // 32-bit size (unchanged in patch)
    v15 = ExAllocatePoolWithTag(NonPagedPoolNx, v14, 'PciB');
    if ( v15 != nullptr ) {
        memmove(v15, a2, (unsigned int)v14);           // copy length == allocation size
        // *** MSI-X capability read AFTER alloc, no validation of ID or BIR ***
        PciReadDeviceConfig(a1, &v17, *(a1 + 57), 0xC);
        *(_DWORD *)(a5 + 12) = HIDWORD(v17) & 0xFFFFFFF8;
        *(_BYTE  *)(a5 + 8)  = BYTE4(v17) & 7;
LABEL_15:
        ...
        return 0;
    }
    return STATUS_INSUFFICIENT_RESOURCES;
}

// ============ PATCHED (0x14009C810) — validate BEFORE alloc ============
if ( a4 < *(_DWORD *)a2 ) {
    *(_DWORD *)a5 = 3;
    if ( a4 != 0 ) goto LABEL_20;
    PciReadDeviceConfig(a1, &v19, *(a1 + 61), 0xC);    // read cap BEFORE alloc
    if ( (_BYTE)v19 == 0x11 && (BYTE4(v19) & 7) != 7 ) // cap id == MSI-X && BIR <= 6
    {
        ... ExAllocatePool2 / memmove (same 88*count+8 size) ...
        *(_DWORD *)(a5 + 12) = HIDWORD(v19) & 0xFFFFFFF8;
        *(_BYTE  *)(a5 + 8)  = BYTE4(v19) & 7;
        goto LABEL_20;
    }
    ... WPP trace ...
    return STATUS_INVALID_DEVICE_REQUEST;              // 0xC0000184
}

Finding 2 — PciProcessStartResources (error/cleanup path)

// ============ UNPATCHED (0x1C004CE40) ============
// arg5 (a5) = device_ext+0x388 ; a5+0x10 = device_ext+0x398 = mapped BaseAddress
if ( *(_DWORD *)a5 == 3 ) {                            // MSI-X
    va = *(void **)((char*)a5 + 0x10);
    if ( va ) {
        MmUnmapIoSpace(va, r13);                       // 0x1C0063839
        // *** MISSING: *(_QWORD*)((char*)a5 + 0x10) = 0 ***
    }
}

// ============ PATCHED (0x14009C500) ============
if ( *(_DWORD *)a5 == 3 ) {
    va = *(void **)((char*)a5 + 0x10);
    if ( va ) {
        MmUnmapIoSpace(va, r13);                       // 0x14009C721
        *(_QWORD *)((char*)a5 + 0x10) = 0;             // 0x14009C72D  ← FIX
    }
}

4. Assembly Analysis

Finding 1 — Unpatched PciProcessInterruptDescriptor (MSI-X path)

1C004D22D  mov     dword ptr [rbx], 3            ; type = MSI-X
1C004D233  test    r9d, r9d
1C004D236  jz      1C004D24D                     ; proceed to allocation
1C004D2A9  imul    eax, [rsi], 58h               ; size = count * 0x58 (32-bit) — identical in patched
1C004D2B4  add     eax, 8
1C004D2B7  mov     edx, eax                       ; NumberOfBytes
1C004D2B9  mov     r13d, eax                      ; same value reused as copy length
1C004D2BC  call    cs:__imp_ExAllocatePoolWithTag
1C004D2D4  mov     r8d, r13d                      ; Size = allocation size (NOT larger)
1C004D2D7  mov     rdx, rsi                       ; Src
1C004D2DA  mov     rcx, rax
1C004D2DD  call    memmove                        ; copy length == allocation size (no overflow)
1C004D2F9  call    PciReadDeviceConfig            ; MSI-X cap read AFTER alloc, no validation
1C004D302  and     eax, 0FFFFFFF8h                ; table offset
1C004D30C  and     al, 7                          ; BIR stored unchecked
1C004D30E  mov     [rbx+8], al

Finding 1 — Patched PciProcessInterruptDescriptor (added validation)

14009C89F  mov     dword ptr [rbx], 3            ; type = MSI-X
14009C8A5  test    r9d, r9d
14009C8A8  jnz     14009C99C
14009C8AE  movzx   r8d, byte ptr [rdi+3Dh]       ; MSI-X cap offset (relocated device-ext field)
14009C8B3  lea     r9d, [rcx+0Bh]
14009C8BF  call    PciReadDeviceConfig            ; read BEFORE alloc
14009C8C4  cmp     byte ptr [rsp+var_68], 11h     ; capability ID must be MSI-X (0x11)
14009C8C9  jnz     14009C9B8                      ; reject
14009C8CF  mov     eax, dword ptr [rsp+var_68+4]
14009C8D3  and     eax, 7
14009C8D6  cmp     al, 6                          ; table BIR must be <= 6
14009C8D8  ja      14009C9B8                      ; reject
; ... reject block 14009C9B8 traces via WPP then:
14009CA17  mov     eax, 0C0000184h                ; STATUS_INVALID_DEVICE_REQUEST
14009CA1C  jmp     14009CAAB

Finding 2 — Unpatched PciProcessStartResources (dangling pointer)

1C004D0C3  mov     r13d, [rdx+414h]              ; BAR count
1C004D0D8  shl     r13d, 4                        ; size = BAR_count * 16
1C004D0DC  mov     edx, r13d
1C004D0DF  call    cs:__imp_MmMapIoSpaceEx        ; map physical IO space
1C004D0EB  mov     [rbp+10h], rax                 ; store VA into descriptor+0x10 (device_ext+0x398)
1C004D0F2  je      1C0063813                      ; fail path
; --- error/cleanup site ---
1C0063818  mov     rcx, [rsp+arg_20]              ; arg5 = device_ext+0x388
1C0063820  cmp     dword ptr [rcx], 3             ; MSI-X?
1C0063823  jnz     1C004CF6C
1C0063829  mov     rcx, [rcx+10h]                 ; BaseAddress (device_ext+0x398)
1C006382D  test    rcx, rcx
1C0063830  jz      1C004CF6C
1C0063836  mov     edx, r13d
1C0063839  call    cs:__imp_MmUnmapIoSpace
1C0063840  nop
1C0063845  nop                                    ; *** no clear of device_ext+0x398 ***
1C0063846  jmp     1C004CF6C

Finding 2 — Patched PciProcessStartResources

14009C708  cmp     dword ptr [rsi], 3            ; rsi = device_ext+0x388
14009C70B  jnz     14009C678
14009C711  mov     rcx, [rsi+10h]                ; BaseAddress (device_ext+0x398)
14009C715  test    rcx, rcx
14009C718  jz      14009C678
14009C71E  mov     edx, r13d
14009C721  call    cs:__imp_MmUnmapIoSpace
14009C72D  mov     qword ptr [rsi+10h], 0        ; *** CLEAR device_ext+0x398 (FIX) ***
14009C735  jmp     14009C678

5. Trigger Conditions

Finding 1 (Missing MSI-X Capability Validation)

  1. Present a malicious or malformed PCIe device (physical, Thunderbolt/USB4, or VM PCI passthrough / SR-IOV) that advertises an MSI-X capability so the device's MSI type field is set to MSI-X during enumeration.
  2. On IRP_MN_START_DEVICE, arrange for the MSI-X capability read at start to disagree with a valid MSI-X capability: capability ID byte != 0x11, or table BIR field == 7 (for example by mutating config space after enumeration).
  3. Unpatched: the driver proceeds without validation and feeds the device-supplied BIR / table offset into the BAR-to-vector setup consumed by PciProcessStartResources. Patched: the start fails with STATUS_INVALID_DEVICE_REQUEST (0xC0000184).
  4. Observable confirmation: unpatched continues device start and stores the unchecked BIR at descriptor +0x8; patched returns 0xC0000184 and does not allocate. Breakpoint PciProcessInterruptDescriptor and inspect the return status and descriptor +0x8.

Finding 2 (Double MmUnmapIoSpace)

  1. Attach an MSI-X-capable PCI device and let IRP_MN_START_DEVICE map the MSI-X table BAR so device_ext+0x398 is populated.
  2. Force MSI-X resource processing to fail after the mapping — e.g. a later interrupt descriptor returns a negative status from PciProcessInterruptDescriptor (0x1C004D001), or a subsequent MmMapIoSpaceEx returns NULL (0x1C0063813) — so the cleanup path unmaps device_ext+0x398. In the unpatched build the pointer is left set.
  3. Device start fails; PnP issues IRP_MN_REMOVE_DEVICE / IRP_MN_STOP_DEVICE. PciDevice_Remove (0x1C0010340) / PciDevice_Stop (0x1C006CAE0) re-read device_ext+0x388 == 3 and unmap device_ext+0x398 a second time.
  4. Observable confirmation: breakpoint MmUnmapIoSpace and observe two calls with the same base address — once on the start-failure path, once during teardown. The second call unmaps an already-freed system-PTE range, leading to a bugcheck or PTE accounting corruption.

6. Impact Assessment

Finding 1

  • Unvalidated device config data → the device-supplied MSI-X BIR and table offset flow into the BAR-to-vector setup that PciProcessStartResources feeds to MmMapIoSpaceEx. The mapped physical base comes from the OS-assigned CmResourceList; the device controls only the BIR selection (which resource descriptor) and the table offset added to that base, bounded to the device's assigned resource set. This is primarily a robustness / input-validation fix against malicious or mutated device config, including a config-space time-of-check/time-of-use by a device that changes its capability between enumeration and start. No demonstrated memory-safety primitive.

Finding 2

  • Double unmap of a system-PTE IO mapping → denial of service (bugcheck) and potential kernel system-PTE accounting corruption when the start-failure path and the device stop/remove path both call MmUnmapIoSpace on the same VA. No demonstrated controlled read/write primitive.

Reachability

Both findings are reached only during PnP device start / enumeration, operating on the device's own PCI configuration space. They are not reachable from an untrusted user-mode IOCTL surface. The realistic attacker is a malicious or malfunctioning PCIe device, a Thunderbolt/USB4 attachment, or a VM PCI-passthrough / SR-IOV guest. Standard kernel mitigations (KASLR, SMEP/SMAP, HVCI/VBS, pool/PTE integrity checks) apply but are not central to either fix, since neither yields a demonstrated corruption primitive.


7. Debugger Playbook

Finding 1 — PciProcessInterruptDescriptor

Breakpoints

bp pci_unpatched!PciProcessInterruptDescriptor  "Entry — RCX=device ext, RDX=resource list, R8=descriptor, R9D=index"
bp 0x1c004d22d                                   "MSI-X type set (no validation in unpatched)"
bp 0x1c004d2f9                                   "PciReadDeviceConfig — cap read AFTER alloc, no guard"
bp pci_patched!PciProcessInterruptDescriptor
bp 0x14009c8bf                                   "PciReadDeviceConfig — cap read BEFORE alloc"
bp 0x14009c8c4                                   "cmp cap id == 0x11"
bp 0x14009c8d6                                   "cmp BIR <= 6"

Key Offsets

Address Meaning
0x1c004d22d mov dword [rbx], 3 — MSI-X type set (no pre-validation, unpatched)
0x1c004d2a9 imul eax, [rsi], 0x58 — 32-bit size compute (identical in both builds)
0x1c004d2dd call memmove — copy length equals allocation size (no overflow)
0x1c004d2f9 PciReadDeviceConfig — cap read after alloc, unchecked
Patched 0x14009c8bf PciReadDeviceConfig — cap read before alloc
Patched 0x14009c8c4 cmp byte [rsp+var_68], 0x11 — MSI-X capability ID check
Patched 0x14009c8d6 cmp al, 6 — table BIR upper bound
Patched 0x14009ca17 mov eax, 0xC0000184STATUS_INVALID_DEVICE_REQUEST

What to Inspect

  • At entry: RCX = device extension ([RCX+0x410] MSI type in unpatched, [RCX+0x520] in patched); RDX = resource list ([RDX] = descriptor count); R9D = index.
  • In the patched build, [rsp+var_68] after PciReadDeviceConfig holds the capability bytes: low byte = capability ID (expect 0x11), BYTE4 & 7 = BIR (rejected if 7).

Finding 2 — PciProcessStartResources

Breakpoints

bp pci_unpatched!PciProcessStartResources        "Entry — RCX=dev ext, RDX=resource list, R9=out buf, arg5=device_ext+0x388"
bp 0x1c004d0df                                    "MmMapIoSpaceEx — stores VA at device_ext+0x398"
bp 0x1c0063839                                    "MmUnmapIoSpace on error path (pointer left stale in unpatched)"
bp pci_unpatched!PciDevice_Remove
bp 0x1c006cae0                                    "PciDevice_Stop — second unmap of device_ext+0x398"
bp 0x14009c72d                                    "patched: clears device_ext+0x398 after unmap"

Key Offsets

Address Meaning
0x1c004d0df MmMapIoSpaceEx — maps MSI-X table BAR
0x1c004d0eb mov [rbp+0x10], rax — store VA into device_ext+0x398
0x1c0063839 MmUnmapIoSpace on the start-failure cleanup path
0x1c0063846 jmp past cleanup — pointer still set (unpatched)
PciDevice_Remove 0x1c0010340 teardown re-unmaps device_ext+0x398 if non-NULL
PciDevice_Stop 0x1c006cae0 teardown re-unmaps device_ext+0x398 if non-NULL
Patched 0x14009c72d mov qword [rsi+0x10], 0 — clears the pointer

Descriptor Layout (MSI-X output, arg5 = device_ext+0x388, 0x68 bytes)

Offset Type Field
+0x00 DWORD Type (3 = MSI-X)
+0x08 BYTE BIR / BAR index
+0x0C DWORD Table offset
+0x10 QWORD BaseAddress — the dangling pointer (device_ext+0x398)
+0x18 DWORD Vector index / count
+0x20 QWORD BAR-to-vector table pool
+0x28 QWORD MSI-X descriptor copy pool

Device Extension Offsets

Unpatched Patched Field
+0x410 +0x520 MSI type (3 = MSI-X)
+0x414 +0x524 BAR count
+0x388 +0x388 MSI-X output descriptor base (arg5)
+0x398 +0x398 Mapped MSI-X table BaseAddress (arg5+0x10)
+0x38C +0x49C Interrupt pin
+0x39 +0x3D MSI-X capability offset (read by PciProcessInterruptDescriptor)

8. Changed Functions — Triage

Function Sim Change Type Note
PciProcessInterruptDescriptor 0.802 security_relevant Adds MSI-X capability validation (cap_id == 0x11, BIR != 7) and moves the config read to before the allocation. Also swaps ExAllocatePoolWithTagExAllocatePool2 (API modernization) and sets state flag +0x6b0 \|= 0x2000000000000000 on success. The 88*count+8 size math and matched memmove length are unchanged — no integer-overflow fix.
PciProcessStartResources 0.566 security_relevant Clears device_ext+0x398 (arg5+0x10) after MmUnmapIoSpace, preventing the double unmap performed later by PciDevice_Remove / PciDevice_Stop. Also adds a legacy VGA resource filter (PciIsVgaResource, 0x14009C7BC) and a second output buffer; struct offsets shift (0x414→0x524, 0x410→0x520, 0x1EC→0x2FC, 0x38C→0x49C).
PciDeviceStartWorker 0.850 behavioral Caller of PciProcessStartResources (device-start / resource processing). Passes device_ext+0x388 as the MSI-X output descriptor. Offset shifts consistent with the struct refactor.
PciAssignSlotResources 0.489 behavioral Second caller of PciProcessStartResources (slot resource assignment). Rewritten for the new struct layout.

Refactor / non-security churn: the patch is a major version rebuild — pool allocator API swap (ExAllocatePoolWithTagExAllocatePool2), pervasive device-extension layout shifts, and heavy WPP/ETW tracing additions. These lower the similarity score but are not behavior-affecting security changes. An independent size-arithmetic sweep across all shared functions found no additional integer-overflow guard (no new multiply/shift guard, no 32→64-bit widening, no count clamp) beyond the two changes above.


9. Added / Removed Functions

The patched build is a major version rebuild that introduces numerous new functions (for example DOE / CXL / TDISP / AER handlers and feature-staging helpers) with no unpatched counterpart. None of these is the delivered security fix; both security changes are in-place modifications to PciProcessInterruptDescriptor and PciProcessStartResources. The overflow-checked helper RtlULongLongMult appears only in the patched build, but exclusively in new code or as a refactor of an existing guard: the sole shared-function call site, PciGetRequirementsForVfs, already validated the same length * NumVFs multiply with is_mul_ok in the unpatched build, so this is a guard modernization, not a newly added overflow check.

Helpers invoked by the patched code but already present in both builds: PciReadDeviceConfig (config capability reader) and PciIsVgaResource (0x14009C7BC, legacy VGA resource filter).


10. Confidence & Caveats

Confidence: High for the existence, location, and direction of both changes. The assembly evidence is direct: the added capability validation and the cleared dangling pointer both map to concrete instruction differences between the two builds.

Caveats:

  • Reachability: both code paths run only during PnP device start / enumeration against the device's own config space. They are not reachable from an untrusted user-mode IOCTL surface. The attacker is a malicious/malformed PCIe device or a PCI-passthrough / SR-IOV guest.
  • Finding 1 impact is bounded: the device controls only the MSI-X BIR selection and table offset added to an OS-assigned physical base within its assigned resource set; there is no demonstrated pool overflow or arbitrary-mapping primitive. This is a validation / robustness fix.
  • No integer overflow: the 88 * count + 8 allocation-size multiply is 32-bit in both builds and the memmove length equals the allocation size in both builds. The patch does not touch this arithmetic; an integer-overflow framing does not hold against the binaries.
  • Finding 2 is a genuine double unmap: the descriptor at device_ext+0x388 is persistent, and PciDevice_Remove / PciDevice_Stop re-unmap device_ext+0x398 when it is left non-NULL. The fix is the pointer clear in PciProcessStartResources.
  • Symbol names are the driver's own export/analysis names; addresses are authoritative for breakpoints.