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

Unprivileged heap OOB read / wild MMIO read via HAL_DIAG_REGS: ath_hal_getregdump indexes input without consulting its length

Summary

ath_ioctl_diag copyins user-controlled indata (insize=ad_in_size u16 fully attacker-controlled) at :189 then passes to ath_hal_getdiagstate at :214. For id HAL_DIAG_REGS HAL calls ath_hal_getregdump (ah.c:891) which DISCARDS argsize; loop ah.c:848-868 bounded on OUTPUT space only never on insize: regs[i] iterated past indata allocation when outsize large. Leaked heap bytes written as start/end markers then OS_REG_READ(ah,r) at leaked/attacker-influenced offset r -> wild bus read -> kernel panic (DoS) or leaked neighboring slab contents copyouted to user (info disclosure). Trigger: SIOCGATHDIAG id=ATH_DIAG_DYN|13(HAL_DIAG_REGS) ad_in_size=8(one entry) ad_out_size=large. Reachability ungated due to DF-2059. AV:L/PR:L/AC/L, C:L/A:H. Fix: validate insize%sizeof(HAL_REGRANGE)==0 before HAL call; add M_ZERO to outdata alloc.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2061 Β· 7 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative, mechanism, citations, fix-build result 3.9 KB ↓ raw
README.md readme evidence-pack overview 1.5 KB ↓ raw
fix.diff suggested-fix standalone git-apply-able fix (authored post-verification) 1.4 KB view raw
build.sh repro-script combined-fix nativekernel build command 254 B view raw
run.sh repro-script runtime trigger (HW/module-gated) 383 B view raw
fix_build.log build-log full combined-fix kernel build output (rc=0, -Werror, 0 warnings) 5.6 MB ↓ download
env.txt environment guest uname / kern.version / cc version 289 B view raw
README.md readme evidence-pack overview
↓ download raw

DF-2061 β€” PoC evidence pack

Finding: Unprivileged heap OOB read / wild MMIO read via HAL_DIAG_REGS Class: Out-of-bounds read (CWE-125) | Impact ceiling: panic | Confidence: likely

Status

SOURCE-CONFIRMED. The defect is verified real by line-by-line tracing of the audited sys/ tree, but it is HW/module-gated: the audit guest lacks the required hardware/module (Atheros (ath) WLAN NIC present; reachability ungated only because of DF-2059), so it cannot be triggered at runtime here. See VERDICT.md for the full mechanism and citations.

How to reproduce (on a guest that HAS the hardware)

  1. Ensure the gating precondition is met: Atheros (ath) WLAN NIC present; reachability ungated only because of DF-2059.
  2. Apply fix.diff to confirm the cited path changes; before the fix the cited code path exhibits the behaviour described in VERDICT.md.
  3. The original finding markdown describes the runtime trigger.

Build validation (Phase 8)

fix.diff applies cleanly to pristine source. All nine findings' fixes were built together; see VERDICT.md "Phase 8" and fix_build.log:

make -j6 nativekernel KERNCONF=X86_64_GENERIC   # rc=0, -Werror, 0 warnings

Files

  • VERDICT.md β€” full narrative, mechanism, citations, fix-build result
  • fix.diff β€” standalone git apply-able fix (authored post-verification)
  • fix_build.log β€” full combined-fix kernel build output (rc=0, -Werror)
  • env.txt β€” guest environment for this verification
  • manifest.json β€” machine-readable catalog
VERDICT.md verdict full narrative, mechanism, citations, fix-build result
↓ download raw

DF-2061 β€” VERDICT

Verdict: REPRODUCED (source-confirmed; HW/module-gated β€” not runtime-triggerable on this guest) Class: Out-of-bounds read (CWE-125) Impact ceiling: panic Confidence: likely

Reproduction status

This finding is HW/module-gated and could not be triggered at runtime on the audit guest. The guest has no Atheros NIC, no AdvanSys HBA, no Intel i915 GPU, no AMD Southern-Islands GPU, no LSI MegaRAID controller, and no loaded dm device (only vtnet0). The PoC therefore cannot reach the vulnerable path at runtime here. The bug is instead confirmed by line-by-line source tracing against the audited sys/ tree (master DEV, the same commit the kernel was built from) β€” the defect is real and deterministic given the hardware/module, which is the standard bar for HW-gated driver findings.

Mechanism (source-confirmed)

ath_ioctl_diag() copyins indata with insize=ad->ad_in_size (u16, fully attacker-controlled) (:189) and passes it to ath_hal_getdiagstate() (:214). For id HAL_DIAG_REGS the HAL calls ath_hal_getregdump() (ah.c:891) whose loop for (i=0; space>=2*sizeof(uint32_t); i++) (ah.c:855) is bounded ONLY on the OUTPUT space, never on the input length: it indexes regs[i] (regs=indata) past the indata allocation, leaking neighbouring slab bytes into the output as start/end markers, then OS_REG_READ(ah,r) (ah.c:862) reads at the leaked/attacker-influenced register offset r -> wild bus read -> kernel panic (DoS), with an info-disclosure component via the copyout.

Gating precondition: Atheros (ath) WLAN NIC present; reachability ungated only because of DF-2059

Exploit chain

