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

sf_rxeof() trusts DMA-written 11-bit endidx: sf_newbuf() OOB-writes mbuf pointer + DMA address up to 12KB past the entire contigmalloc'd descriptor block

Summary

sf_rxeof indexes the 256-entry RX ring with the unchecked 11-bit DMA-written sf_endidx (0..2047). sf_newbuf then writes the kernel mbuf pointer, DMA address, and valid bit at the OOB address - endidx=2047 lands 12,272 bytes past the END of the whole 40,960-byte contigmalloc allocation into adjacent kernel heap with semi-controlled content. Distinct from DF-1526 (frame length/buffer programming, not the descriptor index). PROVEN on the guest harness (mode 3): live layout facts + canary bytes showing exactly the planted sf_newbuf stores at +12272 past the block. Bounds-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4326 Β· 11 files
FileTypeDescriptionSize
Makefile β€” 147 B ↓ download
README.md β€” 1.0 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
build.log β€” 271 B view raw
build.sh β€” 103 B view raw
env.txt β€” 457 B view raw
fix.diff β€” 1022 B view raw
run.log β€” 1.3 KB view raw
run.sh β€” 133 B view raw
sf_ring_harness.c β€” 10.5 KB view raw
verdict.json β€” 2.3 KB view raw

DF-4326 β€” sf(4) RX completion endidx OOB write past the whole descriptor block

File: sys/dev/netif/sf/if_sf.c:940 (sf_rxeof)

cur_rx->sf_endidx is an 11-bit buffer-descriptor index DMA-written by the AIC-6915. The driver does desc = &sf_rx_dlist_big[sf_endidx] with no bounds check against SF_RX_DLIST_CNT (256). Values 256..2047 dereference up to 1792 entries past the array; sf_newbuf() (if_sf.c:892-894) then WRITES there: an mbuf pointer (8 bytes), a 30-bit DMA address + valid bit. endidx=2047 lands 12,272 bytes past the END of the entire 40,960-byte contigmalloc'd struct sf_list_data β€” i.e. straight into adjacent kernel heap, with semi-controlled content.

Distinct from DF-1526 (that finding covers the length sf_len and the RXDQ buffer-length misprogramming; this is the descriptor index).

Build / Run

Same module as DF-4325. sysctl kern.sfrh.run=3 Expected (run.log): canary bytes past the block end clobbered with exactly the planted mbuf pointer/DMA address; "OOB WRITE PAST sf_list_data PROVEN".

VERDICT.md
↓ download raw

DF-4326 β€” verdict: REPRODUCED (harness on live kernel; hardware-gated path)

Root cause (path:line)

  • sys/dev/netif/sf/if_sf.c:940 β€” desc = &sc->sf_ldata->sf_rx_dlist_big[cur_rx->sf_endidx]; sf_endidx is u_int32_t sf_endidx:11 (if_sfreg.h:728-739) = 0..2047; sf_rx_dlist_big has SF_RX_DLIST_CNT = 256 entries of 16 bytes at offset 20480 of struct sf_list_data (total 40960 bytes, if_sfreg.h:988-1003).
  • sys/dev/netif/sf/if_sf.c:947/953 β€” sf_newbuf(sc, desc, m) stores desc->sf_mbuf (kernel pointer), desc->sf_addrlo (30-bit DMA address), desc->sf_valid = 1 at the OOB address (stores at if_sf.c:892-894).

What was proven on the guest (kern.sfrh.run=3, run.log)

  • Layout verified live: sf_list_data = 40960 bytes; rx_dlist_big spans +20480..+24576.
  • endidx=2047 -> desc at blk+53232 = 12,272 bytes past the end of the whole struct sf_list_data allocation.
  • The exact sf_newbuf stores landed there: canary at +53232..53235 shows the bitfield word (0x2badb002|valid), +53240..53243 shows the planted mbuf pointer 0xabcd11112222 β€” "12/16384 canary bytes clobbered -> OOB WRITE PAST sf_list_data PROVEN".
  • gcc -Warray-bounds statically proves the index expression too (build.log).

Impact

Hardware-gated (NIC DMA) OOB WRITE of {pointer, DMA address, flag} at NIC-chosen offsets up to ~28KB past the ring / ~12KB past the whole DMA block into adjacent kernel heap. On real hardware this is a kernel heap-corruption primitive (content semi-controlled: real mbuf pointer + vtophys-derived address); realistic ceiling: crash to heap corruption. Not reachable to uid=0 on this guest (no AIC-6915 NIC present) β€” chain not attempted past the demonstrated primitive.

Fix

Bounds-check cur_rx->sf_endidx before the dereference (fix.diff).

Fix validation

fix_status=not_testable: fix targets sf_rxeof(), which executes only on AIC-6915 hardware absent from the guest; harness replicates driver code in a module and cannot observe sys/ changes.

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 reproduced→ patch + rebuild →patched clean

fix targets sf_rxeof() executing only on AIC-6915 hardware absent from the guest

fix.diff authored (endidx bounds check + resync)
↓ fix.diffper-fix-DF-4326

Confirmed kernel references

Detail

Exploit chain

on-hw chain (not exercisable on guest): NIC DMA emits RX completion with endidx in 256..2047 -> sf_newbuf OOB-writes kernel pointer + DMA address at NIC-chosen offset past the descriptor block -> adjacent-kernel-heap corruption -> crash..privesc depending on victim object

Evidence (decisive lines)

run.log (layout facts, desc at blk+53232 = +12272 past block end, canary clobber list), build.log (gcc array-bounds static proof), sf_ring_harness.c

PoC changes

shared module with DF-4325; RX path replicates desc fetch + the three sf_newbuf stores

Verified recommended fix

bounds-check cur_rx->sf_endidx against SF_RX_DLIST_CNT before the descriptor dereference

Verdict

REPRODUCED on the live guest kernel via harness: sf_rxeof's unchecked 11-bit DMA-supplied endidx (if_sf.c:940) indexes the 256-entry RX buffer ring with values up to 2047; sf_newbuf's stores then WROTE {mbuf ptr, 30-bit DMA addr, valid bit} at endidx=2047 -> 12,272 bytes PAST the end of the entire 40,960-byte contigmalloc'd sf_list_data block, verified by canary corruption with exactly the planted values. Impact classed panic (heap corruption primitive; on-hw ceiling crash..corruption). Distinct from DF-1526 (length/buffer-programming, not index).