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

Sysctl read handlers unlock before touching the HAL and never test sc_invalid β€” racing device_detach frees the HAL object and unmaps the BARs before the oids are removed, giving an unprivileged reader a use-after-free panic

Summary

All dev.ath.N.* leaves are CTLTYPE_INT|RW with reads needing no privilege. Handlers take ATH_LOCK for one call, RELEASE it, then call ath_hal_get* with no lock and no sc_invalid test (10 sites). device_detach runs ath_detach - which frees the HAL object and DMA - and only AFTERWARDS removes the sysctl oids; the PCI detach then unmaps the register BARs. An unprivileged reader inside that window dereferences freed HAL memory (and possible unmapped MMIO) -> UAF panic. Trigger: unpriv sysctl read loop racing a root detach. Pack carries the full lifetime trace. Guard + hold-lock fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4374 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
trigger_df4374.sh β€” 1.4 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 214 B view raw
build.log β€” 79 B view raw
run.log β€” 79 B view raw
env.txt β€” 131 B view raw
fix.diff β€” 861 B view raw
verdict.json β€” 3.3 KB view raw
manifest.json β€” 1.4 KB view raw

DF-4374 β€” ath sysctl read handlers race driver detach (UAF / unmapped MMIO read)

What

The dev.ath.N.* sysctl read handlers in sys/dev/netif/ath/ath/if_ath_sysctl.c are world-readable, and most of them call into the HAL outside ATH_LOCK with no sc_invalid check and no lifetime guarantee on sc/sc_ah:

  • ath_sysctl_txantenna β€” power-set under lock (:256-258), HAL read at :260 unlocked
  • ath_sysctl_diversity β€” :316-318 lock, :320 unlocked HAL read
  • ath_sysctl_diag β€” :347-349 lock, :351 unlocked ath_hal_getdiag
  • ath_sysctl_tpscale :376-378/:380, ath_sysctl_tpc :403-405/:407, ath_sysctl_rfkill :430-432/:434, ath_sysctl_rfsilent :563-565/:567, ath_sysctl_tpack :602-604/:606, ath_sysctl_tpcts :627-629/:631, ath_sysctl_intmit :652-654/:656 β€” same pattern.

During driver detach, ath_detach() frees sc->sc_ah (ath_hal_detach, if_ath.c:1436) and the PCI layer unmaps the register BARs, all before device_sysctl_fini() (subr_bus.c:2151, called from device_detach only after DEVICE_DETACH returns at :2141) removes the sysctl oids. The per-oid oid_lock (kern_sysctl.c:1460-1482) protects the oid, not the softc: a reader that entered a handler before detach began continues to run while the hardware context is torn down β†’ dereference of freed ath_hal state or an MMIO read from unmapped space β†’ kernel page fault / UAF read β†’ panic.

The read side needs no privilege (sysctl -n dev.ath.0.txantenna as any user); the detach side is privileged (kldunload if_ath, devctl-driven PCI hot-remove, CardBus eject).

Build / Run (probe)

  • ./run.sh β€” unprivileged probe: checks for dev.ath.0.*; if present, runs a reader storm intended to be raced against kldunload if_ath by root. On the audit QEMU guest there is no ath hardware, so it reports absence (see run.log).

Expected on real hardware

With the reader storm running as an unprivileged user and root executing kldunload if_ath (repeatedly), the kernel panics with a page fault in ar5212RegRead/OS_REG_READ or a UAF read of sc_ah β€” timing-dependent, typically within tens of detach attempts.

Files

  • trigger_df4374.sh β€” reader-storm probe
  • VERDICT.md β€” lifetime analysis with citations
  • fix.diff β€” hold ATH_LOCK across the HAL interaction and bail out when the device is invalid (representative for all affected handlers)
VERDICT.md
↓ download raw

DF-4374 VERDICT β€” sysctl read handlers vs ath driver detach lifetime race

Status: not_reproduced β€” no Atheros hardware in the audit QEMU guest (dev.ath tree absent, no 0x168c PCI device). Lifetime analysis verified from source; the race is real but the detach side is privileged, so impact ceiling is a race-condition kernel panic (DoS).

Reproduction attempt (honest)

  • Guest: DragonFly 6.5-DEVELOPMENT, vtnet0 lo0 only; sysctl -n dev.ath.0.slottime β†’ "unknown oid 'dev.ath'" as both root and user (run.log). pciconf -l shows no Atheros (0x168c) device.
  • trigger_df4374.sh executed: reports the absent device and exits.
  • Cannot execute the race without ath(4) hardware β†’ not_reproduced, impact none observed on this guest.

