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

Missing runtime bounds check on gpio in 4/5 entry points (sibling of DF-2199)

Summary

ar5315GpioCfgOutput/CfgInput/Set/SetIntr all use caller-supplied gpio as bit-shift operand and MMIO field selector without any runtime upper-bound check. Only HALASSERT(gpio<AR_NUM_GPIO=7) guards three of them and HALASSERT compiled out of every standard build AH_ASSERT defined nowhere in sys/. ar5315GpioSetIntr has no check at all source comment /* XXX bounds check gpio */ at line 113. Only ar5315GpioGet (line 95) correctly guarded. Out-of-range gpio flows into AR5315_GPIODIR_M/O(gpio)=(1<<(x)) for gpio 7..31 writes stray bits into GPIODIR MMIO register for gpio>=32 1<<gpio is UB shift count>=width of int. ar5315GpioSet lines 80-81 reg &= ~(1<<gpio); reg |= (val&1)<<gpio; signed left shift of 1 with gpio>=31 signed-overflow UB gpio>=32 shift-count UB. ar5315GpioSetIntr line 116 val |= gpio << AR5315_GPIOINT_S ORs raw gpio into interrupt register gpio>=8 sets bits 8+ gpio overlapping GPIOINTLVL_S corrupts interrupt-level field. Reachable: root sysctl hw.athN.ledpin CTLFLAG_RW ath_sysctl_ledpin stores arbitrary int no bounds check when softled set calls ath_led_config->ath_hal_gpioCfgOutput/gpioset. Impact: corrupted bits in GPIO MMIO registers reconfiguring unrelated board GPIOs corrupted GPIO interrupt level/selection arming spurious/never-asserted interrupts interrupt-storm hang/stuck-state local DoS shift UB architecture/optimization-dependent. No arbitrary kernel-memory write found all sinks fixed-offset MMIO addresses inside GPIO register block practical ceiling local DoS/hardware misbehavior not privesc. On x86_64 HAL not loaded exposure to embedded AR5315-class boards where defect fully reachable.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2200 Β· 6 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able: add runtime gpio bounds check to 4 functions in ar5315_gpio.c 1.3 KB view raw
VERDICT.md verdict source-trace confirmation + mechanism + Medium-rated MMIO field-corruption rationale 5.7 KB ↓ raw
build.sh build-script no-op (source-only) 362 B view raw
run.sh run-script no-op (source-only) 112 B view raw
env.txt environment uname, cc version, AH_ASSERT / AH_SUPPORT state 814 B view raw
_batch_build.log build-log Phase 8 batch kernel build with all 3 sibling fixes applied (rc=0) 5.6 MB ↓ download
VERDICT.md verdict source-trace confirmation + mechanism + Medium-rated MMIO field-corruption rationale
↓ download raw

DF-2200 β€” ar5315 GPIO index unvalidated (4/5 entry points; field selector)

Verdict

REPRODUCED (source-only confirmation). Bug confirmed by source tracing of sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c. No runtime PoC attempted because this code is HW-gated (AH_SUPPORT_2316 || AH_SUPPORT_2317, no Atheros NIC on guest).

Mechanism

ar5315GpioCfgOutput (line 42), ar5315GpioCfgInput (line 59), ar5315GpioSet (line 77) validate gpio only with HALASSERT(gpio < AR_NUM_GPIO) where AR_NUM_GPIO == 7 (line 31). As confirmed in ah_internal.h:665-676, HALASSERT expands to nothing unless AH_ASSERT is defined β€” and AH_ASSERT is only enabled in sys/config/LINT64:1105, not in X86_64_GENERIC. In any production build the four entry points accept any uint32_t gpio without bounds checking.

The fifth entry point, ar5315GpioSetIntr (line 107), has no HALASSERT at all β€” just the source comment /* XXX bounds check gpio */ at line 113. Worse than its ar5312/ar5212 siblings, in ar5315 the unvalidated gpio is used as a direct bit-shifted MMIO field selector, not just a per-pin mask: - val |= gpio << AR5315_GPIOINT_S; (line 116, with AR5315_GPIOINT_S 0 from ar5312reg.h:57) β€” gpio becomes bits [6:0] of the GPIOINT MMIO register via AR5315_GPIOINT_M 0x3F. An out-of-range gpio corrupts the AR5315_GPIOINTLVL field above it (AR5315_GPIOINTLVL_S 6, ar5312reg.h:59) and can flip the level-sense bits, re-arming interrupts on the wrong edge. This is more than a UB shift β€” it is direct MMIO field corruption, which is why DF-2200 is rated Medium while its siblings are Low. - AR5315_GPIODIR_M(x) = 1 << (x) (ar5312reg.h:53), AR5315_GPIODIR_O(x) = 1 << (x) (ar5312reg.h:54) β€” used as bit-mask in CfgOutput/Input. For gpio >= 32, UB shift. For 7 ≀ gpio < 32, mask/selects bits outside the 7-pin GPIODIR field. - (1 << gpio) at GpioSet (line 80-81) β€” UB shift for gpioβ‰₯32.

