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)
PoC verification
Evidence pack
findings/poc/DF-4208 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedfix.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'
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:1717
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:1729
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:1732
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:447
- sys/dev/netif/ath/ath/if_ath_ioctl.c:214
- sys/dev/netif/ath/ath/if_ath_ioctl.c:299
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:1061
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.
No comments yet.