Inverted RX descriptor-ownership test in wb_rxeof(): receive path completely broken, per-packet chip reset/reinit storm from interrupt context
Summary
wb_rxeof walks the RX ring while OWN is SET and breaks when CLEAR - the exact inversion of the W89C840F ownership protocol (verified against the FreeBSD 4.11 original and Linux winbond-840). On real hardware: completed frames hit the break and are never delivered (ipackets stays 0); the driver validates descriptors the DMA engine is concurrently writing (torn status/length reads) hitting the 'receiver babbling' branch which re-arms a chip-owned descriptor and runs chip reset+init from the ISR; independently the chip starves and RX_NOBUF triggers full 64-mbuf RX re-init from interrupt context for every packet on the wire. Unauthenticated layer-2-adjacent DoS from a single broadcast ARP. Deterministic logic harness on the guest (DFLY: processed=0 | FBSD411-COND: processed=1). Flip-the-condition fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4266 Β· 23 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4266_wb_poc.c | β | 22.5 KB | view raw | |
| Makefile | β | 113 B | β download | |
| kld_build.sh | β | 562 B | view raw | |
| kld_run.sh | β | 1.5 KB | view raw | |
| kld_build.log | β | 913 B | view raw | |
| build.log | β | 913 B | view raw | |
| kld_run_baseline.log | β | 4.5 KB | view raw | |
| run.log | β | 4.5 KB | view raw | |
| kld_run_fixed.log | β | 3.4 KB | view raw | |
| fix_run.log | β | 3.4 KB | view raw | |
| fix_build_tail.log | β | 1.8 KB | view raw | |
| fix_build.log | β | 1.8 KB | view raw | |
| panic_rig_v1.txt | β | 1.5 KB | view raw | |
| env.txt | β | 1.4 KB | view raw | |
| VERDICT.md | β | 9.0 KB | β raw | |
| README.md | β | 3.0 KB | β raw | |
| wb_logic.c | β | 4.2 KB | view raw | |
| run.sh | β | 240 B | view raw | |
| logic_run.log | β | 719 B | view raw | |
| fix.diff | β | 371 B | view raw | |
| manifest.json | β | 1.8 KB | view raw | |
| verdict.json | β | 8.5 KB | view raw | |
| code_hash.txt | β | 65 B | view raw |
DF-4266 β inverted RX descriptor-ownership test in wb_rxeof() (if_wb.c:972-975)
Verdict: REPRODUCED (kernel-level, production-handler dispatch) β fixed by fix.diff (kernel A/B). Impact: unauthenticated adjacent-network RX DoS (100% RX loss + per-interrupt chip reset/re-init storm) on Winbond W89C840F / Compex RL100-ATX NICs; ownership-protocol break that races the DMA engine (memory-safety tail tracked as DF-4268).
Primary evidence (this revision): production-handler KLD dispatch
The audit guest has no W89C840F NIC, but device wb is built into its
X86_64_GENERIC kernel, so the rig dispatches the running kernel's own
static wb_rxeof/wb_init/wb_stop (nm-resolved, sysctl-injected)
against a fabricated post-attach softc, with the "chip" simulated by
descriptor write-backs (the driver's only observation point of the NIC).
- Build (in guest):
sh /root/df4266/kld_build.shβmake -m /usr/src/share/mk - Run (baseline):
sh /root/df4266/kld_run.sh baseline - Run (fixed kernel): apply fix.diff to /usr/src,
make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel, reboot, re-runsh /root/df4266/kld_run.sh fixed - Expected baseline (stock kernel):
S1 BUG CONFIRMED(chip-completed frame dropped: delivered=0, ipackets=0, descriptor untouched) Β·S1fix(identical state delivered by the one-line-fixed comparator) Β·S2 BUG CONFIRMED(chip-owned descriptor processed per dispatch: ierrors++, "receiver babbling", full 64-mbuf ring teardown/re-init, ~65 ms vs 9-13 Β΅s fixed) Β·S3 BUG CONFIRMED(frame accepted from OWN=1 descriptor + re-armed under the chip) Β· SURVIVED. - Expected fixed kernel: S1 DELIVERED by the real patched handler (ipackets=1); S2 dispatches idle-break in Β΅s with zero side effects; S3 descriptor untouched; all bug-expectation markers "UNEXPECTED" (= fixed).
Historical evidence (prior pack revision, kept): logic harness
wb_logic.c β deterministic userspace transcription of both loop
conditions, compiled and run on the DragonFly guest itself:
DFLY loop processes 0 completed frames; FreeBSD-4.11-condition processes
exactly the completed one (logic_run.log).
Files
df4266_wb_poc.c/Makefileβ the KLD dispatch rig (primary PoC)kld_build.sh,kld_run.shβ exact build/run commandskld_build.logβ full clean -Werror module buildkld_run_baseline.logβ full dmesg of the baseline run (stock kernel #0)kld_run_fixed.logβ full dmesg of the fixed-kernel run (#1, same rig)fix_build_tail.logβ patched-kernel rebuild evidence (if_wb.c compile + install)panic_rig_v1.txtβ rig v1 NULL-subqueue panic (rig bug, not the finding)env.txtβ kernels, compiler, symbols, source identity (MD5 vs audit tree)VERDICT.mdβ full narrative incl. method, layout cross-check, A/B tablewb_logic.c,run.sh,logic_run.logβ prior logic harness (kept)fix.diffβ the one-line fix (restores FreeBSD 4.11 loop direction)manifest.json,verdict.json,code_hash.txt(sha256 of df4266_wb_poc.c)
VERDICT β DF-4266 (inverted RX OWN test in wb_rxeof)
Status: reproduced β at the strongest fidelity possible without the NIC:
the production handler itself (the running kernel's static wb_rxeof(),
resolved from /boot/kernel/kernel via nm(1) and dispatched from a KLD rig)
exhibited every claimed defective behavior on the guest, and a kernel rebuilt
with fix.diff exhibited none of them. Same rig binary (.ko) on both
kernels; the only variable was the one-line fix.
Method β production-handler KLD dispatch (DF-4106 vge evidence class)
The guest has no W89C840F/Compex NIC, but device wb is built into
X86_64_GENERIC (sys/config/X86_64_GENERIC:246), so the driver's real static
functions live in the running kernel image. The rig
(findings/poc/DF-4266/df4266_wb_poc.c, built clean with -Werror,
kld_build.log):
- resolves
wb_rxeof/wb_init/wb_stopfrom/boot/kernel/kernel(sysctl-injected hex addresses βwb_newbufexists only as an IPA-SRA clonewb_newbuf.isra.5with a compiler-mangled ABI and is deliberately never called; the ring is armed by dispatching the kernel's ownwb_init(), whose inlinedwb_list_rx_init/wb_newbufbuild the exact production post-init state); - fabricates a post-attach softc in module
.bss: realwb_list_data(contiguous rxbufs + descriptor rings, armed by the REALwb_initβ 64 MGETHDR mbufs, descriptorsWB_RXSTAT=0x80000300FIRSTFRAG|LASTFRAG|OWN), a fake miibusdevice_twhose softc is a zeroedmii_data(empty phys list:mii_mediachg/mii_pollstat/mii_tickdegenerate to no-ops sowb_init/wb_reset/wb_fixmediarun their full real bodies safely), a real lwkt serializer held across dispatches exactly aswb_intrwould, per-CPU ifdata, a realifaltq_subque(rig v1 panicked inwb_stop+0x156on a NULLif_snd.altq_subqβ rig bug, fixed by replicatingif_attach's subqueue setup, net/if.c:592-640; seepanic_rig_v1.txt), and a scratch page as the MMIO BAR; - simulates the NIC the only way the driver can observe it: descriptor
status write-backs + frame bytes into the armed buffer β bit-exact with
the W89C840F protocol (
wb_newbufarms OWN=1; chip clears OWN and sets RXCMP|FIRSTFRAG|LASTFRAG|len on completion, if_wbreg.h:227-243); - struct-layout agreement between the module compile and the running kernel
is verified:
sizeof(wb_softc)=0x1e80 if_timer=0x6a wb_bhandle=0x3c0 wb_btag=0x3c8 wb_ldata=0x400 rx_chain[0]=0x448 wb_stat_timer=0x1e68 if_snd.altq_subq=0x210β every constant matches the kernel's own disassembly ofwb_stop/wb_init(kld_run_baseline.log line 2). - The one transcription (
df_wb_rxeof_fixed) is NOT the code under test β it is the verbatim counterfactual with exactly the fix.diff line applied, used for the module-level A/B (S1fix) and as an independent sanity check.
Baseline run β stock kernel #0 (Jul 2 2026), REAL wb_rxeof (kld_run_baseline.log)
- S1 β chip-completed frame DROPPED (THE bug). Slot 0 write-back
0x40400300(OWN=0, RXCMP|FIRSTFRAG|LASTFRAG, len 64) + 60-byte frame with marker payload in the armed buffer. Realwb_rxeofbroke immediately at if_wb.c:974-975:delivered=0 ipackets=0 ierrors=0, descriptor NOT re-armed (0x40400300unchanged), head NOT advanced. On silicon this is permanent: 100% RX loss,ipacketsstays 0 forever, and since completed descriptors are never recycled the chip starves βWB_ISR_RX_NOBUFβwb_intr(if_wb.c:1137-1144) runswb_reset+wb_init(full 64-mbuf ring teardown/re-init) for every arriving frame. Any adjacent L2 frame (untargeted broadcast suffices) triggers it: unauthenticated adjacent-network DoS. - S1fix β identical state, one-line-fixed comparator: delivered=1,
if_input: m_len=60 dst=ff:ff:ff:ff:ff:ff src=00:df:42:66:00:01 type=df42 pay=0e 0f 10 11(exact crafted bytes), ipackets=1, slot re-armed to0x80000300. The state was processable; only the inverted test blocks the production handler. - S2 β chip-OWNED descriptors PROCESSED + per-interrupt reset storm.
Armed idle ring (exactly what
wb_initleaves). Each of 3 dispatches of the REAL handler: popped the chip-owned head,ierrors++, kernel printedreceiver babbling: possible chip bug, forcing reset(if_wb.c:989-990), ranwb_fixmedia+wb_reset+wb_initβ full ring re-init observable via the replacedwb_rx_chain[0].wb_mbufpointer each dispatch β at ~60.9/69.8/65.3 ms per dispatch (mostly the MMIO-stand-in reset poll; the 64-mbuf teardown/reinit work is the same on real HW). On the fixed kernel the same dispatches took 9-13 Β΅s and did nothing (below). - S3 β frame ACCEPTED from a descriptor the chip STILL OWNS. Slot 0 =
0xc0400300(OWN=1 β the mid-write-back transient the inverted loop wrongly accepts, i.e. what the driver sees when the interrupt fires while DMA is in flight). Realwb_rxeofprocessed it as a good frame (delivered=1, ipackets=1 β with the correct payload), then re-armed the descriptor under the chip (status rewritten to0x80000300,wb_datare-pointed): driver and hardware now disagree about who owns descriptor 0 and its buffer. This is the on-ramp to the DMA/race memory-safety tail tracked separately as DF-4268 (not re-verified here). After the S3 delivery the loop re-entered the just-re-armed slot (OWN=1 β continue, per the inversion) and babbled into yet another full reset β the storm feeds itself. - Guest stayed up (SURVIVED) β this finding is availability-breaking, not corruption-demonstrating (corruption family is DF-4268).
Fix validation β kernel #1 (Oct 14 2026), same tree + fix.diff (kld_run_fixed.log)
fix.diff applied to the guest's /usr/src (patch output in VERDICT
history; fix_build_tail.log: make -j6 nativekernel + installkernel,
if_wb.c recompiled at build log line 18787), rebooted into kernel #1, and
the identical rig re-run β wb_rxeof resolved again from the new
kernel (same address 0xffffffff808dae30; the one-line change did not move
the symbol):
| scenario | baseline (buggy) | fixed kernel |
|---|---|---|
| S1 completed frame (OWN=0) | dropped, ipackets=0, not re-armed | DELIVERED by the REAL patched handler (m_len=60, exact payload), ipackets=1, re-armed |
| S2 armed ring Γ3 | ierrors +1/dispatch, babble print, full ring re-init, 61-70 ms | ierrors=0, no babble, no re-init (mbuf unchanged), 9-13 Β΅s |
| S3 OWN=1 looking complete | accepted + re-armed under chip | untouched: delivered=0, ipackets=0, status still the crafted 0xc0400300 |
(The fixed-run "S2 BUG CONFIRMED" banner and "S1/S1fix/S3: UNEXPECTED
outcome" strings in the log are the rig's bug-expectation assertions
failing β i.e. exactly the fixed behavior; the data lines are decisive.
The run also crossed a warm reboot β DragonFly's msgbuf preserved the
baseline section in dmesg, so both runs appear in order; attribution is
by boot context and mbuf addresses, see env.txt.)
fix_status: fixed (baseline_reproduced=1, patched_reproduced=0 β every defective marker gone; the previously-impossible behavior β receiving a frame β now works).
Impact
Unauthenticated adjacent-network DoS on W89C840F/Compex RL100-ATX equipped hosts: 100% RX loss from the first frame + a per-interrupt full-chip reset/re-init storm (which also destroys TX progress each time), plus the S3 ownership-protocol break that races the DMA engine (DF-4268 tracks the memory-safety tail). Trigger is any Ethernet frame β untargeted broadcast/multicast suffices; no privileges needed. High severity stands.
Why this is "reproduced" and not "hardware-gated untested"
Everything the driver decides is a function of descriptor words and MMIO.
The rig supplied both bit-exactly per the W89C840F protocol (triangulated:
wb_newbuf arming semantics if_wbreg.h:243, FreeBSD 4.11 original loop,
Linux winbond-840.c DescOwn convention β see prior pack revision), and
the code that was executed was the kernel's own wb_rxeof, not a
transcription. The remaining hardware dependency (the chip actually
clearing OWN on write-back) is a property of the NIC the driver cannot
influence β and S1/S3 demonstrate the driver's response to both sides of
exactly that protocol event. The defective behavior is therefore
demonstrated on production code, on the audit guest, deterministically,
twice (plus once on the patched kernel to prove the delta).
Rig iteration notes (attempts)
- v1: NULL
if_snd.altq_subqβ panic inwb_stop+0x156(rig bug,panic_rig_v1.txt) β also yielded the struct-offset cross-check. - v2: full baseline reproduced (all scenarios, guest survived).
- v3: fixed-kernel A/B β all defective behaviors gone; frame delivery restored by the REAL patched handler.
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 applied to the guest's /usr/src (byte-identical to the audit tree), kernel rebuilt via make -j6 nativekernel + installkernel, rebooted into kernel #1, and the IDENTICAL rig re-run against the real patched wb_rxeof: the chip-completed frame is now DELIVERED (if_input m_len=60 with the exact crafted payload, ipackets=1, descriptor re-armed) - previously impossible; armed-ring dispatches break immediately in 9-13us with ierrors=0 and no ring re-init (vs ~65ms babble+full reset per dispatch on baseline); the OWN=1 mid-write-back descriptor is left untouched (status unchanged, delivered=0). Every defective behavior gone; no regressions observed in the run.
fix.diff; fix_build.log (if_wb.c recompiled, kernel install completed, INSTALL_DONE); kld_run_fixed.log / fix_run.log (S1 delivered by the REAL patched handler; S2 dispatches: ipackets=0 ierrors=0 ring re-inited:no elapsed 9-13us; S3: slot0.status unchanged 0xc0400300, delivered=0); fix_kernel_uname: DragonFly 6.5-DEVELOPMENT #1: Wed Oct 14 08:13:16 UTC 2026
Confirmed kernel references
Detail
Exploit chain
Any adjacent Ethernet frame (untargeted broadcast/multicast suffices, no authentication/privilege) -> chip completes descriptor (OWN cleared) -> wb_rxeof breaks at once -> frame dropped + descriptor never recycled -> ring exhaustion -> WB_ISR_RX_NOBUF/RX_OK interrupts -> wb_reset+wb_init full 64-mbuf ring teardown/re-init per interrupt (permanent reset storm, TX progress destroyed each cycle, 100% RX loss). If the interrupt races DMA write-back (OWN still set, RXCMP+len visible) the inverted loop accepts the in-flight descriptor, delivers a half-DMA'd frame and re-arms the descriptor under the chip - driver/hardware ownership confusion (memory-safety tail: DF-4268). No uid0 route: availability-only on gated hardware.
Evidence (decisive lines)
kld_run_baseline.log (S1/S2/S3 BUG CONFIRMED markers + per-dispatch data: delivered=0 ipackets=0 descriptor untouched on completed frame; ierrors++/'receiver babbling'/mbuf0 replaced/full ring re-init per armed-ring dispatch at ~65ms; frame accepted from OWN=1 crafted 0xc0400300 and re-armed to 0x80000300); kld_run_fixed.log (same rig on the patched kernel: S1 delivered m_len=60 exact payload, S2 9-13us no-op, S3 status unchanged 0xc0400300); S1fix comparator A/B; env.txt (symbol addresses both kernels, MD5 source identity); VERDICT.md (method, layout cross-check vs kernel disassembly, A/B table); panic_rig_v1.txt (rig v1 NULL-altq_subqueue panic - rig bug, fixed); fix_build.log + fix.diff
PoC changes
Prior pack revision had only a userspace logic harness (wb_logic.c) and verdict untested. This run added the production-handler KLD dispatch rig (df4266_wb_poc.c): nm-resolves the kernel's own wb_rxeof/wb_init/wb_stop, fabricates the post-attach softc (static .bss wb_softc+wb_list_data, fake miibus device_t with zeroed mii_data, real lwkt serializer, per-cpu ifdata, real ifaltq_subque per if_attach net/if.c:592-640, MMIO stand-in page), arms the ring by dispatching the real wb_init, and crafts the three W89C840F descriptor states (completed write-back / armed / mid-write-back transient). Rig v1 panicked on a NULL if_snd.altq_subq inside the kernel's wb_stop - fixed by replicating if_attach's subqueue setup; recorded in panic_rig_v1.txt. Authored clean git-apply-able fix.diff (prior one had prose before the header) and validated it with a full kernel rebuild A/B.
Verified recommended fix
Invert the OWN test in wb_rxeof: break while (rxstat & WB_RXSTAT_OWN), i.e. process only chip-released descriptors - restores the FreeBSD 4.11 loop direction (fix.diff, kernel-A/B validated).
Verdict
REPRODUCED at kernel level by dispatching the REAL production handler: the audit guest's X86_64_GENERIC kernel has 'device wb' built in, so the rig resolved the running kernel's own static wb_rxeof()/wb_init()/wb_stop() from /boot/kernel/kernel (nm + sysctl injection; wb_newbuf exists only as an IPA-SRA clone and is never called directly - the ring is armed by the kernel's own wb_init) and dispatched them against a fabricated post-attach softc with real rings, a fake miibus (zeroed mii_data: all mii calls degenerate safely), a real serializer, and an MMIO stand-in page; the NIC is simulated at the driver's only observation point - descriptor write-backs - bit-exact with the W89C840F protocol (wb_newbuf arms OWN=1; chip clears OWN + sets RXCMP|FIRSTFRAG|LASTFRAG|len on completion). Baseline (stock kernel #0): S1 - a chip-COMPLETED descriptor (OWN=0, len 64, marker frame in the armed buffer) made the real wb_rxeof break IMMEDIATELY at if_wb.c:974-975: delivered=0 ipackets=0 ierrors=0, descriptor never re-armed, head never advanced - permanent 100% RX loss, and since completed descriptors are never recycled the chip starves -> WB_ISR_RX_NOBUF -> wb_intr (if_wb.c:1137-1144) runs wb_reset+wb_init (full 64-mbuf ring teardown/re-init) for every arriving frame; untargeted L2 broadcast suffices: unauthenticated adjacent-network DoS. S1fix - the IDENTICAL state handed to a verbatim one-line-fixed comparator delivered the frame (if_input m_len=60, exact crafted payload, ipackets=1, slot re-armed) - only the inverted test blocks production. S2 - an armed idle ring (all OWN=1, exactly what wb_init leaves): every dispatch PROCESSED a chip-owned descriptor (head popped, ierrors++, 'receiver babbling: possible chip bug, forcing reset' from the kernel, wb_fixmedia+wb_reset+wb_init full ring re-init per dispatch, ~65ms vs 9-13us on the fixed kernel). S3 - a mid-write-back-looking descriptor (OWN=1 + RXCMP|FIRSTFRAG|LASTFRAG|len): the real handler ACCEPTED the frame from a descriptor the chip still owns and RE-ARMED it under the chip (status rewritten to WB_RXSTAT) - ownership protocol broken end-to-end, the on-ramp to the DMA-race memory-safety tail tracked as DF-4268 (not re-verified here). Struct-layout agreement module-vs-kernel verified field-by-field against the kernel's own disassembly (sizeof 0x1e80; if_timer 0x6a; wb_bhandle 0x3c0; wb_btag 0x3c8; wb_ldata 0x400; rx_chain[0] 0x448; wb_stat_timer 0x1e68; if_snd.altq_subq 0x210). FIX VALIDATED: fix.diff applied to the guest's byte-identical /usr/src (MD5 bec15392abf0facf8fe77b4aefb23938), kernel rebuilt (make nativekernel) and rebooted, the IDENTICAL rig re-run against the real patched wb_rxeof: the completed frame is DELIVERED (ipackets=1, exact payload), armed-ring dispatches idle-break in 9-13us with zero side effects, the OWN=1 descriptor is untouched - every defective behavior gone, previously-impossible RX now works. Guest survived all runs.
No comments yet.