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

ar5212_xmit: halTotalQueues taken unclamped from EEPROM AR_EEP_MAXQCU (raw 5-bit, 1..31) bounds ah_txq[HAL_NUM_TX_QUEUES=10] in every queue op -> SetupTxQueue OOB scan read + OS_MEMZERO OOB heap write at attach on crafted EEPROM

Summary

ah_txq is sized 10 but every queue op bounds by halTotalQueues, copied unclamped from the raw 5-bit EEPROM MAXQCU (1..31). SetupTxQueue's DATA path scans past ah_txq[9], finds a zeroed adjacent slot (INACTIVE==0), then OS_MEMZEROs sizeof(HAL_TX_QUEUE_INFO) out of bounds; BEACON/CAB/UAPSD reach index 30. Requires a crafted EEPROM (physical-card tampering model). AH_MIN clamp fix build-validated. CROSS-FILE: ar5416_attach.c:957-959 has the identical unclamped assignment - flagged for the 11n HAL.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4520 Β· 6 files
FileTypeDescriptionSize
VERDICT.md verdict full dataflow trace + fix rationale 3.4 KB ↓ raw
verdict.json verdict-machine persist_poc.py input 3.3 KB view raw
fix.diff suggested-fix AH_MIN(val, HAL_NUM_TX_QUEUES) clamp in ar5212_attach.c 372 B view raw
build.log build-log full nativekernel log, BUILD_RC=0, -Werror (shared with DF-4519 build) 5.1 MB ↓ download
build.sh script build validation steps 147 B view raw
run.sh script no-op: HW-gated, see VERDICT.md 156 B view raw
VERDICT.md verdict full dataflow trace + fix rationale
↓ download raw

DF-4520 β€” ar5212_xmit.c: EEPROM-derived halTotalQueues (MAXQCU, 5 bits, unclamped) bounds ah_txq[10] β†’ OOB read + OS_MEMZERO OOB write

What

struct ath_hal_5212 sizes its queue array ah_txq[HAL_NUM_TX_QUEUES] with HAL_NUM_TX_QUEUES == 10 (sys/dev/netif/ath/ath_hal/ar5212/ar5212.h:271, sys/dev/netif/ath/ath_hal/ah.h:236). At attach, ar5212Attach copies the EEPROM opcap field AR_EEP_MAXQCU into pCap->halTotalQueues without clamping (sys/dev/netif/ath/ath_hal/ar5212/ar5212_attach.c:862-865; ah_eeprom_v3.c:1640-1644 returns MS(ee->ee_opCap, AR_EEPROM_EEPCAP_MAXQCU) β€” a raw 5-bit field, 1..31, ah_eeprom_v3.h:72-73).

Every queue function in ar5212_xmit.c then uses halTotalQueues (not the array bound) as the limit for ah_txq[q]:

  • ar5212SetupTxQueue DATA path scans ahp->ah_txq[q].tqi_type for q < pCap->halTotalQueues (lines 160-162) β†’ reads past ah_txq[9];
  • found "inactive" (HAL_TX_QUEUE_INACTIVE == 0, ah.h:226 β€” adjacent heap zeroes read as inactive), it does qi = &ahp->ah_txq[q] and OS_MEMZERO(qi, sizeof(HAL_TX_QUEUE_INFO)) (lines 177-183) β†’ OOB heap write into ah_intrTxqs/ah_decompMask[]/beyond the allocation;
  • BEACON/CAB/UAPSD paths compute q = halTotalQueues-1/-2/-3 (lines 137/144/ 152) then read ah_txq[q] (line 153, 178) β†’ up to index 30;
  • ar5212SetTxQueueProps:91, GetTxQueueProps:109, ReleaseTxQueue:237, ResetTxQueue:277 all gate on q >= halTotalQueues β†’ the same unclamped cap authorizes ah_txq[10..30] accesses.

Trigger (honest)

