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:

  1. UM_Initialize / adapter bring-up or LM_ResetAdapter.
  2. SaveMsiCaps (0x1400021E0) / RestoreMsiCaps (0x140002134).
  3. 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_ProcessSGListCoalescingSendPacketIfNecessary

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:

  1. NDIS send path builds a fragment list.
  2. MP_ProcessSGList (0x14004374C) calls CoalescingSendPacketIfNecessary (call at 0x140043B21).
  3. nd6_LM_HandleTxInterrupts (0x140045318) also calls it (call at 0x140045A2D).

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:

  1. Hardware interrupt → NDIS schedules the DPC.
  2. UM_Dpc (0x140002E28) processes RX/TX completions and RSS queue management.
  3. 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:

  1. UM_CheckForHangLM_ResetAdapterClearActiveSqAndMapping, or direct LM_ResetAdapter.
  2. 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 (0x140042C240x140042C47) 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)

  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.
  2. Triggered during driver initialization / adapter reset: disable/re-enable the adapter, or pnputil /restart-device.
  3. In virtualized environments, an emulated NIC's config space could supply a bad capability pointer.
  4. 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); RestoreMsiCaps additionally 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)

  1. The chip-family identifier [adapter+0x52960] >> 12 must equal 9. (On this device family the surrounding 0x57xx NetXtreme model comparisons in the same function do not apply; the guard is specific to family 9.)
  2. Coalescing must be reached (flag at [adapter+0x568EC], unpatched [adapter+0x568BC]).
  3. 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).
  4. 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)

  1. RSS enabled and network traffic flowing to drive DPCs.
  2. The patch adds validity guards (cmp ebp, [adapter+off]; jnz skip and 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)

  1. TBT-capable adapter (flag at [adapter+0x57160] set) and a context block carrying magic 0xBCEA7765.
  2. A send-queue teardown (adapter reset, hang detection, or power transition).
  3. 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 r13 being 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 with lm b57nd60a. The preferred base in the analyzed image is 0x140000000; 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 added cmp ecx,9 / cmp ebx,0x100 / jnb guard 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

  1. The chip-family identifier is [adapter+0x52960] >> 12. In CoalescingSendPacketIfNecessary the surrounding comparisons use NetXtreme model values (0x5717, 0x5761, 0x5762, 0x5784, 0x57766…), while the new guard and RestoreMsiCaps use family 8/9. These are distinct device families; a given device presents one value.
  2. 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.
  3. The driver is not position-independent; the preferred base is 0x140000000 but it is relocated at runtime.

What to verify manually

  1. For SaveMsiCaps / RestoreMsiCaps: confirm that FindPciCapMem can return an out-of-range offset only under a malformed/emulated device, and that the fixed offsets 0x580x64 match the target's MSI/MSI-X capability layout.
  2. 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 via and [rbp+var_20], 0 before the coalescing path in both builds).
  3. For UM_Dpc: confirm the added cmp ebp, [adapter+off]; jnz guards bound the CPU index against a stored processor count.
  4. For ClearActiveSqAndMapping: trace the context (0xBCEA7765) allocation/free lifecycle to determine whether the removed writeback could ever have targeted freed memory.