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

ar5416ChainTxDesc indexes ah_keytype[] unconditionally, causing OOB read when keyIx == HAL_TXKEYIX_INVALID

Summary

In ar5416ChainTxDesc(), the if (keyIx != HAL_TXKEYIX_INVALID) block guards the DestIdx/Valid writes (ar5416_xmit.c:583-587) but the per-key encryption-type lookup SM(ahp->ah_keytype[keyIx], AR_EncrType) at ar5416_xmit.c:589 sits OUTSIDE that block.

When a caller passes HAL_TXKEYIX_INVALID (defined as ((u_int)-1) in ah_desc.h:222) to indicate "no encryption", the code reads ahp->ah_keytype[0xFFFFFFFF] β€” a wild one-byte OOB read whose result is fed into the TX descriptor's AR_EncrType field.

The sibling function ar5416SetupTxDesc() places the identical lookup correctly INSIDE the INVALID guard (ar5416_xmit.c:390-395), confirming this is a misplaced-statement defect rather than an intentional idiom.

Root cause

ar5416_xmit.c:583-589:

if (keyIx != HAL_TXKEYIX_INVALID) {            /* guards DestIdx only */
    /* XXX validate key index */
    ads->ds_ctl1 |= SM(keyIx, AR_DestIdx);
    ads->ds_ctl0 |= AR_DestIdxValid;
}

ads->ds_ctl6 |= SM(ahp->ah_keytype[keyIx], AR_EncrType);   /* line 589: UNCONDITIONAL */

ah_keytype is uint8_t ah_keytype[AR5416_KEYTABLE_SIZE] with AR5416_KEYTABLE_SIZE == 128 (ar5416.h:56,116).

HAL_TXKEYIX_INVALID is ((u_int)-1) == 0xFFFFFFFF (ah_desc.h:222).

For unencrypted frames the driver convention is to pass INVALID, so keyIx == 0xFFFFFFFF and the array access is wild.

The XXX comment two lines up ("validate key index") acknowledges that no bounds check exists; the misplacement compounds it by removing even the INVALID sentinel check.

ar5416SetupTxDesc shows the intended structure: the same SM(ahp->ah_keytype[keyIx], AR_EncrType) write lives inside the if (keyIx != HAL_TXKEYIX_INVALID) body (ar5416_xmit.c:390-395).

Threat

The bug is real by inspection but currently DORMANT in-tree: ar5416ChainTxDesc is registered as the HAL method ah_chainTxDesc (ar5416_attach.c:184) and the public macro ath_hal_chaintxdesc is exposed (if_athvar.h:1438-1442), BUT no code path in sys/dev/netif/ath/ath/ invokes it β€” the modern EDMA TX path explicitly uses ah_fillTxDesc instead (ah.h:1586-1588 comment: "Descriptors are chained together by using filltxdesc (not ChainTxDesc)").

Confirmed by grep: 'ath_hal_chaintxdesc' appears only in the macro definition, never at a call site. So exploitability through the stock driver is currently zero.

The finding is recorded because (a) the method is part of the public HAL vtable and any out-of-tree net80211/HAL consumer (or a future driver refactor) that calls ah_chainTxDesc with the standard INVALID sentinel for plaintext frames would trigger it on every TX, and (b) it is a one-line fix.

If/when reachable: trigger is trivial (transmit any unencrypted frame), impact is a kernel OOB read of 1 byte at offset ~4GB from ah_keytype[] β€” almost certainly an unmapped page β†’ kernel panic (local DoS).

The read byte is written into the descriptor's 2-bit AR_EncrType field that is DMA-consumed by the NIC, so it does not return to userspace and is not a clean info leak; net effect is denial of service via panic on the wild dereference.

Attacker position: unprivileged local user with transmit access to an ath(4) interface that has been switched onto the ChainTxDesc path. No special caps required beyond network access.

Exploit / PoC

Cannot be reproduced against the unmodified in-tree driver because ah_chainTxDesc has no caller.

To make it reproducible (proof-of-concept of the latent bug), one would write a small loadable kernel module that obtains the ath_hal handle from a live ath(4) softc and invokes the method directly via the vtable:

