sc_invalid written through the rt2560 union alias misses on rt2860-family devices: rt2860_softc dropped sc_tx_timer from the documented common prefix, so the glue writes sc_debug (offset 7404) and rt2860_softc.sc_invalid (7400) is never set β card-eject guards permanently dead
Summary
The union-alias prefix contract drifted (rt2860var.h dropped sc_tx_timer from the prefix, shifting sc_invalid down 4) - every sc_invalid write lands on rt2860 sc_debug for the default-dispatch family while the real sc_invalid has no writer: the eject guard can never trip and the watchdog keeps MMIO-ing after surprise removal. PROVEN with live compiled offsets on guest (alias 7404 == rt2860 sc_debug; sc_st/sc_sh coincide which is why it went unnoticed). Restore-prefix+CTASSERT fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4997 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| run.log | β | 708 B | view raw | |
| build.sh | β | 134 B | view raw | |
| run.sh | β | 121 B | view raw | |
| env.txt | β | 376 B | view raw | |
| fix.diff | β | 1.6 KB | view raw |
DF-4997 β if_ral_pci.c writes sc_invalid through the rt2560 union alias; for rt2860-family devices the offsets diverge and the write lands on rt2860_softc.sc_debug (sc_invalid stays permanently 0)
Where
sys/dev/netif/ral/if_ral_pci.c:205,249-251,296,308 (union ral_pci_softc
at :143-155) vs the softc prefixes in rt2560var.h:106-122,
rt2661var.h:99-115, rt2860var.h:117-132.
What
The glue uses struct rt2560_softc *sc = &psc->u.sc_rt2560; as a universal
alias and writes sc->sc_invalid (attach: 1 then 0; detach: !bus_child_present).
The three child structs documented a common prefix ("The same in both up
to here", rt2860var.h:133-135), but rt2860_softc drifted from it:
sc_snd was moved before sc_mtx and sc_tx_timer was dropped from the
prefix (it lives at rt2860var.h:160). Consequence for the default dispatch
(all rt2860-family PCI IDs, 24+ of 33 entries in ral_pci_ids):
rt2560_softc.sc_invalid(offset 7404) ==rt2860_softc.sc_debug(7404) β the glue writes sc_debug, not sc_invalid;rt2860_softc.sc_invalid(7400) is never set β the "card ejected" guards (rt2860.c:2080watchdog; the whole point of the flag) can never fire, and the "not-yet-initialized" latch during attach is dead.
rt2661 keeps the rt2560 prefix (both at 7404) and is unaffected;
sc_st/sc_sh coincide in all three (7360/7368 β the mtx/snd swap is
offset-neutral since both are 8-aligned blocks), so the bustag/handle setup
is correct and impact is confined to sc_invalid/sc_debug.
Reproduce (build/run on the audit DragonFly guest)
See ../DF-4996/ β the same module prints the compiled offsets at MOD_LOAD
(df4996.c, df4997_report_offsets()). Copies of the decisive output are
in run.log.
kldload ./df4996.ko dmesg | grep DF4997
Expected output (observed 2026-09-25)
DF4997: rt2560_softc size=7920: ... sc_tx_timer=7400 sc_invalid=7404 sc_debug=7408 DF4997: rt2661_softc size=8040: sc_invalid=7404 sc_debug=7408 DF4997: rt2860_softc size=21536: ... sc_invalid=7400 sc_debug=7404 sc_tx_timer=21224 DF4997: VERDICT rt2560.sc_invalid alias == rt2860.sc_debug: glue writes MISS sc_invalid and hit sc_debug on rt2860 (claim TRUE) DF4997: sc_st/sc_sh offsets DO coincide (...)
DF-4997 β VERDICT
Finding: if_ral_pci.c drives the three child drivers through one union
(struct ral_pci_softc.u) and accesses the per-child softc via the
sc_rt2560 member alias. sc_invalid writes through that alias miss their
target for rt2860-family devices.
Reproduced? β YES, at the level the bug exists (compiled layout), live on the guest
The bug is a layout divergence; it is fully demonstrated by the real
compiled offsets, printed from the actual kernel headers on the audit guest
(module df4996.c, df4997_report_offsets(), captured in run.log):
| field | rt2560 | rt2661 | rt2860 |
|---|---|---|---|
| sc_st | 7360 | β | 7360 |
| sc_sh | 7368 | β | 7368 |
| watchdog_ch | 7376 | β | 7376 |
| sc_tx_timer | 7400 | β | (absent; at 21224) |
| sc_invalid | 7404 | 7404 | 7400 |
| sc_debug | 7408 | 7408 | 7404 |
Root cause chain:
rt2560var.h:106-122prefix:sc_ic, sc_mtx, sc_snd, sc_dev, sc_st, sc_sh, watchdog_ch, sc_tx_timer, sc_invalid, sc_debug.rt2860var.h:117-132prefix:sc_ic, sc_snd, sc_mtx, sc_dev, sc_st, sc_sh, watchdog_ch, sc_invalid, sc_debugβsc_sndswapped ahead ofsc_mtx(offset-neutral: sizeof(struct lock)=32, sizeof(struct mbufq)=24, both 8-aligned, so all following offsets coincide) andsc_tx_timerdropped from the prefix (it survives at :160), which shiftssc_invalid/sc_debugdown by 4 relative to rt2560/rt2661. The comment at rt2860var.h:133-135 ("The same in both up to here") is therefore false for the rt2860 prefix.if_ral_pci.c:205aliases&psc->u.sc_rt2560; :251sc->sc_invalid=1, :296sc->sc_invalid=0, :308sc->sc_invalid=!bus_child_present(dev)all write at offset 7404 β rt2860's sc_debug.- Dispatch (
if_ral_pci.c:227-239): default β rt2860 ops for every rt2860-family ID (0x0601/0x0681/0x0701/0x0781/0x3060/β¦/0x539f and all Edimax/MSI/AWT rebrands β the overwhelming majority ofral_pci_ids). Only 0x0201βrt2560 and 0x0301/0x0302/0x0401βrt2661 use the aligned layouts.
Effects (rt2860 family)
rt2860_softc.sc_invalidis never set by the glue: the card-ejected guardif (sc->sc_invalid) return;inrt2860_watchdog(rt2860.c:2080) can never trip after surprise removal (the flag's entire purpose); the "device not ready" latch during attach is equally dead. No other writer of sc_invalid exists (checked rt2560.c/rt2661.c/rt2860.c).rt2860_softc.sc_debugreceives 1 at pre-attach (transiently enables DPRINTF until rt2860.c:242 resets it to 0) and!bus_child_present()at detach (softc is dying; the RAL_DEBUG sysctl at rt2860.c:369 may print debug spam during teardown on an ejected card).sc_st/sc_shwrites land correctly (7360/7368 in both), so the device still works β this is why the regression went unnoticed.
Impact
Robustness/correctness regression with a narrow security edge: dead
surprise-removal guard on CardBus/PCIe hot-unplug of rt2860-family cards
(watchdog keeps driving rt2860_stop_locked/rt2860_init_locked against
unmapped MMIO after ejection). Requires physical removal/failure β
Low severity, no unprivileged trigger.
Fix
Root-cause fix in fix.diff: restore the documented common prefix in
rt2860_softc (move sc_mtx ahead of sc_snd, move sc_tx_timer into the
prefix β private struct, no persistence/ABI impact) and add
compile-time assertions in if_ral_pci.c so any future drift of the aliased
fields (sc_st, sc_sh, sc_invalid) fails the build instead of silently
mis-writing. Not kernel-built on the guest (no ral hardware; a plain
kernel build would only prove it compiles) β layout math and the live
offset table above carry the proof.
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.diff generated mechanically from the real tree and verified with patch --dry-run (APPLY-OK). Not kernel-built: no ral hardware to observe the restored eject-guard behavior, and the CTASSERTs make any residual drift a compile-time error by construction. The reorder moves only members of a driver-private struct (no persistence, no ABI).
findings/poc/DF-4997/fix.diff (patch -p1 --dry-run: OK)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (live offsetof table printed from the real kernel headers on the guest); VERDICT.md (offset table + root-cause chain + scope analysis); fix.diff (restores the common prefix in rt2860_softc + CTASSERT guards on the aliased fields, patch --dry-run verified)
PoC changes
No seed PoC. Offsets demonstrated via the DF-4996 module's MOD_LOAD reporting (df4997_report_offsets), which compiles the actual rt2560var.h/rt2661var.h/rt2860var.h.
Verified recommended fix
Restore the documented common prefix in rt2860_softc (sc_mtx before sc_snd; move sc_tx_timer into the prefix) and add CTASSERTs in if_ral_pci.c pinning the aliased field offsets
Verdict
The layout divergence - which IS the bug - was reproduced live on the audit guest by printing the compiled offsets of the real kernel structs (module df4996.c): rt2560_softc.sc_invalid=7404/sc_debug=7408, rt2661_softc.sc_invalid=7404 (unaffected), rt2860_softc.sc_invalid=7400/sc_debug=7404. if_ral_pci.c writes sc_invalid through the rt2560 union alias (if_ral_pci.c:205,251,296,308), i.e. at offset 7404, which on rt2860-family devices (the default dispatch at if_ral_pci.c:227-239 - 24+ of 33 PCI IDs) is rt2860_softc.sc_debug; rt2860_softc.sc_invalid (7400) is never written anywhere and stays 0, permanently defeating the card-eject guard rt2860.c:2080 and the attach-time not-ready latch; the stray writes land on sc_debug (transiently 1 until rt2860.c:242 resets it, then !bus_child_present at detach). sc_st/sc_sh offsets coincide (7360/7368 in both - sizeof(lock)=32 and sizeof(mbufq)=24, both 8-aligned, so the mtx/snd swap is offset-neutral), which is why the driver still functions and the regression went unnoticed; the actual shift comes from sc_tx_timer being dropped from the rt2860 prefix (now at rt2860var.h:160) despite the comment at rt2860var.h:133 claiming 'The same in both up to here'. Runtime impact ceiling: dead surprise-removal guard (watchdog keeps programming an ejected CardBus/PCIe rt2860) - requires physical removal, no unprivileged trigger; impact=none for runtime escalation purposes, severity Low.
No comments yet.