oce_rx builds mbuf chain with m_pkthdr.len from the CQE 14-bit pkt_size unclamped to num_fragments x frag_size: chain up to 2047 bytes shorter than claimed, handed to if_input β stack offset-walkers (m_copydata KASSERT) panic, TCP desynchronizes
Summary
oce_rx takes pkt_size (14-bit) verbatim, loops num_fragments (3-bit, max 7) posting min(len,2048) per cluster, then sets the first mbuf's m_pkthdr.len to the full pkt_size. 7x2048=14336 < 16383 max pkt_size, so a firmware-shaped CQE (pkt_size=16383, num_fragments=1) delivers a chain claiming up to 2047 bytes more than it contains; m_copydata/m_copym consumers KASSERT-panic and tcp_input's tif_len desyncs. Distinct from DF-1371 (needs no RQ-array inconsistency). Residual-check drop fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4079 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| df4079_oce_poc.c | β | 13.6 KB | view raw | |
| Makefile | β | 163 B | β download | |
| build.sh | β | 179 B | view raw | |
| run.sh | β | 206 B | view raw | |
| build.log | β | 848 B | view raw | |
| run.log | β | 1.5 KB | view raw | |
| panic.txt | β | 643 B | view raw | |
| panic_console_full.log | β | 2.6 KB | view raw | |
| fix.diff | β | 727 B | view raw | |
| fix_build.log | β | 5.8 MB | β download | |
| fix_run.log | β | 2.9 KB | view raw | |
| env.txt | β | 423 B | view raw | |
| manifest.json | β | 1.7 KB | view raw | |
| verdict.json | β | 4.9 KB | view raw |
DF-4079 β oce_rx: CQE pkt_size vs num_fragmentsΓfrag_size desync builds an
inconsistent mbuf chain (m_pkthdr.len > Ξ£ m_len) handed to the network stack
File: sys/dev/netif/oce/oce_if.c (pass 2)
Class: memory-safety contract violation β stack offset-walker panic
(m_copydata KASSERT) / TCP sequence desync
VERIFIED: reproduced (baseline + panic) on the DFly QEMU guest via the
production-handler KLD dispatch rig; fix validated on a rebuilt kernel.
How it was verified without OneConnect hardware
The guest kernel is built with device oce (X86_64_GENERIC), so the driver's
production code β including the static oce_rx() β lives in the RUNNING
kernel image. The KLD rig (df4079_oce_poc.c):
- references the kernel's own
oce_rq_handler(),oce_alloc_rx_bufs(),oce_create_ring_buffer(),oce_destroy_ring_buffer()andoce_free_posted_rxbuf()as extern symbols (kernel-loader bound β nothing recompiled); - fabricates the post-attach softc exactly as production leaves it (BE3
flags, frag_size 2048, verbatim
oce_rq_initDMA replay, real busdma rings from the kernel's own ring allocator, MMIO BAR stand-in pages); - posts real RX clusters via the REAL
oce_alloc_rx_bufs(); - crafts the CQE bitfields the way firmware DMA-writes them:
pkt_size=16383, num_fragments=1andpkt_size=16383, num_fragments=7; - dispatches the REAL
oce_rq_handler(rq)(staticoce_rxruns inside); - captures the delivered chain in an if_input shim and prints claimed (m_pkthdr.len) vs actual (Ξ£ m_len).
Results (stock kernel #0 Thu Jul 2 06:02:54 UTC 2026)
- case 1 (pkt_size=16383, nf=1): chain 2048 bytes, claims 16383 β 14335 phantom bytes
- case 2 (pkt_size=16383, nf=7): chain 7Γ2048=14336 bytes, claims 16383 β 2047 phantom bytes (the finding's headline gap)
- panic mode: the real stack offset-walker
m_copydata(m, off=2056, 8)β the exact consumer cited (sys/kern/uipc_mbuf.c:1679, kernel built with INVARIANTS) β panics:panic: m_copydata: offset > size of mbuf chainwith backtracem_copydata() β df4079_input() β oce_rx.isra.10() β oce_rq_handler()β productionoce_rxbuilt the chain, productionm_copydatadied on it.
Fix validation (fix.diff, fix_build.log, fix_run.log)
fix.diff adds the residual-length check to oce_rx: if len != 0 after
the fragment loop, the chain is freed and dropped. Applied to the guest's
/usr/src (identical to the audit tree), kernel rebuilt
(make -j6 nativekernel KERNCONF=X86_64_GENERIC) and installed, booted
into #1: Thu Oct 15 13:06:11 UTC 2026. The SAME rig re-run against the
patched kernel's own oce_rq_handler (address re-resolved at module load):
both crafted CQEs dropped β chains delivered to if_input: 0; invariant
violations: 0; rx_pkts=0 β FIX CONFIRMED; panic mode on the patched
kernel SURVIVES (no chain β no m_copydata dispatch β no panic).
Reproduce
vm.sh run_root 'sh /root/df4079/build.sh' # build the KLD vm.sh run_root 'sh /root/df4079/run.sh baseline' # survives, prints violations vm.sh reset with-src # fresh boot for the panic run vm.sh run_root 'sh /root/df4079/run.sh panic' # kernel panic
Threat model unchanged from the finding: pkt_size/num_fragments are firmware/DMA-controlled; the driver must not trust the completion.
DF-4079 VERDICT β oce_rx pkt_size/fragment-count desync
Status: REPRODUCED (baseline invariant-violation + m_copydata panic; fix validated on rebuilt kernel)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 (stock) and #1 (patched), x86_64.
Method: production-handler KLD dispatch rig (the DF-4106/DF-4266/if_et
evidence class) β no OneConnect hardware needed because device oce is
compiled into the guest's X86_64_GENERIC kernel.
What ran (all production code, kernel-loader bound)
- Rig externs:
oce_rq_handler,oce_alloc_rx_bufs,oce_create_ring_buffer,oce_destroy_ring_buffer,oce_free_posted_rxbufβ resolved against the running kernel's symbol table at kldload (verified: dispatched handler address 0xffffffff805c9e50 = nm(1)T oce_rq_handler). - Fabricated post-attach state: BE3 softc (flags, ring sizes,
rq_frag_size=2048 per oce_if.c:236), verbatim
oce_rq_initDMA replay (oce_queue.c:429-455: same tag params, 1024 dmamaps, real ring from the kernel's ownoce_create_ring_buffer), RX CQ ring (256Γ16B), real ifnet with an if_input inspection shim, MMIO BAR stand-in pages (register offsets all < 0x140, oce_hw.h:64-88). - Posted 16 real clusters via REAL
oce_alloc_rx_bufs(m_getcl + bus_dmamap_load_mbuf_segment + RQEs + doorbell to the stand-in page). - Crafted CQEs as firmware would DMA them (DW_SWAP is a no-op on LE,
oce_if.h:1114): case 1
pkt_size=16383, num_fragments=1, valid=1; case 2pkt_size=16383, num_fragments=7, valid=1. - Dispatched REAL
oce_rq_handler(rq)β staticoce_rx(confirmed in the panic backtrace asoce_rx.isra.10) βsc->ifp->if_input(...).
Observed (stock kernel, run.log / panic.txt)
- case 1: delivered chain
[m_len=2048]withm_pkthdr.len=16383β 14335 phantom bytes. - case 2: delivered chain of 7Γ2048=14336 bytes with
m_pkthdr.len=16383β 2047 phantom bytes (max representable chain data 7Γ2048=14336 < 16383=max 14-bit pkt_size: the trust-boundary gap). rq->rx_stats.rx_bytes += 32766β stats count phantom bytes (oce_if.c:1381).- Deterministic: baseline run twice, identical results.
- panic mode (panic.txt):
panic: m_copydata: offset > size of mbuf chainβ the exact cited KASSERT (sys/kern/uipc_mbuf.c:1679-1680, INVARIANTS kernel), backtracem_copydata() β df4079_input() β oce_rx.isra.10() β oce_rq_handler(). The rig's m_copydata call models any offset-walking stack consumer (forwarding/fragmentation/ICMP-error paths) that trusts pkthdr.len.
Impact ceiling on this guest
Kernel panic (DoS) demonstrated. Escalation beyond DoS would require the frame bytes (also firmware-controlled) to steer a stack consumer into an OOB copy β the primitive is the 2047-byte phantom region the stack will read past the chain's real end; on this guest the walk hits the KASSERT first. Threat model: buggy/malicious NIC firmware or device DMA; not remotely reachable without the NIC writing the CQE.
Fix validation (fix.diff / fix_build.log / fix_run.log)
fix.diff = residual-length check in oce_rx (drop when len != 0 after
the fragment loop). Applied to guest /usr/src (md5-identical to audit tree
before patch), make -j6 nativekernel KERNCONF=X86_64_GENERIC +
installkernel (full 41592-line log: fix_build.log), rebooted into
#1: Thu Oct 15 13:06:11 UTC 2026.
- baseline on patched kernel: both crafted CQEs dropped β
chains delivered to if_input: 0; invariant violations: 0; rx_pkts=0; rx_bytes=0βFIX CONFIRMED(handler address moved to 0xffffffff805c9e80 = the rebuilt kernel's own oce_rq_handler). - panic mode on patched kernel: SURVIVED (no chain ever reaches if_input, so the m_copydata dispatch never happens).
Distinct from DF-1371 (pckts[] array over-walk): DF-4079 needs no RQ-array inconsistency β the RQ walk here is perfectly legal; only the chain length contract is violated.
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
fixedfix.diff (residual-length drop in oce_rx) applied to guest /usr/src, kernel rebuilt via make -j6 nativekernel KERNCONF=X86_64_GENERIC (full log fix_build.log) and rebooted (#1 Thu Oct 15 13:06:11 UTC 2026); identical rig re-dispatched against the patched kernel's own oce_rq_handler: both crafted CQEs dropped (chains delivered=0, violations=0, rx_pkts=0, rx_bytes=0, FIX CONFIRMED) and panic mode SURVIVES. Every defective behavior gone.
fix_run.log (FIX CONFIRMED markers on kernel #1); fix_build.log (BUILD_DONE_OK, kernel install completed 13:11:22); panic.txt vs fix survival (no second panic.txt exists β the patched panic-mode run completed with GO_RC=0).
Confirmed kernel references
- sys/dev/netif/oce/oce_if.c:1260
- sys/dev/netif/oce/oce_if.c:1274
- sys/dev/netif/oce/oce_if.c:1290
- sys/dev/netif/oce/oce_if.c:1291
- sys/dev/netif/oce/oce_if.c:1299
- sys/dev/netif/oce/oce_if.c:1373
- sys/dev/netif/oce/oce_if.c:1381
- sys/dev/netif/oce/oce_hw.h:2531
- sys/dev/netif/oce/oce_hw.h:2549
- sys/kern/uipc_mbuf.c:1679
Detail
Exploit chain
firmware/DMA-shaped RX CQE (pkt_size=16383, num_fragments=1..7) -> production oce_rq_handler/oce_rx builds a legal-RQ mbuf chain whose m_pkthdr.len exceeds its data by up to 2047 bytes -> if_input -> any offset-walking consumer (m_copydata/m_copym: forwarding, fragmentation, ICMP-error) reads past the chain's real end -> KASSERT panic (demonstrated); with frame bytes also firmware-controlled, silent OOB reads of adjacent cluster data / TCP sequence desync are the same primitive short of the assert.
Evidence (decisive lines)
run.log (stock baseline: both CHAIN INVARIANT VIOLATION markers with per-mbuf m_len dumps); panic.txt (panic: m_copydata: offset > size of mbuf chain + backtrace oce_rx.isra.10 <- oce_rq_handler); fix_run.log (patched kernel: chains=0 violations=0 FIX CONFIRMED); fix_build.log (full 41592-line nativekernel build, BUILD_DONE_OK); panic_console_full.log (full serial console of the panic run)
PoC changes
Seed pack had no PoC (marked hardware-gated/untested). Replaced with the production-handler KLD dispatch rig: extern-binds the kernel's own oce_rq_handler + posting/ring helpers, fabricates the post-attach softc (verbatim oce_rq_init DMA replay), posts real clusters via REAL oce_alloc_rx_bufs, crafts CQEs in DMA memory, dispatches the real handler; if_input shim proves pkthdr.len > sum(m_len); panic mode dispatches the real m_copydata.
Verified recommended fix
In oce_rx, after the fragment loop, drop the chain when residual len != 0 (pkt_size exceeded num_fragments x frag_size); never set m_pkthdr.len above the chain's real data.
Verdict
REPRODUCED via the production-handler KLD dispatch rig (DF-4106/DF-4266 evidence class): the guest kernel ships device oce, so the rig binds the kernel's OWN oce_rq_handler()/oce_alloc_rx_bufs()/ring-allocator as externs over a fabricated post-attach softc (BE3 flags, verbatim oce_rq_init DMA replay, real busdma rings, real posted clusters, MMIO BAR stand-ins) and dispatches it on firmware-shaped CQEs. Stock kernel: pkt_size=16383/num_fragments=1 delivered a 2048-byte chain claiming 16383 (14335 phantom bytes); pkt_size=16383/num_fragments=7 delivered 14336 bytes claiming 16383 (2047 phantom bytes β the max 7x2048<16383 trust-boundary gap); rx_bytes counted the phantom length. Panic mode: the cited stack offset-walker m_copydata (sys/kern/uipc_mbuf.c:1679 KASSERT, INVARIANTS kernel) panicked with backtrace m_copydata() <- df4079_input() <- oce_rx.isra.10() <- oce_rq_handler() β production oce_rx built the chain, production m_copydata died on it. Fix (residual-length drop in oce_rx) validated on a rebuilt+rebooted kernel: both crafted CQEs dropped (chains=0, violations=0, rx_pkts=0, FIX CONFIRMED) and the panic mode survives. Threat model unchanged: firmware/DMA-controlled CQE; not remotely reachable without the NIC.
No comments yet.