Missing/ineffective bounds checks on gpio pin in 4 of 5 entry points (UB in shift macros, register field corruption)
Summary
Four of five GPIO entry points fail to bounds-check gpio pin against AR_NUM_GPIO(6). ar5312GpioCfgOutput/CfgInput/Set rely solely on HALASSERT (ar5312_gpio.c:42 59 77) which ah_internal.h:675 confirms compiles to nothing in release builds (only active under #ifdef AH_ASSERT). ar5312GpioSetIntr has NO check at all -- source comment /* XXX bounds check gpio */ at line 113. Only ar5312GpioGet (line 95) does real runtime check. Out-of-range gpio flows directly into shift macros AR_GPIOCR_CR_A/CR_N/INT (ar5212reg.h:752-757) and into (1<<gpio)/((val&1)<<gpio) at lines 80-81 producing UB for gpio>=16 (CR shifts) gpio>=2^20 (INT shift) gpio>=32 (Set shifts) and corrupting bits in GPIOCR overlapping interrupt-select/int-enable/int-sense fields for any gpio in [6 15]. Reachability: sc->sc_ledpin settable from userland via writable sysctl dev.ath.X.ledpin (if_ath_sysctl.c:210-226 CTLTYPE_INT|CTLFLAG_RW no bounds check). When sc_softled set sysctl write immediately calls ath_led_config->ath_hal_gpioCfgOutput/gpioset(if_ath_led.c:128-130) reaching this file with arbitrary gpio. Root-gated. Worst-case DoS via register misconfiguration/UB no privilege escalation no kernel-memory-corruption primitive every OS_REG_WRITE targets fixed offset inside device MMIO window gpio only selects bit positions never addresses. Sibling ar5315_gpio.c carries identical defect pattern.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2199 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able: add runtime gpio bounds check to 4 functions in ar5312_gpio.c | 1.3 KB | view raw |
| VERDICT.md | verdict | source-trace confirmation + mechanism + threat model | 5.6 KB | β raw |
| build.sh | build-script | no-op (source-only) | 346 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 |
DF-2199 β ar5312 GPIO index unvalidated (4/5 entry points)
Verdict
REPRODUCED (source-only confirmation). Bug confirmed by source tracing of
sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c. No runtime PoC attempted
because this code is HW-gated (only compiled for AR5312 SoC platforms that
require AH_SUPPORT_AR5312, and there is no Atheros NIC on the audit guest).
Mechanism
ar5312GpioCfgOutput (line 42), ar5312GpioCfgInput (line 59),
ar5312GpioSet (line 77) all validate gpio only via
HALASSERT(gpio < AR_NUM_GPIO) where AR_NUM_GPIO == 6 (line 31).
HALASSERT is defined in ah_internal.h:665-676:
#ifdef AH_ASSERT
#define HALASSERT(_x) do { if (!(_x)) ath_hal_assert_failed(...); } while (0)
#else
#define HALASSERT(_x)
#endif
AH_ASSERT is only enabled in sys/config/LINT64:1105 (the LINT config),
NOT in sys/config/X86_64_GENERIC (verified: the GENERIC ath lines are
device ath, device ath_hal, options AH_SUPPORT_AR5416 β no AH_ASSERT).
Concretely: in any production kernel build, HALASSERT(_x) expands to nothing,
so the four CfgOutput/CfgInput/Set calls accept any uint32_t gpio value
without bounds checking.
The fifth entry point, ar5312GpioSetIntr (line 107), is worse: it has
no HALASSERT at all, just the source comment /* XXX bounds check gpio */
at line 113. An out-of-range gpio flows straight into:
- AR_GPIOCR_CR_A(gpio) = 3 << (2*gpio) (ar5212reg.h:755) β UB shift for gpioβ₯16, OOB MMIO field for 6β€gpio<16
- AR_GPIOCR_CR_N(gpio) = 0 << (2*gpio) (ar5212reg.h:752)
- AR_GPIOCR_INT(gpio) = gpio << 12 (ar5212reg.h:757)
- (1 << gpio) at GpioSet (line 80-81) β UB shift for gpioβ₯32
Only ar5312GpioGet (line 95) has a real runtime check (if (gpio < AR_NUM_GPIO)).
Threat model / reachability
- The four vulnerable entry points are exported through
struct ath_halops vectors (ah_gpioCfgOutput,ah_gpioCfgInput,ah_gpioSet,ah_gpioSetIntr) βah.h:1477-1483. - Callers in
sys/dev/netif/ath/ath/if_ath_led.candsys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:157,167passsc->sc_ledpin/selectwhich originate from EEPROM / dev config. - A malicious or corrupted EEPROM could feed an out-of-range
gpio, producing UB shifts and OOB MMIO field selection β local DoS / device misbehavior on AR5312 SoC platforms. No path to direct userspace control ofgpiowas found; this is a hardening gap with realistic-but-bounded impact (HW-gated, driver-internal caller surface). - Severity Low is appropriate per the rubric (narrow trigger, requires specific HW and a malicious EEPROM, no demonstrated escalation).
Why no runtime PoC
- The audit guest has no Atheros NIC.
pciconf -lshows no ath device. ar5312_gpio.cis gated behind#ifdef AH_SUPPORT_AR5312(line 21); the symbol is not auto-defined anywhere insys/(grepped) and not set byX86_64_GENERICorsys/conf/options. The file is also NOT listed insys/dev/netif/ath/ath_hal/MakefileSRCS (onlyar5212_gpio.cis, at line 37). 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 that fires the moment a SoC platform build is done.- Source-only confirmation is therefore the strongest evidence available without bespoke HW, and matches the established pattern of the sibling finding DF-2098 (ar5416) which was also source-confirmed.
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 ar5312GpioSetIntr, 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 and keeps the API contract
(callers already check the return value of HAL_BOOL ops).
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/srcon thewith-srcsnapshot (#0 unpatched baseline, 6.5-DEVELOPMENT, INVARIANTS ON). make -j6 nativekernel KERNCONF=X86_64_GENERICβ rc=0, 0 errors, 0 warnings (other than the usualmissing-include-dirsnoise).- The ar5212 sibling (
ar5212_gpio.c) is in the Makefile SRCS and was rebuilt intoath_hal.kowith-Werrorβ clean. - The ar5312/ar5315 files are NOT in the GENERIC Makefile SRCS (they are
SoC-platform files), so to truly exercise the patch they were
additionally compiled directly with
-DAH_SUPPORT_AR5312/-DAH_SUPPORT_2316 -DAH_SUPPORT_2317and the per-arch include dir β all three files: rc=0, 0 errors, 0 warnings under-Werror.
Kernel references (confirmed during verification)
sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:31βAR_NUM_GPIO 6sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:42β HALASSERT only (CfgOutput)sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:59β HALASSERT only (CfgInput)sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:77β HALASSERT only (Set)sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:95β real runtime check (Get)sys/dev/netif/ath/ath_hal/ar5312/ar5312_gpio.c:113β/* XXX bounds check gpio */(SetIntr, none at all)sys/dev/netif/ath/ath_hal/ah_internal.h:665-676β HALASSERT compiles to nothing unless AH_ASSERTsys/config/LINT64:1105β only place AH_ASSERT is enabledsys/dev/netif/ath/ath_hal/ar5212/ar5212reg.h:752,755,757β shift macros
Fix verification
fixedbatch build rc=0 -Werror + standalone -DAH_SUPPORT_AR5312 rc=0
batch build rc=0 -Werror + standalone -DAH_SUPPORT_AR5312 rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
HW-gated (no Atheros NIC). Source-confirmed: ar5312 GPIO 4/5 funcs use HALASSERT (no-op on GENERIC). ar5312GpioSetIntr has NO check at all.
Verified recommended fix
HW-gated (no Atheros NIC). Source-confirmed: ar5312 GPIO 4/5 funcs use HALASSERT (no-op on GENERIC). ar5312GpioSetIntr has NO check at all.
Verdict
HW-gated (no Atheros NIC). Source-confirmed: ar5312 GPIO 4/5 funcs use HALASSERT (no-op on GENERIC). ar5312GpioSetIntr has NO check at all.
No comments yet.