pci.sys — Missing MSI-X capability validation and dangling IO-mapping pointer causing double MmUnmapIoSpace (CWE-20, CWE-825) fixed
KB5087545
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 be7(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)
- 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.
- 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). - 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 withSTATUS_INVALID_DEVICE_REQUEST (0xC0000184). - Observable confirmation: unpatched continues device start and stores the unchecked BIR at descriptor
+0x8; patched returns0xC0000184and does not allocate. BreakpointPciProcessInterruptDescriptorand inspect the return status and descriptor+0x8.
Finding 2 (Double MmUnmapIoSpace)
- Attach an MSI-X-capable PCI device and let
IRP_MN_START_DEVICEmap the MSI-X table BAR sodevice_ext+0x398is populated. - 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 subsequentMmMapIoSpaceExreturns NULL (0x1C0063813) — so the cleanup path unmapsdevice_ext+0x398. In the unpatched build the pointer is left set. - Device start fails; PnP issues
IRP_MN_REMOVE_DEVICE/IRP_MN_STOP_DEVICE.PciDevice_Remove(0x1C0010340) /PciDevice_Stop(0x1C006CAE0) re-readdevice_ext+0x388 == 3and unmapdevice_ext+0x398a second time. - Observable confirmation: breakpoint
MmUnmapIoSpaceand 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
PciProcessStartResourcesfeeds toMmMapIoSpaceEx. The mapped physical base comes from the OS-assignedCmResourceList; 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
MmUnmapIoSpaceon 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, 0xC0000184 — STATUS_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]afterPciReadDeviceConfigholds the capability bytes: low byte = capability ID (expect0x11),BYTE4 & 7= BIR (rejected if7).
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 ExAllocatePoolWithTag → ExAllocatePool2 (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 (ExAllocatePoolWithTag → ExAllocatePool2), 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 + 8allocation-size multiply is 32-bit in both builds and thememmovelength 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+0x388is persistent, andPciDevice_Remove/PciDevice_Stopre-unmapdevice_ext+0x398when it is left non-NULL. The fix is the pointer clear inPciProcessStartResources. - Symbol names are the driver's own export/analysis names; addresses are authoritative for breakpoints.