/* kld: trigger ar5416ChainTxDesc OOB */
HAL_DMA_ADDR ba = 0; uint32_t sl = 64;
ah->ah_chainTxDesc(ah, some_desc, &ba, &sl,
    64,        /* pktLen */
    0,         /* hdrLen */
    HAL_PKT_TYPE_NORMAL,
    HAL_TXKEYIX_INVALID,   /* <-- triggers ah_keytype[0xFFFFFFFF] */
    HAL_CIPHER_CLR, 0,
    AH_TRUE, AH_TRUE, AH_FALSE);

Building on DragonFlyBSD: place in a module with DECLARE_MODULE, make against /usr/src/sys/modules/ath or a standalone klddev.

Running: kldload ./chain_oob.ko on a host with ath(4) attached.

Expected result: panic in ar5416ChainTxDesc at the wild array index (likely fatal trap 12: page fault while in kernel mode on the dereference of ah_keytype+0xFFFFFFFF).

Success criterion = deterministic panic proving the OOB read.

Because the path is dead in stock sys/, no trigger program that uses only the public if_ath driver interface can reach it; the PoC must poke the HAL vtable directly, hence "speculative" confidence on real-world exploitability.

Move the ah_keytype[] lookup inside the INVALID guard so it matches ar5416SetupTxDesc, and add the missing bounds check the XXX comment asks for.

--- a/sys/dev/netif/ath/ath_hal/ar5416/ar5416_xmit.c
+++ b/sys/dev/netif/ath/ath_hal/ar5416/ar5416_xmit.c
@@ -580,13 +580,15 @@ ar5416ChainTxDesc(struct ath_hal *ah, struct ath_desc *ds,

    ads->ds_ctl2 = 0;
    ads->ds_ctl3 = 0;
-   if (keyIx != HAL_TXKEYIX_INVALID) {
-       /* XXX validate key index */
-       ads->ds_ctl1 |= SM(keyIx, AR_DestIdx);
-       ads->ds_ctl0 |= AR_DestIdxValid;
-   }
-
-   ads->ds_ctl6 |= SM(ahp->ah_keytype[keyIx], AR_EncrType);
+   if (keyIx != HAL_TXKEYIX_INVALID &&
+       keyIx < AR5416_KEYTABLE_SIZE) {
+       ads->ds_ctl1 |= SM(keyIx, AR_DestIdx);
+       ads->ds_ctl0 |= AR_DestIdxValid;
+       ads->ds_ctl6 |= SM(ahp->ah_keytype[keyIx], AR_EncrType);
+   } else {
+       /* No/invalid key index: encryption type = clear. */
+       ads->ds_ctl6 &= ~AR_EncrType;
+   }
    if (isaggr) {
        ads->ds_ctl6 |= SM(delims, AR_PadDelim);
    }

This mirrors ar5416SetupTxDesc's structure (ar5416_xmit.c:390-395), bounds the index against AR5416_KEYTABLE_SIZE (128) as the XXX comment requests, and explicitly clears the AR_EncrType field for plaintext so the descriptor is not left with stale encryption-type bits.

The same explicit keyIx < AR5416_KEYTABLE_SIZE check should also be retro-fitted into ar5416SetupTxDesc at ar5416_xmit.c:390 to harden the reachable path.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1518 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 690 B view raw
VERDICT.md verdict source-confirmation analysis 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1518 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: ar5416_xmit.c:583-589 ar5416ChainTxDesc: if (keyIx != HAL_TXKEYIX_INVALID) block guards DestIdx/Valid writes (583-587) but ah_keytype[keyIx] EncrType lookup at line 589 sits OUTSIDE block. HAL_TXKEYIX

Citation: sys/dev/netif/ath/ath_hal/ar5416/ar5416_xmit.c:583-589

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

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 reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: ah_keytype[keyIx] outside INVALID guard in ar5416ChainTxDesc (ar5416_xmit.c:589)

Verified recommended fix

Source-confirmed: ah_keytype[keyIx] outside INVALID guard in ar5416ChainTxDesc (ar5416_xmit.c:589)

Verdict

Source-confirmed: ah_keytype[keyIx] outside INVALID guard in ar5416ChainTxDesc (ar5416_xmit.c:589)