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

my_stop frees all RX/TX mbufs and zeroes descriptor rings without waiting for the TX/RX engines to go idle β€” trailing DMA into freed clusters (packet-controlled kernel heap corruption)

Summary

my_stop clears the enable bits, then within a few instructions frees all 64 RX clusters and queued TX mbufs and bzeros both rings - no idle poll, no reset. The driver itself performs that exact bounded idle-wait in my_setcfg before merely changing duplex bits, proving the engine rundown is asynchronous. A descriptor already fetched by the engine keeps its latched physical address across the bzero; the NIC can complete the DMA into a freed cluster, and when the cluster is reused the trailing DMA writes packet-controlled bytes into live kernel memory. Exposed on every stop path. Idle-wait-before-free fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4259 Β· 6 files
FileTypeDescriptionSize
trigger.sh β€” 597 B view raw
env.txt β€” 1007 B view raw
VERDICT.md β€” 2.0 KB ↓ raw
fix.diff β€” 840 B view raw
manifest.json β€” 703 B view raw
verdict.json β€” 2.5 KB view raw
VERDICT.md
↓ download raw

DF-4259 VERDICT β€” my_stop frees buffers with DMA engines not proven idle

Status: inconclusive on the audit guest (hardware-gated). Missing quiesce certain; race outcome hardware-timing dependent.

Reproduction attempt

Guest has no Myson device (env.txt): no my0, no DMA. A software-only reproduction is impossible because the hazard is the NIC's DMA engine writing into buffers after the driver has released them.

Analysis

my_stop (if_my.c:1643-1680) clears RE|TE, unmasks nothing, and within a few instructions frees all 64 RX clusters and all queued TX mbufs and bzeros both descriptor rings. It never polls MY_TXRUN/MY_RXRUN (if_myreg.h:58, 82) β€” the exact idle-wait the same driver performs in my_setcfg (if_my.c:705-717) before merely changing duplex bits. The asynchronous engine rundown window is therefore unbounded from the driver's perspective: an RX descriptor already fetched by the engine keeps its my_data physical address (latched before the bzero) and the NIC can complete the DMA into the freed cluster after m_freem returns it to the zone. Reuse of that cluster by any kernel consumer turns the trailing DMA into a write of packet-controlled bytes into live kernel memory.

Reachability: every interface re-init path β€” ifconfig my0 down|up (SIOCSIFFLAGS), media change (my_init from my_autoneg_mii tail, if_my.c:537), watchdog reset (if_my.c:1631-1633), detach, shutdown. Root ioctl + traffic race on real hardware.

Severity Low (hardware-gated, timing window, root-coordinated trigger), CWE-362/CWE-416, confidence likely.

Fix

Wait for TXRUN|RXRUN to clear (my_setcfg-style bounded poll, optionally a chip reset) before freeing the buffers β€” fix.diff.

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 adds the bounded idle-wait before the frees; hardware-gated, not validated here

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

Confirmed kernel references

Detail

Exploit chain

On affected hardware: root races ifconfig down against flood traffic -> NIC DMA writes ethernet-controlled bytes into a reused mbuf cluster (socket buffer / pipe / arbitrary kernel object) -> controlled heap corruption; timing window is one descriptor per stop event.

Evidence (decisive lines)

VERDICT.md (stop path vs my_setcfg discipline contrast); env.txt

PoC changes

trigger.sh runs the down/up + flood race for affected hardware

Verified recommended fix

Poll TXRUN|RXRUN idle (my_setcfg-style bounded loop) before freeing any buffers in my_stop

Verdict

my_stop (sys/dev/netif/my/if_my.c:1643-1680) clears MY_RE|MY_TE, zeroes the list-base registers, then immediately frees all 64 RX clusters and all queued TX mbufs and bzeros both descriptor rings β€” with no wait for MY_TXRUN/MY_RXRUN to clear and no chip reset. The driver's own my_setcfg (705-717) demonstrates the required idle-wait discipline (per the quoted Myson manual note) before even touching duplex bits, so the engine rundown is known asynchronous; a descriptor already fetched by the engine retains its latched my_data physical address across the bzero and can complete its DMA into a freed cluster after m_freem returns it, which becomes packet-controlled kernel heap corruption when the cluster is reused. Exposed on every stop path: my_init via SIOCSIFFLAGS/media change (1417), watchdog (1631), detach (979), shutdown (1692). Not executable on the audit guest (no Myson NIC, env.txt); the missing quiesce is certain at source level, the race outcome is hardware-timing dependent.