β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3718

mxge_tx_done walks the TX ring toward the unvalidated NIC send_done_count: premature TX-buffer free (device DMA-after-free) and permanent interrupt-context spin under tx/main serializers

Summary

mcp_idx is (int)send_done_count from the NIC-DMA'd fw_stats block. mxge_tx_done loops 'while (tx->pkt_done != mcp_idx)' with no relation to tx->req: a counter ahead of pkt_done unloads dmamaps and m_frees still-queued descriptors -> device DMA into freed mbufs; a counter that never matches spins forever once a ring revolution leaves every info[].m NULL - holding tx_serialize (and main_serialize on MSI/legacy) permanently. DF-3680/3714 family shape; idx stays masked so host-side damage is the freed-mapping reuse, not an OOB write. Occupancy-guard fix git-apply-clean.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3718 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
fix.diff β€” 524 B view raw
verdict.json β€” 2.0 KB view raw
manifest.json β€” 820 B view raw
env.txt β€” 583 B view raw

DF-3718 β€” mxge_tx_done() walks the TX ring toward an unvalidated NIC completion counter

Vulnerability

sys/dev/netif/mxge/if_mxge.c mxge_tx_done() (:2364-2415):

while (tx->pkt_done != mcp_idx) {        /* :2369 */
        idx = tx->done & tx->mask;
        tx->done++;
        m = tx->info[idx].m;
        if (m != NULL) {                  /* first-segment slots only */
                tx->pkt_done++;
                ...
                bus_dmamap_unload(tx->dmat, tx->info[idx].map);
                m_freem(m);               /* :2386 */
        }
}

mcp_idx is (int)send_done_count β€” the firmware statistics block's packet-completion counter, NIC-DMA'd data (mcp_irq_data_t.send_done_count, mxge_mcp.h) β€” read at :2695/:2767/:2844 and passed from all three interrupt paths (mxge_legacy :2699, mxge_msi :2770, mxge_msix_rxtx :2847). There is no bound relating mcp_idx to tx->pkt_done/tx->req:

  1. Free of unsent descriptors (DMA-after-free). A counter even a few packets ahead makes the loop walk forward, unloading dmamaps and freeing mbufs for descriptors that are still queued-but-unsent. The NIC keeps owning those DMA mappings β†’ post-free DMA reads of freed mbuf memory (content disclosure to the wire / corruption), the same shape as DF-3714 (em tx_collect) and DF-3680 (igb txeof).
  2. Infinite interrupt-context spin. pkt_done only advances on non-NULL info[idx].m; after one full ring revolution every slot has m == NULL, pkt_done freezes, and if the NIC's counter still doesn't match (garbage, desync), the while spins forever β€” holding tx->tx_serialize (entered at :2769/:2846) and, in the MSI/legacy paths, main_serialize (mxge_msi asserts it at :2737) β€” wedging the interface and blocking every thread that later tries to take those serializers. tx->done/idx stay masked (no OOB write), so the failure mode is a permanent spin/DoS, not memory corruption on the host side.

Threat model

NIC-DMA'd counter (hostile/compromised device or device fault), identical trust boundary to DF-3680/DF-3714 (both filed Low/certain).

Verification

Hardware-gated (no Myricom NIC on the guest; module never attaches) β€” source-certain only, family convention β†’ inconclusive.

Fix

fix.diff bounds the walk by ring occupancy (tx->done >= tx->req β†’ warn + break), which kills the runaway spin; a mismatched-but-in-range counter still must be dealt with by the watchdog. git-apply clean.

VERDICT.md
↓ download raw

DF-3718 VERDICT

Status: inconclusive (hardware-gated, family convention β€” cf. DF-3680 Low/certain, DF-3714 Low/certain), confidence: certain source-level.

Source proof

  • mcp_idx originates from the NIC-DMA'd mcp_irq_data.send_done_count (read be32toh(stats->send_done_count) at if_mxge.c:2695/2767/2844, cast (int) and passed at :2700/:2770/:2847).
  • mxge_tx_done (:2364-2415) loops while (tx->pkt_done != mcp_idx) with no relation to tx->req β€” the driver's own knowledge of queued descriptors. Free path per iteration: bus_dmamap_unload + m_freem on any non-NULL info[idx].m (:2381-2387).
  • Forward-walk consequence A: descriptors still owned by the NIC get their maps unloaded and mbufs freed β†’ device DMA into freed memory.
  • Consequence B: pkt_done advances only on non-NULL m; after one revolution all slots are NULL β†’ permanent interrupt-context spin under tx_serialize (and main_serialize for MSI/legacy paths, :2737) β†’ interface and any serializer-acquiring threads wedge.

Not reproduced

Guest has no Myricom device (see env.txt); the stats block is device-written. No software-only trigger.

Fix

fix.diff: occupancy guard if (tx->done >= tx->req) break; + warning. Mitigates the infinite spin; full validation of the counter against req is the watchdog's job. fix_status=not_testable (no hardware).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff git apply --check clean; not buildable without mxge hardware.

findings/poc/DF-3718/fix.diff
↓ fix.diffper-fix-DF-3718

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-3718/VERDICT.md; fix.diff (occupancy guard, git-apply clean); env.txt

PoC changes

No seed PoC; hardware-gated.

Verified recommended fix

Guard the walk with ring occupancy: if (tx->done >= tx->req) break (warn), before consuming the next slot.

Verdict

Source-certain, hardware-gated (DF-3680/DF-3714 family): mxge_tx_done (if_mxge.c:2364-2415) walks the TX completion ring toward mcp_idx = (int)send_done_count, the NIC-DMA'd firmware stats counter (read :2695/:2767/:2844, passed :2700/:2770/:2847), with no bound against tx->req. A counter ahead of pkt_done unloads dmamaps and frees mbufs of still-queued descriptors (device DMA-after-free); a counter that never matches pkt_done (which freezes once a full revolution leaves every info[].m NULL) spins forever in interrupt context holding tx->tx_serialize and, on MSI/legacy, main_serialize - permanent interface/kernel-thread wedge. Guest lacks Myricom hardware; not runtime-testable (inconclusive per family convention).