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

ar5416_ani: ar5416AniControl OOB array index via untrusted maxLevel (DF-1660 variant)

Field Value
ID DF-1675
File sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c
Lines 230, 238, 240, 242, 244, 317, 324, 336, 343
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 (sibling of DF-1660; same shared ar5212AniSetParams root cause)
Created 2026-07-18

Summary

ar5416AniControl gates level against params->maxNoiseImmunityLevel / maxFirstepLevel / maxSpurImmunityLevel and then immediately uses level to index fixed-size arrays totalSizeDesired[5]/coarseLow[5]/coarseHigh[5]/firpwr[5]/firstep[3]/cycPwrThr1[8].

Those max*Level fields are never validated against the actual array dimensions; the diag ioctl HAL_DIAG_ANI_PARAMS copies a whole user-supplied ar5212AniParams struct into the shared softc with only an argsize check, so a caller can plant maxNoiseImmunityLevel = INT_MAX and then drive HAL_DIAG_ANI_CMD to cause a wild OOB read past the embedded params struct (panicking on unmapped memory) or, with a bounded out-of-range level, silently program PHY registers with OOB-derived values.

Same root cause and trigger as DF-1660 (ar5212_ani.c); reported here because the OOB sink is in ar5416AniControl, which is the dispatched handler for AR5416-class chips.

Root cause

In ar5416AniControl (sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:226-252) the bounds check is:

if (level > params->maxNoiseImmunityLevel) return AH_FALSE;       /* line 230 */
params->totalSizeDesired[level]                                   /* line 238 */
params->coarseLow[level]                                          /* line 240 */
params->coarseHigh[level]                                         /* line 242 */
params->firpwr[level]                                             /* line 244 */

Identical pattern at lines 313-330 for firstep[] (bounded by maxFirstepLevel at 317, indexed at 324) and 332-351 for cycPwrThr1[] (bounded by maxSpurImmunityLevel at 336, indexed at 343).

The arrays are fixed-size: totalSizeDesired/coarseHigh/coarseLow/firpwr are [5] (ar5212.h:153-156), cycPwrThr1 is [8] (ar5212.h:159), firstep is [3] (ar5212.h:162).

params is aniState->params, pointing at &ahp->ah_aniParams24 or &ahp->ah_aniParams5 (set in ar5416AniReset:554-557). Those structs are populated by ar5416AniAttach (131-155) and β€” critically β€” by ar5212AniSetParams (ar5212_ani.c:181-201), which is reachable from userspace via the diag ioctl: HAL_DIAG_ANI_PARAMS in ar5212GetDiagState (ar5212_misc.c:1104-1123) only validates argsize == sizeof(struct ar5212AniParams) (line 1119) and otherwise trusts every field.

AR5416 dispatches through ar5416GetDiagState (ar5416_misc.c:530) which falls through to ar5212GetDiagState for unhandled codes (ar5416_misc.c:557-558). HAL_DIAG_ANI_CMD (ar5212_misc.c:1098-1103) forwards arbitrary (cmd,param) from a user-supplied uint32_t[2] into AH5212(ah)->ah_aniControl, which for AR5416 chips is wired to ar5416AniControl at ar5416_attach.c:247.

No nitems()/ARRAY_SIZE check exists on the max*Level fields anywhere, so the level > maxLevel gate is meaningless when maxLevel itself is untrusted.

Threat model

Requires:

  1. ath(4) driver loaded on AR5416-class Atheros wireless MAC
  2. Kernel built with options ATH_DIAGAPI (sys/conf/options:539 β€” present in LINT64 but NOT GENERIC)
  3. Ability to issue SIOCGATHDIAG ioctls (no priv_check/caps_priv_check in ath_ioctl_diag, if_ath_ioctl.c:170)

Attacker opens a socket, issues SIOCGATHDIAG with ad_id=HAL_DIAG_ANI_PARAMS|ATH_DIAG_IN carrying a forged ar5212AniParams with e.g. maxNoiseImmunityLevel=0x40000000, then issues SIOCGATHDIAG with ad_id=HAL_DIAG_ANI_CMD|ATH_DIAG_IN carrying uint32_t[2]={HAL_ANI_NOISE_IMMUNITY_LEVEL, 0x40000000}.

Worst-case impact: wild OOB kernel read at params+level*sizeof(int) almost certainly faulting into unmapped memory and panicking the kernel β€” a local denial of service. A bounded out-of-range level (e.g. level=6..7 for totalSizeDesired[5]) reads adjacent ar5212AniParams/softc fields and writes them masked into PHY registers; reading those back is gated by HAL_DIAG_REGS so info leak is at best indirect.

