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

ar5312GetMacAddr dereferences unvalidated BSP board-config pointer; 6-byte wild read becomes the unprivileged-readable Wi-Fi MAC on a SoC build with corrupt flash board-data

Summary

The BOARDCONFIG cast blindly loads ->board and OS_MEMCPYs 6 bytes from board->wlanXMac with no NULL check, no AR531X_BD_MAGIC validation, no bounds - the exact condition the magic detects. Purely latent: the file is in NO standard build (conf/files + ath_hal Makefile both lack it; guest-verified 0 ar5312 symbols in ath_hal.ko and kernel). Validate-the-BSP-config fix authored for the day it gets wired up.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5030 Β· 8 files
FileTypeDescriptionSize
README.md β€” 3.9 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
reachability.sh β€” 995 B view raw
reachability.log β€” 290 B view raw
fix.diff β€” 887 B view raw
manifest.json β€” 1018 B view raw
verdict.json β€” 3.0 KB view raw
env.txt β€” 150 B view raw

DF-5030 β€” ar5312_attach.c: unvalidated BSP board-config pointer dereference in ar5312GetMacAddr

File: sys/dev/netif/ath/ath_hal/ar5312/ar5312_attach.c Severity: Info (latent β€” file is not compiled into any standard build) Confidence: certain (code inspection + build-gating proof on the audit guest)

What the code does

ar5312GetMacAddr() (ar5312_attach.c:287-309) obtains the Wi-Fi MAC for the SoC WMAC unit by dereferencing two pointers that come from the HAL bus tag:

const struct ar531x_boarddata *board = AR5312_BOARDCONFIG(ah);
int wlanNum = AR5312_UNIT(ah);
...
macAddr = board->wlan0Mac;   /* or board->wlan1Mac */
OS_MEMCPY(AH5212(ah)->ah_macaddr, macAddr, 6);

AR5312_BOARDCONFIG/AR5312_UNIT (ar5312/ar5312.h:25-30) simply cast ah->ah_st (the HAL_BUS_TAG argument the SoC BSP passed to attach) to struct ar531x_config * and load ->board / ->unit. There is no NULL check, no AR531X_BD_MAGIC (0x35333131, ah_soc.h:35) validation, and no bounds check anywhere between the BSP contract ("the driver is required to locate and fill-in this information", ah_soc.h:79-84) and the 6-byte copy into ah_macaddr. On a SoC build whose flash scan failed to find the "5311" magic (boarddata absent/corrupt β€” exactly the situation the magic constant exists to detect), the copy reads 6 bytes from a wild flash-derived address: at best a kernel panic on unmapped flash, at worst 6 bytes of arbitrary kernel/flash memory that then become the interface MAC and are readable by any unprivileged user via ifconfig/getifaddrs.

Why it is only Info

The file is dead code on every standard DragonFly build:

  • sys/conf/files:153-232 lists the whole ath_hal (ar5210, ar5211, ar5212, ar5416, ar9001, ar9002, ar9300 contrib) β€” no ar5312 entry.
  • sys/dev/netif/ath/ath_hal/Makefile (kmod) SRCS β€” no ar5312 path.
  • AH_SUPPORT_2316/AH_SUPPORT_2317 appear nowhere in the tree, so even a hand-wired build compiles out ar5312_attach.c:101-109/134-146.
  • AH_CHIP(AR5312, ...) (ar5312_attach.c:335) never registers, so ar5312Probe/ar5312Attach are unreachable even inside a loaded ath_hal.
  • Verified on the audit guest (reachability.log): 0 ar5312 references in sys/conf/files, 0 in the kmod Makefile, 0 ar5312 symbols in the shipped ath_hal.ko and in the static kernel.

The remaining latent nits found in the same file (recorded, not filed): duplicated AR_SREV_VERSION_VENICE term in the unsupported-chip check (ar5312_attach.c:157-160, upstream FreeBSD "XXX THIS IS WRONG" quirk, fails closed); dead stores at ar5312_attach.c:149-150; (uint32_t)sh truncation in the RSTIMER reads (ar5312_attach.c:139,149-152) which is only valid for the 32-bit SoC bus layout. The bad: unwind was audited and is sound: ar5212InitState (line 85) sets the magic before any failure, and ar5212RfDetach NULL-guards ah_rfHal (ar5212/ar5212.h:403-406), so no double-free/UAF on any error path.

Reproduce (reachability, read-only)

See reachability.sh / reachability.log β€” run on the QEMU guest via dfbsd-qemu/vm.sh run_root. No PoC exists because the code cannot be reached at runtime on this (or any standard) build.

Validate the BSP-supplied pointer before use (one-line defense-in-depth):

