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

Spectral scan start/configure permanently disables radar-detect hardware with no restore path β€” latent DFS-detection bypass (ar5416DisableRadar clobbers AR_PHY_RADAR_0_ENA/EXT and AR_RX_FILTER; ar5416StopSpectralScan restores none of it)

Summary

Prep runs DisableRadar on every configure/start/restore, Stop restores none of it, and the only re-enable callers are #if 0 in dfs_null.c - after any spectral scan, hardware radar detection stays off for the life of the channel. In-tree latent (null-DFS only + AR928x gating); on a real-DFS deployment it composes with the DF-2081 priv gap into an unpriv DFS-detection DoS. Save-and-restore fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5113 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
fix.diff β€” 1.6 KB view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 3.0 KB view raw

DF-5113 β€” Spectral scan permanently disables radar-detect hardware; no restore path (Info / hardening)

File: sys/dev/netif/ath/ath_hal/ar5416/ar5416_spectral.c Severity: Info (latent; see VERDICT.md) Β· Bucket: base:dev Β· Pass 2 (GLM 5.3)

What

ar5416PrepSpectralScan() (ar5416_spectral.c:75-81) is invoked on every ar5416ConfigureSpectralScan() (:88), ar5416StartSpectralScan() (:193) and ar5416RestoreSpectralConfig() (:234). It calls ar5416DisableRadar() (:47-73), which:

  • maxes the radar RSSI/height detection thresholds (AR_PHY_RADAR_0_RRSSI/AR_PHY_RADAR_0_HEIGHT, :56-61),
  • clears AR_PHY_RADAR_0_ENA β€” hardware radar detection OFF (:63),
  • clears AR_PHY_RADAR_EXT_ENA (:67-68),
  • ORs bit 13 into AR_RX_FILTER (:70-72) β€” never cleared either.

ar5416StopSpectralScan() (:205-218) clears only the spectral ENA/ACTIVE bits and the AR_PHY_ERR_RADAR mask bit; it restores none of the radar state, and no reachable in-tree path re-enables radar afterwards (the only ath_hal_enabledfs() callers are inside #if 0 blocks in sys/dev/netif/ath/ath_dfs/null/dfs_null.c:104-148).

Why Info (not higher)

  • Reachable only on AR9280/AR9285/AR9287 NICs (halSpectralScanSupport = AH_TRUE only in ar9280/9285/9287_attach.c; ar5416_attach.c:954 sets it AH_FALSE).
  • In this tree radar detection is non-operational anyway β€” the only DFS module compiled is the null stub, whose ath_dfs_radar_enable() body is #if 0'd out and returns 0 unconditionally.
  • On a stack with a real DFS module (upstream FreeBSD HAL lineage), an unprivileged SIOCGATHSPECTRAL (missing priv check, DF-2081) SPECTRAL_CONTROL_START on a DFS-channel AP would silently defeat radar detection until the next channel change β€” a regulatory/interference hazard, not a memory-safety bug.

PoC status

Not executed (skipped per contract β€” Info hardening finding). No PoC is possible on the audit guest: it has no AR928x NIC, and the in-tree null-DFS module makes the "disabled radar detection" state indistinguishable from the never-enabled state. verdict.json carries status: "untested".

ar5416StopSpectralScan() (or the driver, on scan stop) should restore the AR_PHY_RADAR_0/AR_PHY_RADAR_EXT/AR_RX_FILTER values captured before ar5416PrepSpectralScan() clobbered them β€” see fix.diff.

VERDICT.md
↓ download raw

DF-5113 VERDICT β€” spectral scan leaves radar-detect hardware disabled (no restore)

Status: untested (Info hardening finding β€” PoC skipped per contract). Reproduced: N/A Β· Impact: none in-tree (latent DFS-regulatory hazard upstream).

Narrative

Pass-2 audit of sys/dev/netif/ath/ath_hal/ar5416/ar5416_spectral.c (244 LOC). Known-family items were excluded up front:

  • The ss NULL-deref at :92 (ss->ss_fft_period) and the tail call ar5416GetSpectralParams(ah, ss) at :143 are the sink of the already-filed DF-5070 chain (SPECTRAL_CONTROL_SET_PARAMS passes a NULL indata when ATH_DIAG_IN is clear, if_ath_spectral.c:232-242). Not re-reported.
  • Driver-side GET/SET parameter copy bugs are DF-2079 (GET_PARAMS out-buffer NULL/size mismatch), DF-2080 (DYN leak), DF-2081 (missing privilege check on SIOCGATHSPECTRAL). Not re-reported.

The finding

ar5416DisableRadar() (ar5416_spectral.c:47-73) is reached via ar5416PrepSpectralScan() (:75-81) from Configure (:88), Start (:193) and Restore (:234). It maxes detection thresholds and clears AR_PHY_RADAR_0_ENA + AR_PHY_RADAR_EXT_ENA (radar detection OFF) and ORs bit 13 into AR_RX_FILTER. ar5416StopSpectralScan() (:205-218) restores none of this; the only in-tree re-enable path (ath_hal_enabledfs()) is inside #if 0 in sys/dev/netif/ath/ath_dfs/null/dfs_null.c:104-148. So after any spectral configure/start, radar-detect hardware stays disabled for the life of the channel β€” an asymmetric state clobber with no rollback.

Why not verified on the guest

  1. The audit guest has no AR9280/AR9285/AR9287 NIC β€” the only chips for which halSpectralScanSupport is AH_TRUE (ar9280_attach.c:904, ar9285_attach.c:558, ar9287_attach.c:457; ar5416_attach.c:954 sets AH_FALSE for everything else), so ath_ioctl_spectral() returns EINVAL at if_ath_spectral.c:194-195 before reaching any of this code.
  2. Even with the NIC, this tree's radar detection is never enabled (null DFS stub, body #if 0'd), so there is no observable "before" state to lose β€” the bug is latent, manifesting only in DFS-enabled deployments (the upstream FreeBSD HAL lineage compiled with a real DFS module).