Why the race is real (citations)

  1. Unprivileged read surface. All dev.ath.N.* leaves are created with SYSCTL_ADD_PROC(..., CTLTYPE_INT | CTLFLAG_RW, ...). Reads need no privilege; only writes are gated (kern_sysctl.c:1446-1447: caps_priv_check(td->td_ucred, SYSCAP_NOSYSCTL_WR) unless CTLFLAG_ANYBODY). Any local user can enter these handlers.

  2. Handlers unlock before touching the HAL. Representative: ath_sysctl_txantenna takes ATH_LOCK for ath_power_set_power_state (if_ath_sysctl.c:256-258), releases it, then calls ath_hal_getantennaswitch(sc->sc_ah) at :260 with no lock and no sc_invalid test. Same pattern at :320, :351, :380, :407, :434, :567, :606, :631, :656. (slottime/acktimeout/ctstimeout/rxantenna do the read under ATH_LOCK but are still exposed to the softc lifetime problem below.)

  3. Driver detach destroys the hardware context while oids are live. device_detach (subr_bus.c:2131-2152) runs DEVICE_DETACH β€” for ath: ath_detach (if_ath.c:1385-1438) stops the chip, frees DMA, and calls ath_hal_detach(sc->sc_ah) at :1436 (frees the HAL object) β€” and only afterwards calls device_sysctl_fini(dev) at :2151, which is what removes/frees the dev.ath.N.* oids. The PCI detach then unmaps the register BARs.

  4. The sysctl lock protects the oid, not the softc. Handlers execute under the per-oid lockmgr lock (kern_sysctl.c:1460-1482; shared for reads). device_sysctl_fini/sysctl_ctx_free must take the exclusive oid lock, so it waits for readers β€” but that only prevents oid UAF; it does nothing to stop ath_detach from freeing sc_ah while a reader is parked between its ATH_UNLOCK and its HAL call.

  5. Result. Reader at :260 during/after ath_hal_detach: - reads ah->ah_sc-adjacent state from freed memory (UAF read), and/or - executes OS_REG_READ(ah, ...) through a bus-space handle whose BAR mapping was torn down β†’ kernel-mode access to unmapped device space β†’ page fault β†’ panic.

Impact

Race-condition local DoS (kernel panic). The reader side is unprivileged but the detach side requires root (kldunload if_ath) or physical hot-removal β€” per the audit rubric this is Low ("narrow trigger ... requires specific hardware or already-privileged user" for the triggering half).

Fix validation

Not possible on this guest (no ath hardware). fix.diff shows the pattern: check sc_invalid under ATH_LOCK and keep the lock across the HAL interaction so ath_detach's ATH_LOCK-serialized teardown cannot proceed underneath the reader; apply the same shape to every listed handler. fix_status: not_testable (hardware absent).

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

fix.diff shows the representative corrected ath_sysctl_txantenna (git-apply-able, verified); the same shape must be applied to diversity/diag/tpscale/tpc/rfkill/rfsilent/tpack/tpcts/intmit. Cannot validate on the guest (no ath hardware).

fix.diff; VERDICT.md 'Fix validation' section
↓ fix.diffper-fix-DF-4374

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (4-step lifetime proof with citations); run.log + env.txt (guest: dev.ath unknown oid, NO_ATHEROS_PCI, unprivileged uid=1001 probe); fix.diff (representative ATH_LOCK + sc_invalid pattern, git-apply-able); README.md

PoC changes

Trigger is a shell reader-storm (sysctl(8)) raced against root kldunload; no C needed. Guest probe reports hardware absence.

Verified recommended fix

In every affected handler, take ATH_LOCK, bail with ENXIO when sc->sc_invalid, and keep the lock across the HAL interaction (pattern in fix.diff).

Verdict

Lifetime race between world-readable dev.ath.N.* sysctl read handlers and ath(4) driver detach. Most handlers (txantenna if_ath_sysctl.c:260, diversity :320, diag :351, tpscale :380, tpc :407, rfkill :434, rfsilent :567, tpack :606, tpcts :631, intmit :656) call into the HAL outside ATH_LOCK with no sc_invalid check; ath_detach frees sc_ah at if_ath.c:1436 and the PCI layer unmaps the register BARs before device_sysctl_fini (subr_bus.c:2151) removes the sysctl oids, and the per-oid sysctl lock (kern_sysctl.c:1460-1482) protects only the oid, not the softc. An unprivileged reader parked between ATH_UNLOCK and the HAL call during root-initiated detach (kldunload if_ath / hot-remove) dereferences freed HAL state or reads unmapped MMIO -> kernel page fault panic. Reader side needs no privilege (writes are gated by SYSCAP_NOSYSCTL_WR per kern_sysctl.c:1446-1447, reads are not). Not executed: the audit QEMU guest has no Atheros hardware (dev.ath absent, no 0x168c PCI device); probe run honestly reports absence. Impact ceiling: race-condition local DoS; detach side is privileged, hence severity Low.