--- a/sys/dev/netif/ath/ath_hal/ar5312/ar5312_attach.c
+++ b/sys/dev/netif/ath/ath_hal/ar5312/ar5312_attach.c
@@ -286,7 +286,12 @@ static HAL_BOOL
 ar5312GetMacAddr(struct ath_hal *ah)
 {
    const struct ar531x_boarddata *board = AR5312_BOARDCONFIG(ah);
-        int wlanNum = AR5312_UNIT(ah);
+   const struct ar531x_config *config;
+        int wlanNum;
         const uint8_t *macAddr;
+
+   if ((config = (const struct ar531x_config *) ah->ah_st) == AH_NULL ||
+       (board = config->board) == AH_NULL ||
+       board->magic != AR531X_BD_MAGIC)
+       return AH_FALSE;
+   wlanNum = config->unit;

(Full context in fix.diff; never applied to sys/.)

VERDICT.md
↓ download raw

DF-5030 VERDICT β€” untested (Phase V skipped: Info-severity latent finding)

Bottom line

ar5312GetMacAddr() (sys/dev/netif/ath/ath_hal/ar5312/ar5312_attach.c:287-309) copies 6 bytes from ((struct ar531x_config *)ah->ah_st)->board->wlanXMac with no NULL check, no AR531X_BD_MAGIC validation, no bounds check β€” a genuine validation gap that on a MIPS AR5312/AR231x SoC build with missing or corrupt flash board-data turns into a wild-pointer kernel read whose result (the interface MAC) is exposed to unprivileged users via ifconfig, or a panic. It is not exploitable on any standard DragonFly build because the file is not compiled at all β€” proven three ways (sys/conf/files, ath_hal kmod Makefile, and 0 ar5312 symbols in the shipped ath_hal.ko and static kernel on the audit guest β€” see reachability.log).

Why no runtime PoC (status untested, not not_reproduced)

The trigger requires (a) a kernel built with the ar5312 HAL wired in (no build target does this today) and (b) AR5312-class SoC hardware with attacker-influenced flash. The audit guest is x86_64 with the standard X86_64_GENERIC kernel; the cited path is dead at build time, so there is nothing to run. This is a code-inspection finding with certain confidence about the code, zero runtime exposure.

Evidence

  • reachability.log β€” guest output: conf-files-ar5312-count: 0, hal-makefile-ar5312-count: 0, symbols-ar5312-in-ath_hal.ko: 0, symbols-ar5312-in-kernel: 0.
  • Source: ar5312_attach.c:287-309 (the deref+copy), ar5312/ar5312.h:25-30 (the unchecked casts), ah_soc.h:33-35 (AR531X_BD_MAGIC defined, never checked here), ah_soc.h:79-90 (the BSP contract the HAL trusts blindly).

Other classes checked and killed in this file (pass 2)

  • EEPROM/flash map OOB β€” not in this file (ar5312_eeprom.c); attach-side reads go through ar5312EepromRead indirection only.
  • Ini table counts (ar5312_attach.c:125-126) β€” compile-time tables from ar5212.ini; HAL_INI machinery iterates fixed row/col counts; no attacker-controlled length.
  • ANI params (ar5312_attach.c:36-57) β€” arrays sized 5/3/3 vs max levels 4/2/2; ar5212_ani.c guards every level index (ar5212_ani.c:240,313,331).
  • Error unwind UAF/double-free β€” bad: path (278-283) is safe: magic set by ar5212InitState (85) before any failure; ar5212RfDetach NULL-guards ah_rfHal (ar5212.h:403-406); ath_hal_free called exactly once.
  • devid table sentinel β€” AH_CHIP (ah_internal.h:97-104) linker-set registration; probe table is a complete switch with default AH_NULL return (311-334); no fallthrough.
  • Duplicated VENICE condition (157-160) β€” upstream logic typo, fails closed (attach rejected), no security impact. Dead stores (149-150), cosmetic.

Fix validation

Not performed β€” there is no reproducible baseline behavior to validate against (dead code). fix.diff is the reviewer-authored defense-in-depth patch (NULL + magic validation), untested at runtime by construction.

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

not_testable
baseline no→ patch + rebuild →patched clean

Fix validation not applicable: Info-severity latent finding, no runtime baseline exists on the guest (file not compiled), Phase V skipped per contract.

n/a (no fix build performed)
↓ fix.diffper-fix-DF-5030

Confirmed kernel references

Detail

Evidence (decisive lines)

reachability.log (guest: conf-files-ar5312-count 0, hal-makefile-ar5312-count 0, symbols-ar5312-in-ath_hal.ko 0, symbols-ar5312-in-kernel 0); ar5312_attach.c:287-309; ar5312/ar5312.h:25-30; ah_soc.h:33-35,79-90; sys/conf/files:153-232; VERDICT.md

PoC changes

No PoC built: the cited path is not compiled into the shipped ath_hal.ko or kernel (proven read-only on the guest). A reachability probe script (reachability.sh) replaces the trigger.

Verified recommended fix

Validate the BSP-supplied ar531x_config in ar5312GetMacAddr: reject NULL config/board and board->magic != AR531X_BD_MAGIC before the 6-byte copy (fix.diff).

Verdict

ar5312GetMacAddr (sys/dev/netif/ath/ath_hal/ar5312/ar5312_attach.c:287-309) dereferences the BSP-supplied ah_st as struct ar531x_config* and copies 6 bytes from board->wlanXMac with no NULL check and no AR531X_BD_MAGIC validation; on a MIPS SoC build with missing/corrupt flash board-data this is a wild-pointer kernel read (panic, or 6 bytes of kernel/flash memory exposed as the unprivileged-readable Wi-Fi MAC). However, the file is not compiled into any standard DragonFly build: sys/conf/files and the ath_hal kmod Makefile contain no ar5312 entry, AH_SUPPORT_2316/2317 are defined nowhere, and the shipped ath_hal.ko and static kernel on the audit guest contain zero ar5312 symbols. Info-severity latent defect; Phase V runtime PoC skipped per contract (code unreachable at build time).