ar5212_ani: HAL_DIAG_ANI_PARAMS allows setting maxLevel > array dim -> wild OOB read in ar5212AniControl
| Field | Value |
|---|---|
| ID | DF-1660 |
| File | sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c |
| Lines | 181, 189, 191, 240, 248, 250, 252, 254, 313, 320, 331, 338 |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-129 Improper Validation of Array Index |
| Confidence | likely |
| Status | new |
| CVE match | variant (ath_hal SIOCGATHDIAG unguarded diag class β DF-1520/1521/1522) |
| Created | 2026-07-18 |
Summary
ar5212AniControl bounds-checks the level argument against
params->{maxNoiseImmunityLevel, maxSpurImmunityLevel, maxFirstepLevel}
(ar5212_ani.c:240, 313, 331), then immediately uses level to index
fixed-size arrays totalSizeDesired[5] / coarseHigh[5] / coarseLow[5]
/ firpwr[5] / cycPwrThr1[8] / firstep[3] (ar5212.h:151-162).
Those max*Level fields have no validation anywhere against the actual
array dimensions; the diagnostic API (HAL_DIAG_ANI_PARAMS via
ar5212_misc.c:1118-1122 β ar5212AniSetParams at ar5212_ani.c:181-201)
copies a whole user-supplied ar5212AniParams struct into
ah_aniParams24/5 with only an argsize check, so a caller can set
maxNoiseImmunityLevel = INT_MAX and then drive HAL_DIAG_ANI_CMD
(ar5212_misc.c:1098-1103) with a large level, causing a wild
out-of-bounds read past the embedded params struct in the ath_hal_5212
softc and then writing the OOB-derived value into a PHY register.
Root cause
In ar5212AniControl (sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c:237-262)
the check is:
if (level > params->maxNoiseImmunityLevel) return AH_FALSE;
...
params->totalSizeDesired[level] /* line 248 */
coarseLow[level] /* line 250 */
coarseHigh[level] /* line 252 */
firpwr[level] /* line 254 */
Identical pattern at lines 313/320 (firstep[3], bounded by
maxFirstepLevel) and 331/338 (cycPwrThr1[8], bounded by
maxSpurImmunityLevel).
params is aniState->params, which points at &ahp->ah_aniParams24 or
&ahp->ah_aniParams5 (set in ar5212AniReset:597-600). Those structs are
populated by ar5212AniAttach (149-178) and ar5212AniSetParams
(181-201); ar5212AniSetParams is reachable from user space via
ar5212GetDiagState case HAL_DIAG_ANI_PARAMS
(sys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:1104-1123), which only
validates argsize == sizeof(struct ar5212AniParams) (line 1119) and
otherwise trusts every field.
ar5212_misc.c:1098-1103 (HAL_DIAG_ANI_CMD) forwards arbitrary
(cmd, param) pairs from a user-supplied uint32_t[2] directly into
AH5212(ah)->ah_aniControl, i.e. into ar5212AniControl. No
nitems()/ARRAY_SIZE check exists on the max*Level fields anywhere in
the HAL, so the level > maxLevel gate is meaningless when maxLevel
itself is untrusted.
Threat model
Requires:
- The
ath(4)driver loaded (only on systems with Atheros AR5212/AR5416 wireless MACs) - Kernel built with
options ATH_DIAGAPI(declared insys/conf/options:539; present inLINT64but notGENERIC) - The ability to issue
SIOCGATHDIAGioctls
The diag ioctl reaches ath_ioctl_diag (sys/dev/netif/ath/ath/if_ath_ioctl.c:170)
with no priv_check/caps_priv_check of its own; on DragonFly,
SIOCGATHDIAG is not in the privileged-case list in ifioctl
(sys/net/if.c:2004-2408) nor in ieee80211_ioctl
(sys/netproto/802_11/wlan/ieee80211_ioctl.c:3377-3528 β the default
branch at 3516 passes it to ic->ic_ioctl with no credential check), so
the gate is the privilege to open a network socket.
Worst-case impact is a wild OOB kernel read (params + level*sizeof(int))
when level is large, almost certainly faulting into unmapped memory and
panicking the kernel β a local denial of service. A bounded level
(e.g. level = 5..7 for totalSizeDesired[5]) reads adjacent
ar5212AniParams fields within the softc and writes them masked into PHY
registers; reading those back is gated by HAL_DIAG_REGS, so info leak is
at best indirect and root-restricted.
PoC
findings/poc/DF-1660/:
- Build a kernel with
options ATH_DIAGAPIand anath(4)device present (e.g. AR5212 PCI card or appropriateqemu -device). - Open a socket:
int s = socket(AF_INET, SOCK_DGRAM, 0); struct ifreq ifr; strcpy(ifr.ifr_name, "ath0"); - Stage malformed params: build a
struct ar5212AniParams p; memcpy from a known-good baseline (capture viaHAL_DIAG_ANI_PARAMSread first), then setp.maxNoiseImmunityLevel = 0x40000000(any value > 4). Issue theSIOCGATHDIAGwithad_id = HAL_DIAG_ANI_PARAMS | ATH_DIAG_IN,ad_in_data = &p,ad_in_size = sizeof(p). - Trigger the OOB:
uint32_t cmd[2] = { HAL_ANI_NOISE_IMMUNITY_LEVEL, 0x40000000 };issueSIOCGATHDIAGwithad_id = HAL_DIAG_ANI_CMD | ATH_DIAG_IN,ad_in_data = cmd,ad_in_size = sizeof(cmd).
Success criterion: immediate kernel panic on the wild load (or, for
level=6, silent misprogramming of AR_PHY_DESIRED_SZ /
AR_PHY_AGC_CTL1 / AR_PHY_FIND_SIG with OOB values). The 0x40000000
case is the panic PoC; the level=6 case demonstrates the indexed-table
defect without faulting.
Recommended fix
Validate the max*Level fields against the actual array dimensions
whenever params are accepted from outside the HAL. Cheapest correct fix is
at the ar5212AniSetParams entry point (and at ar5212AniAttach for
defense-in-depth).
--- a/sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c
+++ b/sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c
@@ -148,6 +148,17 @@
OS_REG_WRITE(ah, AR_PHYCNTMASK2, 0);
}
+#define ANI_NI_LEVELS 5 /* totalSizeDesired/coarseHigh/coarseLow/firpwr */
+#define ANI_SI_LEVELS 8 /* cycPwrThr1 */
+#define ANI_FS_LEVELS 3 /* firstep */
+
+static HAL_BOOL
+aniParamsValid(const struct ar5212AniParams *p)
+{
+ return (p->maxNoiseImmunityLevel >= 0 &&
+ p->maxNoiseImmunityLevel < ANI_NI_LEVELS &&
+ p->maxSpurImmunityLevel >= 0 &&
+ p->maxSpurImmunityLevel < ANI_SI_LEVELS &&
+ p->maxFirstepLevel >= 0 &&
+ p->maxFirstepLevel < ANI_FS_LEVELS);
+}
+
/*
* Return the current ANI state of the channel we're on
*/
@@ -158,13 +169,17 @@
ar5212AniAttach(struct ath_hal *ah, const struct ar5212AniParams *params24,
const struct ar5212AniParams *params5, HAL_BOOL enable)
{
struct ath_hal_5212 *ahp = AH5212(ah);
+ HALASSERT(params24 == AH_NULL || aniParamsValid(params24));
+ HALASSERT(params5 == AH_NULL || aniParamsValid(params5));
ahp->ah_hasHwPhyCounters =
@@ -181,6 +196,11 @@
HAL_BOOL
ar5212AniSetParams(struct ath_hal *ah, const struct ar5212AniParams *params24,
const struct ar5212AniParams *params5)
{
+ if (!aniParamsValid(params24) || !aniParamsValid(params5)) {
+ HALDEBUG(ah, HAL_DEBUG_ANY,
+ "%s: rejected ANI params with out-of-range maxLevel\n",
+ __func__);
+ return AH_FALSE;
+ }
struct ath_hal_5212 *ahp = AH5212(ah);
A second defense-in-depth is to also clamp level inside
ar5212AniControl itself (e.g. if (level >=
nitems(params->totalSizeDesired)) return AH_FALSE;), so a future internal
caller that hands in a corrupted maxLevel cannot trigger the same OOB.
The primary fix above closes the attacker-reachable path.
Related findings
- DF-1520/1521/1522 (ath_hal ah.c SIOCGATHDIAG memory disclosure class)
- DF-1660 is the same "diag ioctl accepts untrusted data" pattern in a different ath_hal sub-driver.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1660 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Fix for ar5212 ANI params OOB index | 802 B | view raw |
| VERDICT.md | verdict | Source-only verification verdict | 815 B | β raw |
| build.sh | build-script | No-op (source-only) | 109 B | view raw |
| run.sh | run-script | No-op (source-only) | 107 B | view raw |
VERDICT DF-1660: ar5212 ANI params OOB index
Verdict
REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.
Mechanism
User-supplied maxLevel via HAL_DIAG_ANI_PARAMS indexes fixed-size arrays OOB.
Source reference: sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c,ar5212_misc.c:1118.
Reproduction
Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed.
The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present
on the QEMU/virtio guest. The finding is HW-gated.
Fix
Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC β rc=0, -Werror clean.
See fix.diff for the git-apply-able patch.
Fix verification
fixedCombined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.
'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- a
- t
- h
- /
- a
- t
- h
- _
- h
- a
- l
- /
- a
- r
- 5
- 2
- 1
- 2
- /
- a
- r
- 5
- 2
- 1
- 2
- _
- a
- n
- i
- .
- c
- :
- 1
- 8
- 2
Detail
Exploit chain
none
Evidence (decisive lines)
Source confirmed: sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c:182. Combined 41-fix kernel build rc=0 -Werror clean.
PoC changes
fix.diff authored; validated by combined kernel build.
Verified recommended fix
Bounds-check maxLevel in SetParams. Supersedes finding.
Verdict
REPRODUCED (source-confirmed). User maxLevel via diag ioctl indexes arrays OOB. Cited path verified at sys/dev/netif/ath/ath_hal/ar5212/ar5212_ani.c:182. HW/module-gated on QEMU guest.
No comments yet.