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

legacyEepromDiag() HAL_DIAG_EEPROM_EXP_* union type confusion: returns EEPROM-derived wild pointer/size that ath_ioctl_diag copyout()s (non-canonical -> panic), plus HAL_DIAG_EEPROM full-struct dump leaking 6 kernel heap pointers

Summary

legacyEepromDiag handles HAL_DIAG_EEPROM_EXP_11A/B/G by reading ee_modePowerArray5112 (a #define onto the ee_u union) regardless of which member is live. On AR2413/AR5413 cards the live member is eu_rawDataset2413, so *result reads the first four pier frequencies AS A POINTER (e.g. 0x167114c81478143c - non-canonical) and *resultsize reads pChannels[4] as the size (498,800 bytes). ar5212GetDiagState forwards unchecked and ath_ioctl_diag copyout()s from that pointer -> panic; the full-struct dump additionally leaks 6 live heap pointers. SIOCGATHDIAG has NO privilege check on DF (unlike SIOCZATHSTATS). HARNESS-PROVEN: verbatim extraction with a 4-pier 2413 dataset reproduces the exact non-canonical pointer + provenance match; fix eepMap-gate -> returns 0/NULL. Only on ATH_DIAGAPI kernels (LINT64). eepMap-gate fix (+ add the privilege check).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4208 Β· 8 files
FileTypeDescriptionSize
trigger.c β€” 6.1 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 116 B view raw
fix.diff β€” 724 B view raw
build.log β€” 182 B view raw
run.log β€” 1.8 KB view raw
env.txt β€” 280 B view raw
verdict.json β€” 4.1 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff (eepMap/version gate) applied to the verbatim extraction: legacyEepromDiag returns AH_FALSE for EXP codes on eepMap==2, *result stays NULL, copyout skipped, exit 0 β€” non-canonical pointer eliminated. git apply --check clean against sys/; never applied to the read-only tree.

run.log '===== with fix.diff applied =====' section: 'legacyEepromDiag(EXP_11A) returned 0', '*result = NULL ... copyout skipped', 'PASS (fixed) ... exit=0', 'RESULT: union confusion reproduced on stock, GONE with fix'
↓ fix.diffper-fix-DF-4208

Confirmed kernel references

Detail

Exploit chain

custom kernel with options ATH_DIAGAPI + AR2413/AR5413 NIC + unprivileged local user -> SIOCGATHDIAG(HAL_DIAG_EEPROM_EXP_11A) -> legacyEepromDiag union confusion -> copyout from non-canonical pointer -> kernel panic (DoS); secondary: HAL_DIAG_EEPROM leaks 6 kernel heap pointers (KASLR/heap-layout); a uid0 chain is not available from this primitive (fault is deterministic, no controlled write)

Evidence (decisive lines)

run.log '[EXP_11A] *result = 0x167114c81478143c' + 'NON-CANONICAL kernel pointer ... PANIC' + provenance 'pointer == pChannels[0..3] as u64 ... (match: YES)' + '5-pier variant ... *resultsize = 498800 bytes'; fixed section shows 'returned 0', NULL result, exit 0

PoC changes

No working seed; built sha256-gated verbatim extraction of the full HAL_EEPROM type + legacyEepromDiag from the guest /usr/src; RD_EDGES_POWER had to be extracted first (HAL_EEPROM references it); the AH_PRIVATE shim declared after the struct block; diag constants taken from ah_diagcodes.h values; emulated copyout canonicity check instead of real ioctl (no ath hardware in guest).

Verified recommended fix

Gate HAL_DIAG_EEPROM_EXP_* in legacyEepromDiag on the 5112 layout actually being live (ee_version >= AR_EEPROM_VER4_0 && ee_eepMap == 1); independently add a privilege check to ath_ioctl_diag.

Verdict

Reproduced via source-faithful harness on the audit guest: legacyEepromDiag() (verbatim kernel code, sha256-gated extraction) on a 2413-layout (eepMap==2, AR2413/AR5413) card returns result = 0x167114c81478143c β€” byte-for-byte the four EEPROM-derived pier frequencies pChannels[0..3] reinterpreted as a pointer (provenance check: match YES) β€” which is non-canonical, so the SIOCGATHDIAG copyout in ath_ioctl_diag() page-faults -> kernel panic; with 5 piers pe->numChannels aliases pChannels[4] giving resultsize = 498,800 EEPROM-derived bytes. HAL_DIAG_EEPROM additionally dumps all 6568 bytes of HAL_EEPROM including six live kernel heap pointers. Reachability is conditional: SIOCGATHDIAG has no privilege check in DragonFly (if_ath_ioctl.c:299) but ATH_DIAGAPI is not in X86_64_GENERIC, so the unprivileged panic/pointer-leak requires a custom kernel with options ATH_DIAGAPI plus an ath NIC β€” hence on-guest impact none (guest has no ath hardware and no ATH_DIAGAPI). fix.diff (eepMap gate) validated: EXP codes refused, NULL result, exit 0.