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)
PoC verification
Evidence pack
findings/poc/DF-4326 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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".
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_endidxisu_int32_t sf_endidx:11(if_sfreg.h:728-739) = 0..2047;sf_rx_dlist_bighas SF_RX_DLIST_CNT = 256 entries of 16 bytes at offset 20480 ofstruct 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)storesdesc->sf_mbuf(kernel pointer),desc->sf_addrlo(30-bit DMA address),desc->sf_valid = 1at 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_testablefix targets sf_rxeof() executing only on AIC-6915 hardware absent from the guest
fix.diff authored (endidx bounds check + resync)
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).
No comments yet.