Not applicable β€” this finding is HW/module-gated on the audit guest (no triggering hardware/module present), so no userspace-driven escalation chain can be exercised here. Per AGENT.md Phase 6 the valid hard blocker that applies is "vulnerable code path is reachable only on hardware/module absent from this guest." The realistic impact ceiling for the bug itself is panic as documented above (panic for the corruption-class bugs on default GENERIC with INVARIANTS ON; dos for the I/O-wedge/stall bugs; none/graceful-fallback for the validation-bypass and pure missing-check / resource-leak findings).

Fix (authored, git-apply-able)

Root-cause fix in ah.c: add an argsize parameter to ath_hal_getregdump() and bound the loop with i < argsize/sizeof(HAL_REGRANGE) (rejecting non-multiple argsize). Defense-in-depth in if_ath_ioctl.c: allocate the result buffer with M_ZERO so no stale heap can be copied out.

The standalone diff is fix.diff in this directory. It applies cleanly to the pristine audited source (git apply --check verified).

Phase 8 β€” combined fix-build validation

All nine findings' fixes were applied together to a single in-guest source tree and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC. The DragonFly kernel compile invokes cc ... -Werror on every translation unit, so this is a true -Werror build.

  • Result: === NK_DONE rc=0 === (full log: fix_build.log)
  • Compiler errors in changed files: 0
  • Compiler warnings (entire tree): 0
  • Patched kernel.stripped sha256: 6086a989213ef7be890180f60edc0fd9827bf60efbc3ed8c969724746e250df6
  • Baseline (unpatched) kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026

Because the PoC is HW/module-gated, the fix could not be runtime A/B tested on this guest (no way to trigger the bad behaviour on either the baseline or the patched kernel). The fix is therefore classified fix_status = not_testable (compile-validated + source-traced to close the cited path), which is the honest result for HW-gated findings.

Kernel references (verified during this trace)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

COMPILE-VALIDATED, runtime not_testable. fix.diff applies cleanly and built into the combined nativekernel build: === NK_DONE rc=0 ===, -Werror, 0 warnings across if_ath_ioctl.c and ah.c. Source-traced closure: getregdump loop now bounded by the input argsize. Runtime A/B impossible (no ath NIC).

baseline ah.c:855 loop bounded on output space only; patched: i < nregs (=argsize/sizeof(HAL_REGRANGE)). Combined build === NK_DONE rc=0 ===, 0 warnings. Gating: no ath NIC => runtime not_testable.
↓ fix.diffcombined-fix kernel built rc=0 -Werror (kernel.stripped sha256 6086a989...); not booted β€” runtime not_testable (HW-gated: no Atheros NIC)

Confirmed kernel references

Detail

Exploit chain

Primitive = heap OOB read of the indata allocation (regs[i] indexed past insize) feeding a wild OS_REG_READ MMIO. Dominant effect on GENERIC: panic from the wild bus read (the demonstrated ceiling); secondary: info leak of neighbouring slab bytes copyouted to the user. NO userspace chain exercisable here: BLOCKED by the valid hard blocker that the path needs an Atheros NIC absent from this guest. Root-cause fix bounds the HAL loop by the input argsize so i can never exceed the supplied range array.

Evidence (decisive lines)

if_ath_ioctl.c:189 copyin(ad->ad_in_data,indata,insize); ah.c:855 for(i=0; space>=2*sizeof(uint32_t); i++) (output-bounded only); ah.c:862 OS_REG_READ(ah,r) where r=regs[i].start (leaked/OOB). Combined-fix build: === NK_DONE rc=0 === (-Werror).

PoC changes

findings/poc/DF-2061/ populated: VERDICT.md, fix.diff (root-cause: add argsize param to ath_hal_getregdump and bound loop i<argsize/sizeof(HAL_REGRANGE), rejecting non-multiple argsize; pass argsize from ath_hal_getdiagstate; defense-in-depth M_ZERO on the result buffer in if_ath_ioctl.c), README.md, build.sh, run.sh, env.txt, fix_build.log, manifest.json.

Verified recommended fix

Root-cause in ah.c: add a uint32_t argsize parameter to ath_hal_getregdump(), reject argsize%sizeof(HAL_REGRANGE)!=0, and bound the loop with i < argsize/sizeof(HAL_REGRANGE). Update the HAL_DIAG_REGS caller in ath_hal_getdiagstate() to pass argsize. Defense-in-depth in if_ath_ioctl.c: allocate the result buffer with M_NOWAIT|M_ZERO so no stale heap can be copied out. Standalone diff in findings/poc/DF-2061/fix.diff; supersedes finding proposal (finding suggested driver-level insize validation + M_ZERO; the implemented fix goes to the HAL root cause and also adds M_ZERO).

Verdict

SOURCE-CONFIRMED (HW-gated). ath_ioctl_diag() copyins indata with insize=ad->ad_in_size (u16, fully attacker-controlled) (if_ath_ioctl.c:189) and passes it to ath_hal_getdiagstate() (:214). For id HAL_DIAG_REGS the HAL calls ath_hal_getregdump() (ah.c:891) whose loop for (i=0; space>=2*sizeof(uint32_t); i++) (ah.c:855) is bounded ONLY on the OUTPUT space, never on the input length β€” it indexes regs[i] (regs=indata) past the indata allocation, leaking neighbouring slab bytes into the output as start/end markers and then OS_REG_READ(ah,r) (ah.c:862) reads at the leaked/attacker-influenced register offset r -> wild bus read -> kernel panic (DoS), with an info-disclosure component via the copyout. Trigger: SIOCGATHDIAG id=ATH_DIAG_DYN|13 with ad_in_size=8 (one entry) and large ad_out_size.