β¬’ 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 726 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 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 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)