Requires an ar5212-family NIC whose EEPROM opcap encodes MAXQCU > 10 (real AR5212 parts program ≀10; the 5-bit field allows up to 31) β€” i.e. a crafted / corrupted EEPROM (physical-card trust boundary), read at driver attach (if_ath β†’ ar5212GetCapability/queue setup). No unprivileged-software trigger exists: the value comes only from the card's own EEPROM, and a card whose EEPROM an attacker can rewrite implies physical access (out of the local-user threat model; the same device could DMA memory anyway). Low / speculative β€” latent hardening gap of the same family as DF-2201 / DF-2073.

Fix

Clamp the cap where it enters (single choke point):

--- a/sys/dev/netif/ath/ath_hal/ar5212/ar5212_attach.c
+++ b/sys/dev/netif/ath/ath_hal/ar5212/ar5212_attach.c
@@ -860,7 +860,7 @@
        pCap->halTsfAddSupport = AH_TRUE;

    if (ath_hal_eepromGet(ah, AR_EEP_MAXQCU, &val) == HAL_OK)
-       pCap->halTotalQueues = val;
+       pCap->halTotalQueues = AH_MIN(val, HAL_NUM_TX_QUEUES);
    else
        pCap->halTotalQueues = HAL_NUM_TX_QUEUES;

Full diff in fix.diff. Note: ar5416_attach.c:957-959 has the identical unclamped AR_EEP_MAXQCU assignment for the 11n HAL (out of this file's scope; should get the same one-line clamp when audited).

Build validation

Applied to guest /usr/src together with DF-4519's diff; full make nativekernel KERNCONF=X86_64_GENERIC rc=0 with -Werror (ar5212_attach.o rebuilt: build.log lines 7145/13441). Runtime behavior is not testable on the audit guest (no Atheros NIC).

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

fixed
baseline no→ patch + rebuild →patched clean

build-only validation (runtime HW-gated): fix.diff applied to guest /usr/src; make nativekernel KERNCONF=X86_64_GENERIC rc=0 with -Werror; ar5212_attach.o rebuilt (build.log:7145,13441,14895-14897). Baseline/patched runtime not reproducible without an Atheros NIC with a crafted EEPROM.

findings/poc/DF-4520/build.log (full untrimmed nativekernel log, BUILD_RC=0; decisive lines 3, 7145, 13441, 14895, 14897)
↓ fix.diffper-fix-DF-4520

Confirmed kernel references

Detail

Exploit chain

[]

Evidence (decisive lines)

AR_EEP_MAXQCU returns MS(ee->ee_opCap, 0x01F0) = raw 5-bit 1..31 (ah_eeprom_v3.c:1640-1644, ah_eeprom_v3.h:72-73); ar5212_attach.c:862-865 assigns it to halTotalQueues unclamped; ah_txq is [HAL_NUM_TX_QUEUES=10] (ar5212.h:271, ah.h:236); ar5212_xmit.c:160-162 scans ah_txq[q] q<halTotalQueues, :177-183 OS_MEMZEROs &ah_txq[q] (HAL_TX_QUEUE_INACTIVE==0 so adjacent zeroed heap reads as free), :137/:144/:152 compute q=halTotalQueues-1/-2/-3. Fix clamp AH_MIN(val, HAL_NUM_TX_QUEUES) build-validated rc=0 -Werror (build.log:7145,13441,14897).

PoC changes

n/a (no runtime PoC attempted; source-only confirmation per HW-gated protocol)

Verified recommended fix

Clamp pCap->halTotalQueues = AH_MIN(val, HAL_NUM_TX_QUEUES) at the single choke point in ar5212_attach.c (and mirror in ar5416_attach.c).

Verdict

untested (runtime): halTotalQueues is a raw 5-bit EEPROM opcap field copied unclamped into the bound used for ah_txq[10] everywhere in ar5212_xmit.c; a crafted EEPROM (MAXQCU 11..31) makes ar5212SetupTxQueue read ah_txq[10..30] and OS_MEMZERO an out-of-bounds slot at attach. Real cards program <=10, so the trigger needs physical EEPROM tampering; no Atheros NIC exists on the guest. Source-confirmed; fix.diff clamps the cap and build-validated rc=0 -Werror.