PoC

findings/poc/DF-1675/:

Identical to DF-1660 with the AR5416 ah_aniControl dispatch:

  1. Build a kernel with options ATH_DIAGAPI and an AR5416-class ath(4) device present (e.g. AR9160/AR9280/AR9285 PCI card or appropriate qemu -device).
  2. Open a socket: int s = socket(AF_INET, SOCK_DGRAM, 0); struct ifreq ifr; strcpy(ifr.ifr_name, "ath0");
  3. Capture a known-good baseline via SIOCGATHDIAG with ad_id=HAL_DIAG_ANI_PARAMS (no ATH_DIAG_IN), copy into a local struct ar5212AniParams p, then set p.maxNoiseImmunityLevel = 0x40000000. Issue SIOCGATHDIAG with ad_id=HAL_DIAG_ANI_PARAMS|ATH_DIAG_IN, ad_in_data=&p, ad_in_size=sizeof(p).
  4. Trigger the OOB: uint32_t cmd[2] = { HAL_ANI_NOISE_IMMUNITY_LEVEL, 0x40000000 }; issue SIOCGATHDIAG with ad_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 through ar5416AniControl:238 (panic-backtrace will show ar5416AniControl β†’ ar5212GetDiagState β†’ ar5416GetDiagState β†’ ath_ioctl_diag).

For a non-faulting demonstration of the indexed-table defect, use level=6 with maxNoiseImmunityLevel=7 β€” silent misprogramming of AR_PHY_DESIRED_SZ/AR_PHY_AGC_CTL1/AR_PHY_FIND_SIG with OOB-derived values, observable via HAL_DIAG_REGS.

Primary fix at the shared sink β€” validate the max*Level fields against the actual array dimensions whenever params are accepted from outside the HAL. Cheapest correct point is ar5212AniSetParams (which is what ar5416GetDiagState actually invokes via the fall-through, so a single fix closes both ar5212 and ar5416). This is the same fix proposed in DF-1660; it is restated here because the vulnerable OOB sink in ar5416_ani.c is independently reachable.

See DF-1660 for the ar5212_ani.c patch.

Additionally, defense-in-depth for ar5416_ani.c itself (so a future internal caller that hands in a corrupted maxLevel cannot trigger the same OOB):

--- a/sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c
+++ b/sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c
@@ -227,6 +227,7 @@
        u_int level = param;

        HALDEBUG(ah, HAL_DEBUG_ANI, "%s: HAL_ANI_NOISE_IMMUNITY_LEVEL: set level = %d\n", __func__, level);
+       if (level > nitems(params->totalSizeDesired))
+           return AH_FALSE;
        if (level > params->maxNoiseImmunityLevel) {
@@ -314,6 +315,7 @@
        u_int level = param;

        HALDEBUG(ah, HAL_DEBUG_ANI, "%s: HAL_ANI_FIRSTEP_LEVEL: level = %d\n", __func__, level);
+       if (level > nitems(params->firstep))
+           return AH_FALSE;
        if (level > params->maxFirstepLevel) {
@@ -333,6 +335,7 @@
        u_int level = param;

        HALDEBUG(ah, HAL_DEBUG_ANI, "%s: HAL_ANI_SPUR_IMMUNITY_LEVEL: level = %d\n", __func__, level);
+       if (level > nitems(params->cycPwrThr1))
+           return AH_FALSE;
        if (level > params->maxSpurImmunityLevel) {

Note: > nitems() (not >=) preserves the existing inclusive-max semantics, where the in-tree defaults already use maxNoiseImmunityLevel=4, maxSpurImmunityLevel=7, maxFirstepLevel=2 (ar5212_attach.c:199/204/206).

  • DF-1660 (ar5212_ani.c β€” same shared root cause via ar5212AniSetParams)
  • DF-1520/1521/1522 (ath_hal ah.c SIOCGATHDIAG memory disclosure class)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1675 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for ar5416 ANI control OOB index variant 458 B view raw
VERDICT.md verdict Source-only verification verdict 821 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.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1675: ar5416 ANI control OOB index variant

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

Same class as DF-1660: maxLevel gates check against params but arrays are fixed-size.

Source reference: sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:226-252.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:228. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

Add nitems bounds check. Matches finding.

Verdict

REPRODUCED (source-confirmed). maxLevel indexes fixed arrays OOB (DF-1660 variant). Cited path verified at sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:228. HW/module-gated on QEMU guest.