Only ar5315GpioGet (line 95) has a real runtime check (if (gpio < AR_NUM_GPIO)).

Threat model / reachability

  • Exported through struct ath_hal ops vectors (ah.h:1477-1483).
  • Callers in sys/dev/netif/ath/ath/if_ath_led.c, sys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:157,167 pass sc->sc_ledpin / select from EEPROM / dev config.
  • The ar5315 backend specifically targets the AR2316/AR2317 SoC platforms (#if (AH_SUPPORT_2316 || AH_SUPPORT_2317) at line 21). A malicious EEPROM feed produces MMIO field corruption on those platforms β€” local DoS, spurious interrupts, possible device lockup. The MMIO field corruption (vs. plain UB shift) is what elevates this above the Low siblings. No path to direct userspace control of gpio was found; the caller surface is driver-internal.
  • Severity Medium is appropriate per the rubric: realistic impact is local DoS / device misbehavior requiring specific HW (AR2316/2317 SoC) and a malicious EEPROM; field corruption is more dangerous than the siblings' pure UB shift.

Why no runtime PoC

  • The audit guest has no Atheros NIC (pciconf -l shows no ath device).
  • ar5315_gpio.c is gated behind #if (AH_SUPPORT_2316 || AH_SUPPORT_2317) (line 21); neither symbol is auto-defined anywhere in sys/ (grepped) nor set by X86_64_GENERIC or sys/conf/options. The file is also NOT in sys/dev/netif/ath/ath_hal/Makefile SRCS. So the function bodies never link into the running kernel on this guest; the bug is a latent source-level defect in maintained SoC code.
  • Source-only confirmation is the strongest evidence available without bespoke HW; matches the established pattern of the sibling DF-2098.

Fix

For each HAL_BOOL function, add a real runtime guard immediately after the existing HALASSERT: if (gpio >= AR_NUM_GPIO) return AH_FALSE;. For the void function ar5315GpioSetIntr, replace the /* XXX bounds check gpio */ comment with if (gpio >= AR_NUM_GPIO) return;. This is exactly the pattern applied to the sibling ar5416 fix in DF-2098.

See fix.diff (12 hunks, all git apply-able; applied cleanly with patch -p1 --forward, every hunk "succeeded").

Batch-build status (Phase 8)

  • All three sibling fixes (DF-2199 / DF-2200 / DF-2201) applied together to /usr/src on the with-src snapshot (#0 unpatched baseline, 6.5-DEVELOPMENT, INVARIANTS ON).
  • make -j6 nativekernel KERNCONF=X86_64_GENERIC β†’ rc=0, 0 errors, 0 warnings.
  • The ar5212 sibling is in the Makefile SRCS and was rebuilt into ath_hal.ko with -Werror β€” clean.
  • The ar5312/ar5315 files are NOT in the GENERIC Makefile SRCS (SoC-platform files), so to truly exercise the patch they were additionally compiled directly with -DAH_SUPPORT_2316 -DAH_SUPPORT_2317 (ar5315) and the per-arch include dir β€” rc=0, 0 errors, 0 warnings under -Werror.

Kernel references (confirmed during verification)

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

batch build rc=0 -Werror + standalone -DAH_SUPPORT_2316 rc=0

batch build rc=0 -Werror + standalone -DAH_SUPPORT_2316 rc=0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

HW-gated. Source-confirmed: ar5315 GPIO 4/5 funcs HALASSERT no-op. SetIntr uses gpio as direct MMIO field selector -> field corruption (Medium-rated).

Verified recommended fix

HW-gated. Source-confirmed: ar5315 GPIO 4/5 funcs HALASSERT no-op. SetIntr uses gpio as direct MMIO field selector -> field corruption (Medium-rated).

Verdict

HW-gated. Source-confirmed: ar5315 GPIO 4/5 funcs HALASSERT no-op. SetIntr uses gpio as direct MMIO field selector -> field corruption (Medium-rated).