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

dc_pnic_rx_bug_war unbounded copy loop - heap overflow if PNIC produces >5 descriptors

Summary

dc_pnic_rx_bug_war at if_dc.c:2418-2429: while(1) { bcopy(mtod(m,char*),ptr,DC_RXLEN); ptr+=DC_RXLEN; } with NO iteration cap. Buffer is kmalloc(DC_RXLEN*5=7680). If PNIC 82c168/169 chip produces >5 descriptors for a corrupted RX event, ptr overflows into adjacent kernel heap. Also total_len=DC_RXBYTES(rxstat) at :2432 (14-bit 0-16383) used in bcopy(ptr,mtod(m,char*),total_len) at :2453 without checking <mbuf area (~2040B) -> cluster overflow. Trigger: flood PNIC NIC to hit known FIFO bug during heavy activity. Speculative: depends on chip producing >5 extra descs. Fix: cap loop at 5 iterations, clamp total_len.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1249 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict source trace, mechanism, impact ceiling, fix rationale 3.3 KB ↓ raw
fix.diff suggested-fix cap dc_pnic_rx_bug_war loop at 5 iterations (DC_RXLEN*5 buffer) 962 B view raw
fix_build.log build-log if_dc.ko build with fix applied (52696 bytes, RC=0) 6.7 KB view raw
build.sh build-log repro: apply-check + note 378 B view raw
run.sh run-log no runtime trigger (no PNIC NIC) 404 B view raw
env.txt environment uname, cc version, module state 359 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict source trace, mechanism, impact ceiling, fix rationale
↓ download raw

DF-1249 β€” dc_pnic_rx_bug_war unbounded copy loop (heap overflow if PNIC produces >5 fragments)

Verdict

SOURCE-CONFIRMED (real code-level hardening gap, speculative exploitation), INCONCLUSIVE at runtime β€” the if_dc driver IS compiled into the running GENERIC kernel, but the guest's NIC is vtnet0 (virtio-net); no DEC 21143 / PNIC 82c168/82c169 NIC is attached, so dc_pnic_rx_bug_war is never called. Fix authored and compile-validated.

Mechanism (source trace)

dc_pnic_rx_bug_war() salvages a corrupted multi-fragment RX frame from the PNIC chip bug by copying fragments into a fixed salvage buffer:

  • sys/dev/netif/dc/if_dc.c:1976 β€” sc->dc_pnic_rx_buf = kmalloc(DC_RXLEN * 5, M_DEVBUF, M_WAITOK); β†’ buffer is 7680 bytes (5 Γ— 1536).
  • sys/dev/netif/dc/if_dc.c:2414-2429: ptr = sc->dc_pnic_rx_buf; /* 7680-byte buffer */ bzero(ptr, DC_RXLEN * 5); while (1) { c = &sc->dc_ldata->dc_rx_list[i]; rxstat = c->dc_status; m = sc->dc_cdata.dc_rx_chain[i]; bcopy(mtod(m, char *), ptr, DC_RXLEN); /* copies 1536 bytes each iteration */ ptr += DC_RXLEN; /* advances 1536, NO bound check */ if (i == idx || rxstat & DC_RXSTAT_LASTFRAG) break; dc_newbuf(sc, i, m); DC_INC(i, DC_RX_LIST_CNT); /* ring is 64 or 192 descriptors */ }
  • The loop's only termination is i == idx (reaching the LASTFRAG descriptor index) or rxstat & LASTFRAG. There is no cap on the number of iterations. The caller (if_dc.c:2525-2533) enters this path when FIRSTFRAG and LASTFRAG are not both set in the same descriptor, walking from the saved FIRSTFRAG index to the LASTFRAG index.
  • If the PNIC chip (or a corrupted descriptor ring) yields more than 5 fragments between FIRSTFRAG and LASTFRAG, ptr walks past the end of the 7680-byte dc_pnic_rx_buf into adjacent kernel heap. The ring (DC_RX_LIST_CNT = 64 or 192, if_dcreg.h:459/461) bounds the worst case to 64–192 iterations β‡’ up to ~98KB–294KB of overflow.
  • Additionally total_len = DC_RXBYTES(rxstat) (if_dc.c:2432, 14-bit) is used unchecked in bcopy(ptr, mtod(m,char*), total_len) at line 2453.

Why not reproduced at runtime

  • Guest NIC is vtnet0 (virtio-net). The dc driver probe never matches a virtio device, so no dc_softc with DC_PNIC_RX_BUG_WAR is ever instantiated.
  • The PNIC RX-bug war is gated by sc->dc_flags & DC_PNIC_RX_BUG_WAR, set only for DC_TYPE_PNIC (if_dc.c:1973-1975), which requires a Lite-On 82c168/82c169 chip.
  • The finding's own confidence is speculative: it requires the PNIC chip to produce >5 descriptors for a single corrupted RX event (worse than the known bug the war was written to handle). This is a driver-robustness issue against misbehaving hardware, not an unprivileged-user-reachable bug.

Fix (fix.diff, compile-validated)

Cap the loop at 5 iterations (matching the DC_RXLEN * 5 buffer) so a misbehaving PNIC cannot overflow the salvage buffer. The patched if_dc.c builds cleanly into if_dc.ko (52696 bytes) with gcc 8.3, -Werror.

Realistic impact ceiling

Kernel heap overflow from a misbehaving PNIC NIC (worst-case ~294KB overflow). On this guest: not reachable (vtnet0 NIC, no PNIC). Defense-in-depth driver hardening.

Fix verification

not_testable

compile validated

module/object build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. dc_pnic_rx_bug_war unbounded loop vs 7680B buffer -> heap overflow. dc in GENERIC, no PNIC NIC.