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

ar5210GetDiagState HAL_DIAG_EEREAD dereferences args==NULL when SIOCGATHDIAG omits ATH_DIAG_IN β€” kernel page-fault panic (DF-4670 family; sibling of DF-1819, distinct deref)

Summary

The EEREAD case checks only argsize, then dereferences args - ath_ioctl_diag forwards ad_in_size unconditionally while allocating indata only under ATH_DIAG_IN, so ad_in_size=2 with no IN bit reads address 0. Discriminated from known DF-1819 (the *result write) via DYN keeping result valid. Identical deref in the generic ah.c:904 fallback. ATH_DIAGAPI+ar5210 gated (verified absent on guest; trigger builds+runs harmlessly). Reject-NULL-args two-file fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4705 Β· 10 files
FileTypeDescriptionSize
trigger.c β€” 2.5 KB view raw
build.sh β€” 101 B view raw
run.sh β€” 98 B view raw
build.log β€” 134 B view raw
run.log β€” 134 B view raw
guest_session.log β€” 371 B view raw
env.txt β€” 714 B view raw
VERDICT.md β€” 2.7 KB ↓ raw
fix.diff β€” 1012 B view raw
verdict.json β€” 3.3 KB view raw
VERDICT.md
↓ download raw

VERDICT β€” DF-4705

status: inconclusive (hardware/option-gated; runtime confirmation impossible on the audit guest) Β· reproduced: 0 Β· analysis confidence: certain

Was it reproduced?

No. The QEMU audit guest has (all verified this run, see guest_session.log):

  1. no Atheros PCI device (pciconf -l has no 0x168c vendor);
  2. no ath interface (ifconfig -a β†’ 0);
  3. stock X86_64_GENERIC has no ATH_DIAGAPI/AH_PRIVATE_DIAG options (grep β†’ 0 matches) and the shipped if_ath.ko contains no ath_ioctl_diag symbol (nm β†’ 0), so the SIOCGATHDIAG entry point does not exist in the running kernel.

The trigger builds and runs on the guest (CC_EXIT=0, RUN_EXIT=0, ioctl β†’ -1 errno=6 ENXIO, no panic) β€” proving the userspace side is correct and the failure is purely the missing hardware/kernel-option gate, not the PoC.

Why the finding is real (code trace)

  • sys/dev/netif/ath/ath/if_ath_ioctl.c:174 void *indata = NULL;
  • :176 insize = ad->ad_in_size; (attacker-controlled, taken regardless of flags)
  • :180-192 indata is allocated only if (ad->ad_id & ATH_DIAG_IN); with the bit clear, args = indata = NULL while argsize = insize = 2 is forwarded at :214 into ath_hal_getdiagstate().
  • sys/.../ar5210/ar5210_misc.c:638 checks only argsize != sizeof(uint16_t) β†’ passes.
  • :642 *(const uint16_t *)args β†’ 2-byte read from address 0 β†’ page fault β†’ panic. The read happens while evaluating the call arguments of ath_hal_eepromRead() regardless of evaluation order; the PoC supplies ATH_DIAG_DYN so *result is a valid kmalloc'd buffer β€” the only NULL deref on the line is args (this is the discriminator against known DF-1819, whose trigger leaves *result NULL by omitting DYN).
  • Identical deref in the generic fallback ah.c:904 (compiled without AH_PRIVATE_DIAG) β€” same NULL read reachable on all ath HALs.

Exploit chain

None β€” NULL read β†’ controlled panic only (unprivileged local DoS, on double-gated configurations). No write primitive.

Fix validation

Not possible on this guest (baseline cannot be triggered without ar5210 hardware + ATH_DIAGAPI). fix.diff is authored from the line-accurate root cause above; fix_status: not_testable.

Guest state

Guest untouched except /root/poc/trigger{,.c} (removed after the run); no panic, guest stayed up; guest_dirty: 0.

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 impossible on this guest: baseline requires ar5210 hardware + ATH_DIAGAPI kernel (both absent); fix.diff authored from line-accurate root cause and mirrors the DF-1819-style NULL guards extended to args

fix.diff (ar5210_misc.c + ah.c hunks); guest_session.log gate evidence
↓ fix.diffper-fix-DF-4705

Confirmed kernel references

Detail

Evidence (decisive lines)

guest_session.log (uname, pciconf 0x168c absent, ifconfig 0 ath, GENERIC 0 ATH_DIAGAPI matches, nm if_ath.ko 0 ath_ioctl_diag, nm ath_hal.ko shows ar5210GetDiagState present); trigger.c (ATH_DIAG_DYN|HAL_DIAG_EEREAD, ad_in_size=2, no ATH_DIAG_IN); build.log CC_EXIT=0; run.log 'ioctl -> -1 errno=6 ... no panic'; VERDICT.md full trace if_ath_ioctl.c:174->ar5210_misc.c:638,642

PoC changes

seed trigger rewrote: trimmed struct ath_diag from if_athioctl.h (not in guest /usr/include), set ad_id=ATH_DIAG_DYN|HAL_DIAG_EEREAD (0x8011) with ad_in_size=2 and NO ATH_DIAG_IN so the ONLY NULL deref on line 642 is args (discriminates from DF-1819); ioctl request re-derived as _IOWR('i',138,...)

Verified recommended fix

reject NULL args/result in the HAL_DIAG_EEREAD case of ar5210GetDiagState and the generic ath_hal_getdiagstate fallback (see fix.diff)

Verdict

ar5210GetDiagState HAL_DIAG_EEREAD (ar5210_misc.c:642) dereferences args==NULL when SIOCGATHDIAG omits ATH_DIAG_IN while setting ad_in_size=2: ath_ioctl_diag (if_ath_ioctl.c:174-192) only allocates indata under ATH_DIAG_IN but forwards insize unconditionally, so the argsize check at :638 passes and (const uint16_t )args reads address 0 -> kernel page-fault panic. Distinct from known DF-1819 (the result NULL write in the same expression; this trigger supplies ATH_DIAG_DYN so result is valid) and the ar5210 instance of the DF-4670 args==NULL family; the unconditional generic fallback ah.c:904 has the identical deref. Runtime confirmation impossible on the audit guest: no Atheros PCI device, no ath interface, no ATH_DIAGAPI option in X86_64_GENERIC and no ath_ioctl_diag symbol in the stock if_ath.ko (all verified, guest_session.log); the trigger builds (CC_EXIT=0) and runs harmlessly (ENXIO) on the guest, proving the PoC side correct and the block purely hardware/option-gated.