Per the audit contract, Low/Info hardening findings skip the QEMU phase; verdict.json records status: "untested", impact: "none".

Negative result (what else was hunted and killed in this file)

  • Get-params copy sizes / NULL (ar5416GetSpectralParams :150-168): fills only 5 of the ~36 bytes of HAL_SPECTRAL_PARAM; every driver call site passes a memset/kzalloc'd destination (if_ath_spectral.c:226-228, :240-241, ath_spectral_attach :117-128). No uninit leak from this file.
  • RX phyerr samples: no in-tree consumer of HAL_PHYERR_SPECTRAL exists (grep across sys/dev/netif/ath) β€” the FFT-sample surface is dead in this tree.
  • Config register writes (:90-141): every field write goes through SM() (((_v) << _f##_S) & _f, ah_internal.h:579) which masks to the field width; register addresses are fixed constants. The Kiwi ss->ss_count == 128 β†’ 0 mutation (:125-126) only rewrites the caller's kernel copy. No OOB, no div-by-zero (no division in the file).
  • Disable pairs/flags: Start (:196-202) vs Stop (:209-217) are symmetric for ENA/ACTIVE + AR_PHY_ERR_RADAR mask. The dead register reads at :200/:215 (value overwritten on the next line) and the duplicated ss_period block at :102-105 are cosmetic.
  • Dead code: ar5416RestoreSpectralConfig() (:229-243) and ar5416GetSpectralConfig() (:220-227) have no ah_* method slot (ah.h:1528-1534 registers only Configure/GetConfig/Start/Stop/ IsEnabled/IsActive) and zero callers tree-wide β€” orphaned.

Fix

See fix.diff: capture AR_PHY_RADAR_0/AR_PHY_RADAR_EXT/AR_RX_FILTER on first prep and restore them in ar5416StopSpectralScan(). fix_status: not_testable β€” behavioral fix on hardware we do not have; the patch is compile-reviewed only.

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

not_testable: fix validation requires AR928x hardware and an operational DFS stack; the guest has neither. Patch is compile-reviewed only.

findings/poc/DF-5113/fix.diff
↓ fix.diffper-fix-DF-5113

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5113/VERDICT.md (path:line narrative); README.md; fix.diff (save/restore of AR_PHY_RADAR_0, AR_PHY_RADAR_EXT, AR_RX_FILTER in StopSpectralScan)

PoC changes

No PoC authored - Info hardening finding with no in-tree manifestation (no AR928x NIC on guest; radar detection never enabled in-tree).

Verified recommended fix

Save AR_PHY_RADAR_0/AR_PHY_RADAR_EXT/AR_RX_FILTER in ar5416PrepSpectralScan() and restore them in ar5416StopSpectralScan() - see fix.diff.

Verdict

ar5416PrepSpectralScan() (ar5416_spectral.c:75-81) runs ar5416DisableRadar() on every spectral configure/start/restore (:88, :193, :234); it maxes radar thresholds, clears AR_PHY_RADAR_0_ENA and AR_PHY_RADAR_EXT_ENA, and ORs bit 13 into AR_RX_FILTER (:47-73). ar5416StopSpectralScan() (:205-218) restores none of that state and the only in-tree re-enable path (ath_hal_enabledfs in ath_dfs/null/dfs_null.c:104-148) is #if 0'd out. On DFS-enabled deployments of this HAL lineage this silently defeats radar detection after any spectral scan; in this DragonFly tree the null-DFS module makes radar detection non-operational anyway, so the impact is latent - filed as Info hardening. PoC skipped per contract: the audit guest has no AR9280/9285/9287 NIC (halSpectralScanSupport is AH_TRUE only on those; ar5416_attach.c:954), so ath_ioctl_spectral() returns EINVAL at if_ath_spectral.c:194-195 before this file is reached.