b57nd60a.sys — Unvalidated PCI MSI capability offset in raw config-space access (CWE-125) hardened via safe HAL API; low severity, fixed
KB5073723
1. Overview
| Field | Value |
|---|---|
| Unpatched binary | b57nd60a_unpatched.sys |
| Patched binary | b57nd60a_patched.sys |
| Overall similarity | 0.9893 (98.93%) |
| Matched functions | 457 |
| Changed functions | 21 |
| Identical functions | 436 |
| Unmatched (either direction) | 0 |
Verdict: This patch is a set of low-severity hardening and behavioral changes to the Broadcom NetXtreme Gigabit Ethernet NDIS miniport driver. The clearest security-relevant change moves PCI MSI capability config-space reads/writes off raw pointer arithmetic (using a device-supplied capability offset) and onto the HAL config API (SaveMsiCaps / RestoreMsiCaps). The remaining changes are a chip-family-specific coalescing guard (CoalescingSendPacketIfNecessary), an added CPU-index validity guard and RSS refactor (UM_Dpc), and removal of a Thunderbolt register-writeback block during send-queue cleanup (ClearActiveSqAndMapping). None of these is a demonstrable high-severity, remotely-exploitable memory-corruption bug in the binaries.
The bulk of the 21 changed functions differ only by adapter-context structure-offset shifts (the context struct grew, moving many fields by a small constant delta) plus compiler register-allocation differences.
2. Vulnerability Summary
Finding 1 — SaveMsiCaps / RestoreMsiCaps (LOW)
| Attribute | Detail |
|---|---|
| Severity | Low (defense-in-depth; requires malicious/emulated PCI hardware) |
| Class | CWE-125 Out-of-bounds Read via unvalidated PCI capability offset |
| Function | SaveMsiCaps (unpatched 0x1400021E0, patched 0x1400021EC); RestoreMsiCaps (0x140002134) |
| Entry point | Adapter initialization / reset / power transition |
Root cause: The unpatched SaveMsiCaps calls FindPciCapMem(arg1, 5) to locate the MSI capability, takes the returned byte offset (al), and computes four kernel-virtual addresses as config_base + offset + {2, 8, 4, 0xC} (where config_base = [rbx+0x20]), then dereferences them directly. The offset is a single byte (0–255) read out of the device's own PCI capability list. If a malicious or emulated PCI device returns an unexpected offset, the computed address can land past a legacy 256-byte config-space window (still within the 4 KB extended config mapping), producing an out-of-bounds read. RestoreMsiCaps uses the same pattern and additionally performs a raw write back into config space (mov [rdx+rax], cx). The patch replaces both with the HAL-mediated MM_ReadConfig32 / MM_WriteConfig32 calls using fixed offsets (0x58, 0x5C, 0x60, 0x64), which cannot go out of bounds. This is a hardening change: for a well-formed NetXtreme device the capability walk returns a valid in-range offset, so the practical impact is limited to attackers who already control the PCI device (and therefore already have bus-master DMA).
Call chain:
UM_Initialize/ adapter bring-up orLM_ResetAdapter.SaveMsiCaps(0x1400021E0) /RestoreMsiCaps(0x140002134).- Raw dereference at
config_base + FindPciCapMem_result + N(unpatched only).
Finding 2 — CoalescingSendPacketIfNecessary (LOW)
| Attribute | Detail |
|---|---|
| Severity | Low (chip-family-specific coalescing correctness guard / defense-in-depth) |
| Class | CWE-20 Improper Input Validation (bounded fragment field on one chip family) |
| Function | CoalescingSendPacketIfNecessary (unpatched 0x140042B18, patched 0x140042E58) |
| Entry point | NDIS send path → MP_ProcessSGList → CoalescingSendPacketIfNecessary |
Root cause: CoalescingSendPacketIfNecessary iterates over a driver-internal fragment array and decides whether to coalesce the outgoing packet. Its loop sets a decision local (var_28, values 1 or 2) and returns a boolean; it does not itself write into the TX descriptor ring based on the fragment fields. The patch adds a chip-family-gated guard: when the chip-family identifier [rsi+0x52960] >> 12 equals 9 and a per-fragment 32-bit field (the third field of each fragment entry, at [r14 + index*12 + 0x10], held in ebx in the patched build) is >= 0x100, it forces the non-coalescing decision (var_28 = 2) instead of coalescing. This field is only consumed inside the decision loop and in a stack qword (var_20) that is zeroed (and [rbp+var_20], 0) before the coalescing path runs, so it does not feed any ring-index arithmetic. The downstream ring bookkeeping is byte-identical between the two builds. The change therefore constrains a coalescing decision on one chip family; it is not a fix for an out-of-bounds write.
Call chain:
- NDIS send path builds a fragment list.
MP_ProcessSGList(0x14004374C) callsCoalescingSendPacketIfNecessary(callat0x140043B21).nd6_LM_HandleTxInterrupts(0x140045318) also calls it (callat0x140045A2D).
Finding 3 — UM_Dpc (LOW)
| Attribute | Detail |
|---|---|
| Severity | Low (added CPU-index validity guard + RSS refactor) |
| Class | CWE-20 Improper Input Validation / defensive refactor |
| Function | UM_Dpc (unpatched 0x140002E28, patched 0x140002E78) |
| Entry point | NDIS interrupt DPC dispatch |
Root cause: In both builds the CPU index used to index per-CPU arrays is captured from mm_get_current_cpu_idx() and stored in the non-volatile ebp. In the unpatched build the index is captured fresh immediately before use (call mm_get_current_cpu_idx; mov ebp, eax at 0x140002FC2, then [rbx+rbp*4+0x56A38] at 0x140002FE3), so it is not stale. The patch moves the capture to function entry (call at 0x140002EA8, mov ebp, eax at 0x140002EAD) and adds explicit validity guards before the per-CPU array accesses (test cl,cl; jnz skip, cmp ebp, [rbx+off]; jnz skip, and a slot check cmp [rbx+rbp*4+off], r12d; jz skip), together with an RSS-servicing refactor that uses a lock cmpxchg update. This is a synchronization/validation refactor that adds bounds-style guards around the CPU-indexed accesses. No stale-index out-of-bounds access is demonstrable in the unpatched code.
Call chain:
- Hardware interrupt → NDIS schedules the DPC.
UM_Dpc(0x140002E28) processes RX/TX completions and RSS queue management.- Per-CPU array accesses at
[rbx + (ebp<<2) + 0x56A38]and[rbx + (rax<<3) + 0x61F8].
Finding 4 — ClearActiveSqAndMapping (LOW)
| Attribute | Detail |
|---|---|
| Severity | Low (removed register-writeback block; no freed-memory access demonstrable) |
| Class | CWE-1164 Irrelevant/removed code path (potential-corruption cleanup) |
| Function | ClearActiveSqAndMapping (unpatched 0x1400426AC, patched 0x140042A98) |
| Entry point | Adapter reset / send-queue teardown |
Root cause: The unpatched code contains two identical blocks that, when the flag at [rdi+0x57160] is set, load a context pointer from the loop structure ([r13]), validate the magic 0xBCEA7765, then read two Thunderbolt registers (REG_RD_TBT at config_base + 0x5C0 and + 0x5C4) and write them into [r13+8] and [r13+0xC]. The patch removes both blocks. The report cannot show that r13 is ever freed before this runs; the explicit 0xBCEA7765 magic check is a deliberate type gate, not a naive use-after-free. This is best read as removal of a register-readback/writeback path during teardown (potentially to avoid writing to a context whose lifetime is uncertain), i.e. a hardening/behavioral removal rather than a confirmed use-after-free.
Call chain:
UM_CheckForHang→LM_ResetAdapter→ClearActiveSqAndMapping, or directLM_ResetAdapter.- Cleanup loop reads the context pointer and (unpatched only) writes two TBT register values into it.
3. Pseudocode Diff
SaveMsiCaps
// ===== UNPATCHED (0x1400021E0) =====
void SaveMsiCaps(adapter)
{
uint8_t cap_offset = FindPciCapMem(adapter, 5); // MSI capability ID, byte offset
void *base = adapter->config_base; // [adapter+0x20]
adapter->msi_a = *(uint16_t *)(base + cap_offset + 2); // raw deref
adapter->msi_b = *(uint32_t *)(base + cap_offset + 8); // raw deref
adapter->msi_c = *(uint32_t *)(base + cap_offset + 4); // raw deref
adapter->msi_d = *(uint16_t *)(base + cap_offset + 0xC); // raw deref
}
// ===== PATCHED (0x1400021EC) =====
void SaveMsiCaps(adapter)
{
uint32_t val;
MM_ReadConfig32(adapter, 0x58, &val); adapter->msi_a = val >> 16; // HAL-mediated
MM_ReadConfig32(adapter, 0x60, &val); adapter->msi_b = val;
MM_ReadConfig32(adapter, 0x5C, &val); adapter->msi_c = val;
MM_ReadConfig32(adapter, 0x64, &val); adapter->msi_d = (uint16_t)val;
}
RestoreMsiCaps is the companion: unpatched reads and writes config space at config_base + FindPciCapMem_offset; patched uses MM_ReadConfig32(0x58) / MM_WriteConfig32. Both are gated on chip family ((chip_id >> 12) - 8 <= 1, i.e. family 8 or 9).
CoalescingSendPacketIfNecessary
// Both builds: a decision function. The loop validates fragment entries and
// sets a coalescing-decision local (var_28 = 1 or 2). It does NOT write the
// TX ring based on the fragment fields.
int CoalescingSendPacketIfNecessary(adapter, ..., frag_array)
{
for (int i = 0; i < count; i++)
{
uint32_t f1 = frag[i].dword_at_08;
uint32_t f0 = frag[i].dword_at_0C;
int32_t f2 = frag[i].dword_at_10; // held in ebx (patched)
if ((f1 + f0 + 8) < f0) { var_28 = 2; break; } // overflow check, both builds
// ===== PATCHED ONLY (0x140042F87) =====
if ((chip_id >> 12) == 9 && f2 >= 0x100) // chip-family-9 guard
{ var_28 = 2; break; } // force non-coalescing decision
if (adapter->coalesce_flag == 0) // [rsi+0x568EC] (unpatched: 0x568BC), both builds
{ /* size-accumulation check */ }
}
// ... ring bookkeeping below is byte-identical between builds and
// indexed by queue number [rdi+0x1C], not by f2 ...
}
UM_Dpc
// ===== UNPATCHED (0x140002E28) =====
void UM_Dpc(adapter, ...)
{
mm_get_current_cpu_idx(); // entry call (result not retained here)
// ...
int cpu = mm_get_current_cpu_idx(); // FRESH capture right before use (0x140002FC2)
// cpu (ebp) used immediately:
x = adapter->rss_mask[cpu]; // [rbx + cpu*4 + 0x56A38]
}
// ===== PATCHED (0x140002E78) =====
void UM_Dpc(adapter, ...)
{
mm_get_current_cpu_idx();
int cpu = mm_get_current_cpu_idx(); // capture moved to entry, saved in ebp
// ... added validity guards before every per-CPU array access:
if (flag && cpu == adapter->cpu_limit_field && adapter->rss_mask[cpu] != 0)
x = adapter->rss_mask[cpu];
// plus RSS servicing refactored to use lock cmpxchg
}
ClearActiveSqAndMapping
// ===== UNPATCHED (0x1400426AC) — two identical blocks =====
if (adapter->tbt_flag) // [rdi+0x57160]
{
int32_t *ctx = loop_struct.context_ptr; // [r13]
if (ctx && ctx[0] == 0xBCEA7765 && !ctx[1])
{
ctx[2] = REG_RD_TBT(adapter, config_base + 0x5C0); // write to ctx+0x8
ctx[3] = REG_RD_TBT(adapter, config_base + 0x5C4); // write to ctx+0xC
}
}
// ===== PATCHED (0x140042A98) =====
// Both blocks removed; the context pointer is not dereferenced for register writes.
4. Assembly Analysis
SaveMsiCaps — Unpatched (full function)
; ===== UNPATCHED SaveMsiCaps (0x1400021E0) =====
00000001400021E0 push rbx
00000001400021E2 sub rsp, 20h
00000001400021E6 mov edx, 5 ; capability ID = MSI
00000001400021EB mov rbx, rcx
00000001400021EE call FindPciCapMem
00000001400021F3 movzx r8d, al ; capability offset (BYTE)
00000001400021F7 lea edx, [r8+2]
00000001400021FB add rdx, [rbx+20h] ; config_base + offset + 2
00000001400021FF movzx eax, word ptr [rdx] ; RAW DEREF
0000000140002202 lea ecx, [r8+8]
0000000140002206 add rcx, [rbx+20h]
000000014000220A mov [rbx+59EE4h], ax
0000000140002211 mov eax, [rcx] ; RAW DEREF
0000000140002213 lea ecx, [r8+4]
0000000140002217 add rcx, [rbx+20h]
000000014000221B mov [rbx+59EE8h], eax
0000000140002221 mov eax, [rcx] ; RAW DEREF
0000000140002223 lea ecx, [r8+0Ch]
0000000140002227 add rcx, [rbx+20h]
000000014000222B mov [rbx+59EECh], eax
0000000140002231 movzx eax, word ptr [rcx] ; RAW DEREF
0000000140002234 mov [rbx+59EF0h], ax
000000014000223B add rsp, 20h
000000014000223F pop rbx
0000000140002240 retn
SaveMsiCaps — Patched (full function)
; ===== PATCHED SaveMsiCaps (0x1400021EC) =====
00000001400021EC push rbx
00000001400021EE sub rsp, 20h
00000001400021F2 lea r8, [rsp+28h+arg_0]
00000001400021F7 mov edx, 58h ; fixed offset
00000001400021FC mov rbx, rcx
00000001400021FF call MM_ReadConfig32
0000000140002204 mov eax, [rsp+28h+arg_0]
000000014000220D shr eax, 10h
0000000140002210 mov edx, 60h
0000000140002218 mov [rbx+59F2Ch], ax
000000014000221F call MM_ReadConfig32
0000000140002228 mov edx, 5Ch
0000000140002235 mov [rbx+59F30h], eax
000000014000223B call MM_ReadConfig32
0000000140002249 mov edx, 64h
0000000140002251 mov [rbx+59F34h], eax
0000000140002257 call MM_ReadConfig32
000000014000225C movzx eax, word ptr [rsp+28h+arg_0]
0000000140002261 mov [rbx+59F38h], ax
0000000140002268 add rsp, 20h
000000014000226C pop rbx
000000014000226D retn
CoalescingSendPacketIfNecessary — Fragment loop (both builds)
; ===== PATCHED loop head (0x140042EC9) — fragment fields =====
0000000140042EC9 lea rcx, [r8+r8*2] ; rcx = index * 3
0000000140042ECD mov edx, [r14+rcx*4+0Ch] ; fragment field @ +0xC
0000000140042ED2 mov r9d, [r14+rcx*4+8] ; fragment field @ +0x8
0000000140042ED7 mov ebx, [r14+rcx*4+10h] ; fragment field @ +0x10 (the bound-checked value)
0000000140042EDC lea r11d, [r9+rdx]
0000000140042EE3 mov [rbp+var_20+4], ebx ; stored to stack (var_20 qword)
0000000140042EE6 lea eax, [r11+8]
0000000140042EEA cmp eax, edx
0000000140042EEC jb loc_140042FFA ; overflow check -> non-coalesce
In the unpatched build the same field is loaded at 0x140042B91 (mov eax, [r14+rcx*4+10h]) and stored to the same stack qword, but never compared to 0x100.
CoalescingSendPacketIfNecessary — the added guard (patched only)
; ===== PATCHED (0x140042F62 .. 0x140042F94) =====
0000000140042F62 cmp r9d, 8
0000000140042F66 ja short loc_140042F87
0000000140042F68 cmp dword ptr [r14+4], 5EAh
0000000140042F70 ja short loc_140042F87
0000000140042F72 lea eax, [rcx-5717h] ; chip-family range check (present in both)
0000000140042F78 cmp eax, 9
0000000140042F7B ja short loc_140042F87
0000000140042F7D mov edx, 205h
0000000140042F82 bt edx, eax
0000000140042F85 jb short loc_140042FFA
0000000140042F87 cmp ecx, 9 ; NEW: chip family == 9 ? (ecx = chip_id >> 12)
0000000140042F8A jnz short loc_140042F94 ; NEW
0000000140042F8C cmp ebx, 100h ; NEW: fragment field @ +0x10 >= 256 ?
0000000140042F92 jnb short loc_140042FFA ; NEW: force non-coalescing decision (var_28 = 2)
0000000140042F94 cmp [rsi+568ECh], r12b ; coalescing flag (present in both; unpatched [rsi+568BCh])
0000000140042F9B jnz short loc_140042FAA
The unpatched build has the identical chip-family range checks (0x140042C24–0x140042C47) followed directly by the coalescing-flag test cmp [rsi+568BCh], r12b at 0x140042C49 — with no cmp ..., 9 / cmp ..., 0x100 block in between. loc_140042FFA (patched) / loc_140042CAD (unpatched) is mov dword ptr [rbp+var_28], 2, the non-coalescing decision. The ring bookkeeping that follows (writes to [r15+rsi+0x57BB8], inc [r15+rsi+0x57B84], shifted to +0x57C00 / +0x57BCC in the patched build by the struct-offset delta) is logically identical in both builds and is indexed by the queue number [rdi+0x1C], not by the fragment field.
UM_Dpc — entry sequence
; ===== UNPATCHED (0x140002E28) =====
0000000140002E50 mov rbx, rcx
0000000140002E53 call mm_get_current_cpu_idx ; entry call
0000000140002E58 cmp [rbx+57170h], r12d
; ... later, fresh capture immediately before array use:
0000000140002FC2 call mm_get_current_cpu_idx
0000000140002FD0 mov ebp, eax
0000000140002FE3 mov esi, [rbx+rbp*4+56A38h] ; per-CPU RSS mask, fresh index
; ===== PATCHED (0x140002E78) =====
0000000140002EA0 mov rbx, rcx
0000000140002EA3 call mm_get_current_cpu_idx
0000000140002EA8 call mm_get_current_cpu_idx ; capture moved to entry
0000000140002EAD mov ebp, eax ; saved in ebp
; ... added validity guards before each per-CPU access, e.g.:
; cmp ebp, [rbx+off] / jnz skip
; cmp [rbx+rbp*4+off], r12d / jz skip
; plus RSS servicing refactored with lock cmpxchg
ClearActiveSqAndMapping — Removed block (unpatched only)
; ===== UNPATCHED TBT writeback (0x1400427FE first of two instances) =====
00000001400427FE cmp [rdi+57160h], r10d ; TBT support flag
...
0000000140042810 cmp dword ptr [r13+0], 0BCEA7765h ; magic validation
...
0000000140042827 add rdx, 5C0h
000000014004282E call REG_RD_TBT
0000000140042833 mov [r13+8], eax ; write to ctx+0x8
...
000000014004283E add rdx, 5C4h
0000000140042845 call REG_RD_TBT
000000014004284D mov [r13+0Ch], eax ; write to ctx+0xC
; Second identical block at 0x1400429DD. PATCHED (0x140042A98): both removed.
5. Trigger Conditions
SaveMsiCaps / RestoreMsiCaps (Finding 1)
- Requires a malicious or malformed PCI device that returns an out-of-range MSI capability offset (capability ID 5). A well-formed NetXtreme device returns a valid in-range offset and the code behaves correctly.
- Triggered during driver initialization / adapter reset: disable/re-enable the adapter, or
pnputil /restart-device. - In virtualized environments, an emulated NIC's config space could supply a bad capability pointer.
- Observable effect (unpatched, bad offset only): an out-of-bounds read a few bytes past a legacy 256-byte config window (bounded by the 4 KB extended config mapping);
RestoreMsiCapsadditionally issues a raw config-space write. In practice this needs an attacker who already controls the PCI device (and thus already has DMA), so the marginal impact is small.
CoalescingSendPacketIfNecessary (Finding 2)
- The chip-family identifier
[adapter+0x52960] >> 12must equal9. (On this device family the surrounding0x57xxNetXtreme model comparisons in the same function do not apply; the guard is specific to family 9.) - Coalescing must be reached (flag at
[adapter+0x568EC], unpatched[adapter+0x568BC]). - A fragment entry must have its third 32-bit field (
[r14 + index*12 + 0x10])>= 0x100, and the packet must satisfy the loop preconditions ([r14+8] >= 0xE, fragment count< 0x18). - Effect of the patch: on such a packet the driver takes the non-coalescing send path instead of coalescing. This changes a send-path decision on one chip family; no crash or memory corruption is demonstrable from the fragment field in either build.
UM_Dpc (Finding 3)
- RSS enabled and network traffic flowing to drive DPCs.
- The patch adds validity guards (
cmp ebp, [adapter+off]; jnz skipand a slot check) around the per-CPU array accesses at[adapter + (cpu<<2) + 0x56A38]and[adapter + (idx<<3) + 0x61F8]. In the unpatched build the CPU index is captured fresh immediately before these accesses, so the guards are defensive rather than a fix for an observed out-of-bounds access.
ClearActiveSqAndMapping (Finding 4)
- TBT-capable adapter (flag at
[adapter+0x57160]set) and a context block carrying magic0xBCEA7765. - A send-queue teardown (adapter reset, hang detection, or power transition).
- Effect of the patch: the two TBT register-readback/writeback blocks that would write into the context (
ctx+0x8,ctx+0xC) during teardown are removed.
6. Exploit Primitive & Development Notes
None of the four changes provides a demonstrable exploit primitive in the binaries.
- SaveMsiCaps / RestoreMsiCaps: at most a bounded out-of-bounds read (and, for
RestoreMsiCaps, a raw config-space write) that requires an attacker who already controls the PCI device. This is an information-disclosure-adjacent hardening change, not a usable memory-corruption primitive for an unprivileged local or remote attacker. - CoalescingSendPacketIfNecessary: the added guard only alters a coalescing decision on chip family 9. The bound-checked fragment field is stored to a stack qword that is zeroed (
and [rbp+var_20], 0) before the coalescing path runs, and the ring bookkeeping is identical between builds. There is no attacker-controlled write index and no primitive. - UM_Dpc: the change adds index-validity guards and refactors RSS servicing. The unpatched index is captured fresh before use; no controllable out-of-bounds access is present.
- ClearActiveSqAndMapping: a removed register writeback. The binaries do not show
r13being freed before the write, and the code validates a type magic before writing, so a use-after-free is not established.
7. Debugger Playbook
Setup: Kernel debugger attached to the target. Load symbols for
b57nd60a.sys. Confirm the driver base withlm b57nd60a. The preferred base in the analyzed image is0x140000000; the driver is relocated at runtime, so subtract the analyzed base and add the loaded base for every offset below.
Finding 1: SaveMsiCaps / RestoreMsiCaps
bp b57nd60a!SaveMsiCaps
bp b57nd60a!RestoreMsiCaps
Inspect rcx (adapter), [rcx+0x20] (config_base), and al after FindPciCapMem.
| Register / Memory | Meaning |
|---|---|
al / r8d |
capability offset returned by FindPciCapMem (byte). Valid MSI offsets are aligned and inside config space. |
qword [rbx+0x20] |
config_base — virtual address of the PCI config mapping. |
rdx after add rdx, [rbx+0x20] |
the kernel VA actually read (unpatched). Compare to the config-space range. |
| Offset | Description |
|---|---|
+0x21E0 |
SaveMsiCaps entry (unpatched) |
+0x21EE |
call FindPciCapMem |
+0x21F3 |
movzx r8d, al — capability offset captured |
+0x21FF |
movzx eax, word [rdx] — first raw deref |
+0x21EC |
SaveMsiCaps entry (patched, MM_ReadConfig32) |
+0x2134 |
RestoreMsiCaps entry (both builds) |
Finding 2: CoalescingSendPacketIfNecessary
bp b57nd60a!CoalescingSendPacketIfNecessary
Inspect r14 (fragment array), r10d (fragment count), rsi (adapter).
| Register / Memory | Meaning |
|---|---|
ebx (patched) / [rbp-0x1c] |
fragment field [r14+index*12+0x10] — the value the patched guard bounds against 0x100. |
dword [rsi+0x52960] >> 0xC |
chip-family identifier. The guard applies only when this equals 9. |
rsi+0x568EC (patched) / rsi+0x568BC (unpatched) |
coalescing flag. |
[rdi+0x1C] |
queue number driving the ring bookkeeping (identical in both builds). |
| Offset | Description |
|---|---|
+0x42B18 |
function entry (unpatched) |
+0x42B88 |
fragment loop head (unpatched); field loaded from [r14+rcx*4+0x10] |
+0x42C49 |
coalescing-flag test (unpatched) — no 0x100 check precedes it |
+0x42E58 |
function entry (patched) |
+0x42F87 |
added cmp ecx,9 / cmp ebx,0x100 / jnb guard (patched) |
+0x4374C |
MP_ProcessSGList (caller) |
+0x45318 |
nd6_LM_HandleTxInterrupts (caller) |
Finding 3: UM_Dpc
bp b57nd60a!UM_Dpc
Compare the CPU index in ebp to the value used at the per-CPU array accesses.
| Offset | Description |
|---|---|
+0x2E53 |
mm_get_current_cpu_idx (unpatched, entry call) |
+0x2FC2 |
mm_get_current_cpu_idx (unpatched) — fresh capture before array use |
+0x2FD0 |
mov ebp, eax (unpatched) |
+0x2FE3 |
[rbx+rbp*4+0x56A38] — per-CPU RSS mask |
+0x2EA8 |
second mm_get_current_cpu_idx (patched, capture at entry) |
+0x2EAD |
mov ebp, eax (patched) |
Finding 4: ClearActiveSqAndMapping
bp b57nd60a!ClearActiveSqAndMapping
| Offset | Description |
|---|---|
+0x426AC |
entry (unpatched) |
+0x427FE |
first TBT writeback block (unpatched) |
+0x429DD |
second TBT writeback block (unpatched) |
+0x42A98 |
entry (patched; blocks removed) |
| Register / Memory | Meaning |
|---|---|
r13 |
context pointer; check with !pool r13 whether it is a live allocation. |
dword [rdi+0x57160] |
TBT support flag; must be non-zero to reach the block. |
dword [r13] |
magic; must be 0xBCEA7765. |
8. Changed Functions — Full Triage
Security-Relevant Hardening (3 functions)
| Function | Similarity | Change Type | Note |
|---|---|---|---|
SaveMsiCaps |
0.6786 | Hardening | Raw config_base + FindPciCapMem_offset dereferences replaced with MM_ReadConfig32 at fixed offsets. Removes an out-of-bounds read reachable only via a malformed capability offset. |
RestoreMsiCaps |
0.9468 | Hardening | Companion: raw config-space read and write via FindPciCapMem offset replaced with MM_ReadConfig32 / MM_WriteConfig32. Gated on chip family 8/9. |
CoalescingSendPacketIfNecessary |
0.9786 | Hardening | Adds a chip-family-9-gated guard (cmp ecx,9 / cmp ebx,0x100 / jnb) that forces the non-coalescing decision for a large fragment field. Changes only a coalescing decision; downstream ring code is identical. |
Defensive Refactors (2 functions)
| Function | Similarity | Change Type | Note |
|---|---|---|---|
UM_Dpc |
0.9181 | Refactor | Moves CPU-index capture to entry, adds index-validity guards before per-CPU array accesses, and refactors RSS servicing (lock cmpxchg). Unpatched captured the index fresh before use. |
ClearActiveSqAndMapping |
0.9157 | Removal | Removes two TBT register-readback/writeback blocks during send-queue teardown. No freed-memory access is demonstrable in the binaries. |
Callers of CoalescingSendPacketIfNecessary (2 functions)
| Function | Similarity | Change Type | Note |
|---|---|---|---|
MP_ProcessSGList |
0.9284 | Behavioral | Direct caller (call at 0x140043B21). Differs by register allocation and structure-offset shifts. |
nd6_LM_HandleTxInterrupts |
0.9566 | Behavioral | TX completion path; also calls CoalescingSendPacketIfNecessary (call at 0x140045A2D). Offset shifts. |
Behavioral (Non-Security, 5 functions)
| Function | Similarity | Change Type | Note |
|---|---|---|---|
UM_GenTimerFunction |
0.8622 | Behavioral | Additional stack-frame setup, security-cookie restructure. Timer logic equivalent. |
setup_msix |
0.8828 | Behavioral | MSI-X CPU affinity: precomputes total processor count and adds a minimum-clamp for the single-group case. |
UM_CheckForHang |
0.9175 | Behavioral | Adds an RX-hang check in a specific device state; adds guard *(arg1+0x57AF4) != 5. Chip-ID thresholds updated. |
SetVmqParam |
0.9837 | Behavioral | Adds guard !*(arg1+0x571A4) before RSS register config; variable-reuse optimization. |
nd6_ProcessSendEvents |
0.9735 | Behavioral | Register allocation and offset shifts from the structure layout change. No logic change. |
Cosmetic / Register Allocation (9 functions)
The following functions show only offset shifts, variable renames, or register-allocation changes with no meaningful logic differences: syncReenableIntr (0.9766), UM_Isr (0.9766), UM_DpcMsi (0.985), RestoreDefaultVmqFilter (0.992), UM_Initialize (0.9922), UM_QueryInformation (0.9924 — 1472 diff lines but no logic change), SetDefaultVmqFilter (0.9929), LM_ResetAdapter (0.993), LM_SetPowerState (0.9786). These are consistent with the adapter-context structure-offset shift (several fields moved by a small delta) combined with compiler register-allocation differences.
9. Unmatched Functions
No functions were added or removed in either direction (unmatched_unpatched: 0, unmatched_patched: 0). All changes are in-place modifications to existing functions.
10. Confidence & Caveats
Overall confidence: High that these are the changes; the changes are low-severity hardening and refactors, not a high-severity exploitable vulnerability.
Rationale
SaveMsiCaps/RestoreMsiCaps: the pattern change (raw pointer arithmetic on a device-supplied byte offset → HAL config API at fixed offsets) is unambiguous and directional. The out-of-bounds read it removes is bounded and requires a malicious/emulated PCI device, so it is low severity.CoalescingSendPacketIfNecessary: the addedcmp ecx,9 / cmp ebx,0x100 / jnbguard is present only in the patched build and is verified instruction-for-instruction. The function computes a coalescing decision; the bound-checked field is zeroed before the coalescing path and the ring bookkeeping is byte-identical between builds, so this is a chip-family-specific coalescing guard, not an out-of-bounds-write fix.UM_Dpc: the unpatched build captures the CPU index fresh immediately before the per-CPU array accesses; the patch relocates the capture and adds validity guards. This is a defensive refactor.ClearActiveSqAndMapping: the two removed TBT writeback blocks are verified. No free of the target context is visible in the binaries, and the magic-value gate argues against a naive use-after-free.
Assumptions
- The chip-family identifier is
[adapter+0x52960] >> 12. InCoalescingSendPacketIfNecessarythe surrounding comparisons use NetXtreme model values (0x5717,0x5761,0x5762,0x5784,0x57766…), while the new guard andRestoreMsiCapsuse family8/9. These are distinct device families; a given device presents one value. - The adapter-context structure grew between builds, shifting many field offsets by a small constant delta (e.g.
+0x57170→+0x571A0,+0x568BC→+0x568EC,+0x57BB0→+0x57BF8). This accounts for most of the cosmetic diffs. - The driver is not position-independent; the preferred base is
0x140000000but it is relocated at runtime.
What to verify manually
- For
SaveMsiCaps/RestoreMsiCaps: confirm thatFindPciCapMemcan return an out-of-range offset only under a malformed/emulated device, and that the fixed offsets0x58–0x64match the target's MSI/MSI-X capability layout. - For
CoalescingSendPacketIfNecessary: confirm the fragment-entry layout ([r14 + index*12 + {0x8,0xC,0x10}]) against the actual send-path fragment structure, and that the third field is never used as a write index downstream (it is zeroed viaand [rbp+var_20], 0before the coalescing path in both builds). - For
UM_Dpc: confirm the addedcmp ebp, [adapter+off]; jnzguards bound the CPU index against a stored processor count. - For
ClearActiveSqAndMapping: trace the context (0xBCEA7765) allocation/free lifecycle to determine whether the removed writeback could ever have targeted freed memory.