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

ar5211_xmit: signed q<0 bypasses the >=-only checks in ar5211Set/GetTxQueueProps down to &ah_txq[q], and five exported xmit ops gate q only via inert HALASSERT (ar5211 analogue of DF-4519)

Summary

Set/GetTxQueueProps take signed q and check only >=, so negative q reaches &ah_txq[q] - setTxQProps reads tqi_type then writes ~15 fields below the array; the five tx ops gate q only via the production-no-op HALASSERT, leaving AR_QTXDP(q) OOB MMIO and 1<<q UB unvalidated. Latent (all in-tree callers pass validated 0..9); same class as verified DF-4519 (ar5212). Hardware-gated on guest. Mirror-DF-4519 fix, build-validated on guest (-Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4712 Β· 8 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 5.9 KB ↓ raw
fix.diff β€” 2.3 KB view raw
build.log β€” 5.1 MB ↓ download
env.txt β€” 856 B view raw
manifest.json β€” 1.6 KB view raw
verdict.json β€” 4.1 KB view raw
build.sh β€” 154 B view raw

DF-4712 β€” ar5211 HAL xmit ops: signed/negative tx-queue index reaches ah_txq[q] and unchecked MMIO (ar5211 analogue of DF-4519)

File: sys/dev/netif/ath/ath_hal/ar5211/ar5211_xmit.c (pass 2, GLM 5.3) Severity: Low (latent β€” no unprivileged trigger today) Β· Confidence: certain (code pattern) Β· Bucket: base:net

Build / Run

This finding is hardware-gated (requires an Atheros AR5211-family NIC to reach any HAL xmit op at runtime; the audit QEMU guest has none β€” verified: pciconf -l | grep -i ath β†’ empty). Per the DF-4519/DF-2201 HW-gated protocol there is no runtime PoC; the defect is source-verified and the fix is build-validated against the guest kernel:

# guest (root):
cd /usr/src && patch -p1 < /tmp/df4712_fix.diff
make nativekernel KERNCONF=X86_64_GENERIC     # rc=0, -Werror (build.log)

Expected: ar5211_xmit.o compiles into the kernel with real runtime q checks; behavior on this guest is otherwise unchanged (no ath hardware present).

What to look at

  • VERDICT.md β€” full narrative, reachability analysis, killed candidates.
  • build.log β€” full untrimmed nativekernel log (BUILD_RC=0).
  • fix.diff β€” runtime q validation for the five exported xmit ops + signed-q rejection in ar5211SetTxQueueProps/ar5211GetTxQueueProps.
  • env.txt β€” guest uname, AH_ASSERT absence in X86_64_GENERIC, no-ath proof.
VERDICT.md
↓ download raw

DF-4712 VERDICT β€” ar5211_xmit.c signed-q OOB + inert-assert queue validation

Status: untested (runtime) β€” source-confirmed defect; runtime reproduction hardware-gated (no Atheros NIC on the guest). Fix build-validated (nativekernel rc=0, -Werror). Same class and same driver guards as sibling DF-4519 (ar5212_xmit, verified 2026-07); this pack covers the ar5211 variant.

The defect

ar5211SetTxQueueProps(struct ath_hal *ah, int q, ...) β€” sys/dev/netif/ath/ath_hal/ar5211/ar5211_xmit.c:80-90:

if (q >= HAL_NUM_TX_QUEUES) {   /* line 84 β€” signed int q, no q < 0 check */
        ...
        return AH_FALSE;
}
return ath_hal_setTxQProps(ah, &ahp->ah_txq[q], qInfo);   /* line 89 */
  • q is signed; any negative value passes the >=-only check.
  • ahp->ah_txq is HAL_TX_QUEUE_INFO ah_txq[HAL_NUM_TX_QUEUES=10] (ar5211/ar5211.h:121; HAL_NUM_TX_QUEUES=10 at ah.h:236).
  • ath_hal_setTxQProps (ah.c:951) first reads qi->tqi_type (ah.c:958) and then writes ~15 fields (tqi_ver/qflags/aifs/cwmin/ cwmax/shretry/…, ah.c:962-1010) through the out-of-bounds pointer below the array β€” an OOB kernel read + OOB kernel write into adjacent struct ath_hal_5211 state (and, for large |q|, beyond the object).
  • ar5211GetTxQueueProps (lines 95-106) has the identical signed-q hole β†’ OOB read via ath_hal_getTxQProps.

Additionally, five exported xmit ops gate q only via HALASSERT:

op HALASSERT-only line unchecked sink
ar5211GetTxDP :405 OS_REG_READ(ah, AR_QTXDP(q)) OOB MMIO
ar5211SetTxDP :415 OS_REG_WRITE(ah, AR_QTXDP(q), txdp) OOB MMIO write
ar5211StartTxDma :435 OS_REG_WRITE(ah, AR_Q_TXE, 1 << q) UB shift + OOB MMIO
ar5211NumTxPending :458 AR_QSTS(q) OOB MMIO
ar5211StopTxDma :480 AR_Q_TXD 1<<q UB + OOB MMIO

HALASSERT expands to nothing unless the kernel is built with AH_ASSERT (ah_internal.h:665-676; option only in LINT β€” verified absent from the guest's running X86_64_GENERIC config). Production kernels therefore perform zero validation of q on these paths. (u_int q in these five means no negative index, but any qβ‰₯10 reaches register space computed as AR_QTXDP(q) etc., and 1 << q with qβ‰₯31 is UB.)

Reachability (why Low/latent)

Every in-tree caller passes driver-internal queue numbers:

  • if_ath.c:4013-4032 β€” qnum = ath_hal_setuptxqueue(...); the driver rejects -1 and qnum >= nitems(sc->sc_txq) before storing axq_qnum.
  • if_ath.c:4125 β€” ath_hal_settxqueueprops(ah, txq->axq_qnum, &qi) uses only those validated qnums.
  • ar5211SetupTxQueue itself only ever returns literal 0/8/9 or -1 (ar5211_xmit.c:119-135).

So there is no unprivileged trigger today; the defect is a latent kernel OOB-read/write primitive that any future/buggy caller (or a refactored multiqueq path) can trip. ar5211 has no EEPROM-derived queue-count bound issue (unlike DF-4520 on ar5212): every op here bounds by the constant HAL_NUM_TX_QUEUES β€” the checks are simply signed-only or assert-only.

Verification performed

  1. Source-traced every caller of the seven exported ops (if_ath.c, if_ath_tx.c, if_ath_beacon.c, if_ath_tdma.c) β€” all pass validated indexes.
  2. Confirmed HALASSERT is inert in production (no AH_ASSERT in X86_64_GENERIC; sys/conf/options:549; ah_internal.h:669-676).
  3. Guest confirmed no Atheros NIC (pciconf -l β€” see env.txt), so no runtime trigger exists on the audit guest.
  4. fix.diff applied to guest /usr/src; make nativekernel KERNCONF=X86_64_GENERIC completed rc=0 with -Werror (build.log) β€” the patched ar5211_xmit.c compiles into the kernel with real runtime bounds checks.

Killed candidates (negative results, this file)

  • txRate0 << AR_XmitRate_S unmasked (ar5211_xmit.c:537): the 5211 rate tables only emit codes 0x08-0x0f (4-bit field, ar5211_phy.c:31-76; short-preamble mask is 0x04, not 0x10 as on 5212), and every driver caller sources txrate from rt->info[].rateCode (if_ath_tx.c:1660-1691, 2199) β€” never out of field. Not reachable.
  • segLen unmasked in ar5211FillTxDesc (:589-610): reachable segment lengths are bounded < 0x1000 (802.11 fragmentation threshold ~2346; bpf raw-inject chunks at MCLBYTES), so bits above AR_BufLen are never set in practice. Hardening-only (worth masking like pktLen).
  • ar5211GetTxIntrQueue stub leaves *txqs untouched (:663-667): the driver pre-initializes txqs = 0xffffffff (if_ath.c:2275) β€” no uninitialized read; conservative "all queues" semantics.
  • ar5211GetTxCompletionRates stub returns AH_FALSE (:672-676); the ath_hal_gettxcompletionrates macro (if_athvar.h:1420) has no callers in the driver .c files.
  • ar5211ProcTxDesc (:619-657): every decode is a bounded MS() field extraction into struct ath_tx_status; no array indexing by HW-controlled values. Clean.
  • (type << 26) vs AR_FrmType_S=20 (:544 vs ar5211desc.h:58-59): quirk inherited verbatim from FreeBSD HAL; functional (frame-type field placement), not memory safety. Not filed.
  • CAB readytime unsigned arithmetic (:347-350) can wrap for tiny beacon intervals β€” bad register value, no memory-safety impact.
  • ar5211UpdateTxTrigLevel ISR/non-ISR concurrent lost-update (:38-72): mask-bounded register field, non-security.

fix.diff (this pack): reject q < 0 in Set/GetTxQueueProps; replace the inert HALASSERT q-gates in GetTxDP/SetTxDP/StartTxDma/NumTxPending/StopTxDma with runtime checks returning 0/AH_FALSE β€” mirrors the accepted DF-4519 fix.

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 hardware-gated): fix.diff applied to guest /usr/src; make nativekernel KERNCONF=X86_64_GENERIC BUILD_RC=0 with -Werror; patched ar5211_xmit.c compiled into the kernel (build.log:7139). Baseline/patched runtime behavior not observable without an Atheros NIC.

findings/poc/DF-4712/build.log (full untrimmed nativekernel log; decisive lines: 5505 mkdep ar5211_xmit.c, 7139 compile -Werror, final BUILD_RC=0)
↓ fix.diffper-fix-DF-4712

Confirmed kernel references

Detail

Exploit chain

[]

Evidence (decisive lines)

findings/poc/DF-4712/VERDICT.md (full reachability + killed-candidate narrative); build.log (full nativekernel log, BUILD_RC=0; decisive line 7139 ar5211_xmit.c -Werror); env.txt (no-ath pciconf proof, AH_ASSERT absent from X86_64_GENERIC); fix.diff (7 runtime q checks)

PoC changes

n/a (no runtime PoC attempted; source-only confirmation per DF-4519/DF-2201 hardware-gated protocol)

Verified recommended fix

Replace the inert HALASSERT q-gates in ar5211GetTxDP/SetTxDP/StartTxDma/NumTxPending/StopTxDma with runtime checks (return 0/AH_FALSE on bad q) and reject q<0 in ar5211SetTxQueueProps/ar5211GetTxQueueProps (mirrors accepted DF-4519 fix).

Verdict

untested (runtime): ar5211SetTxQueueProps/ar5211GetTxQueueProps take signed int q and check only q >= HAL_NUM_TX_QUEUES, so q<0 reaches &ahp->ah_txq[q] (ar5211.h:121, HAL_NUM_TX_QUEUES=10) and ath_hal_setTxQProps (ah.c:951) writes ~15 fields through the OOB pointer; GetTxDP/SetTxDP/StartTxDma/NumTxPending/StopTxDma gate q only via HALASSERT, which expands to nothing in production (ah_internal.h:669-676, no AH_ASSERT in X86_64_GENERIC - verified on guest). All in-tree callers pass driver-validated 0..9 (if_ath.c:4013-4032), so no trigger exists on the guest (no Atheros NIC - verified). Source-confirmed defect; ar5211 analogue of sibling DF-4519 (ar5212), which used the same bounds constant bug shape. Fix build-validated: nativekernel KERNCONF=X86_64_GENERIC BUILD_RC=0 with -Werror, patched ar5211_xmit.c compiled into the kernel (build.log:7139).