# 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`:
```c
#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_hal` ops
  vectors (`ah_gpioCfgOutput`, `ah_gpioCfgInput`, `ah_gpioSet`,
  `ah_gpioSetIntr`) — `ah.h:1477-1483`.
- Callers in `sys/dev/netif/ath/ath/if_ath_led.c` and
  `sys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:157,167` pass
  `sc->sc_ledpin` / `select` which 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
  of `gpio` was 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 -l` shows no ath device.
- `ar5312_gpio.c` is gated behind `#ifdef AH_SUPPORT_AR5312` (line 21); the
  symbol is not auto-defined anywhere in `sys/` (grepped) and not set by
  `X86_64_GENERIC` or `sys/conf/options`. The file is also NOT listed in
  `sys/dev/netif/ath/ath_hal/Makefile` SRCS (only `ar5212_gpio.c` is, 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/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** (other than the usual `missing-include-dirs` noise).
- The ar5212 sibling (`ar5212_gpio.c`) 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 (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_2317` and 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 6`
- `sys/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_ASSERT
- `sys/config/LINT64:1105` — only place AH_ASSERT is enabled
- `sys/dev/netif/ath/ath_hal/ar5212/ar5212reg.h:752,755,757` — shift macros
