# 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)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:31` — `AR_NUM_GPIO 7`
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:42` — HALASSERT only (CfgOutput)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:59` — HALASSERT only (CfgInput)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:77` — HALASSERT only (Set)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:95` — real runtime check (Get)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:113` — `/* XXX bounds check gpio */` (SetIntr, none at all)
- `sys/dev/netif/ath/ath_hal/ar5312/ar5315_gpio.c:116` — `val |= gpio << AR5315_GPIOINT_S;` — direct field selector
- `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/ar5312/ar5312reg.h:53-60` — GPIODIR / GPIOINT macros
