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

ar5416GpioCfgInput drives the GPIO pin (DRV_ALL) instead of tristating it, contradicting its own contract β€” BT-coexistence sense pins get actively driven by the MAC

Summary

The function clears the 2-bit drive field then immediately re-sets DRV_ALL (0x3=drive) - an OUTPUT where the caller asked for input, unlike the ar5210/5211/5212 twins which tristate. The btcoex sense pins (BT ACTIVE/PRIORITY) get push-pull driven, contending with the BT module's driver - crowbar current, unusable coexistence. Byte-identical to FreeBSD CURRENT (upstream-inherited). Delete-the-DRV_ALL-line fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5092 Β· 7 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.5 KB ↓ raw
verify_static.sh β€” 1.1 KB view raw
verify_static.out β€” 1.8 KB view raw
fix.diff β€” 508 B view raw
manifest.json β€” 1.8 KB view raw
verdict.json β€” 4.3 KB view raw

DF-5092 β€” ar5416GpioCfgInput drives the pin instead of tristating it

File: sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:136-155 Class: hardware-correctness / insecure pin initialization (Info) Pass 2 (GLM 5.3 re-audit) of ar5416_gpio.c. Known findings DF-2098 (unvalidated gpio index in 4/5 exports) and DF-2099 (AR9287 wrong input bit) were not re-reported.

What the code does

ar5416GpioCfgInput() is documented (by its own comment, line 146) to tristate the pin when configuring it as an input:

/* TODO: configure input mux for AR5416 */
/* If configured as input, set output to tristate */
gpio_shift = gpio << 1;

reg = OS_REG_READ(ah, AR_GPIO_OE_OUT);
reg &= ~(AR_GPIO_OE_OUT_DRV << gpio_shift);
reg |= AR_GPIO_OE_OUT_DRV_ALL << gpio_shift;      /* <-- BUG */
OS_REG_WRITE(ah, AR_GPIO_OE_OUT, reg);

AR_GPIO_OE_OUT_DRV_ALL == 0x3 is "drive always" (ar5416reg.h:574), the exact opposite of the intended AR_GPIO_OE_OUT_DRV_NO == 0x0 ("tristate", ar5416reg.h:571). Clearing the field and not setting anything would tristate the pin β€” that is precisely what the AR5210/5211/5212 twins do:

The function is byte-identical in FreeBSD CURRENT (sys/dev/ath/ath_hal/ar5416/ar5416_gpio.c, verified 2026-09-27 against cgit.freebsd.org) β€” this is an upstream-inherited defect, not a DragonFly regression. Fix owner: upstream FreeBSD ath HAL, portable to DF as below.

Why it matters

Real consumers configure externally driven sense pins through this entry point:

  • ar5416_btcoex.c:355-356 β€” BT ACTIVE / BT PRIORITY inputs (ar5416GpioCfgInput(ah, ahp->ah_btActiveGpioSelect) etc., 3-wire btcoex)
  • ar5416_btcoex.c:388 β€” BT ACTIVE input (2-wire btcoex)
  • ar5416_btcoex.c:237 β€” WLAN ACTIVE pin switched back to "input" on disable

After this call the WLAN MAC actively drives a pin that the Bluetooth module (or another chip) also drives. Two push-pull drivers at opposite levels = electrical contention (crowbar current), mis-read inputs, and degraded BT coexistence β€” the "input" pin can never reliably sample the external signal.

Security impact: none at the kernel-memory level. Reachability is root-gated (driver/HAL-internal configuration, EEPROM-derived pin selects); there is no unprivileged path to these functions. Filed as Info for hardware-correctness and defense-in-depth (input must not drive).

Reproduction

Not runnable in the QEMU guest: the standard snapshot has no Atheros WLAN device (dfbsd-qemu/boot.log contains zero ath(4) probes; the code is compiled in via sys/config/X86_64_GENERIC:263-266 but only executes against real AR5416-family silicon). To reproduce on hardware with a btcoex-capable Atheros NIC (AR9285/AR9287 + BT combo):

  1. sysctl hw.ath.0.btcoex=... per if_ath docs to enable 2/3-wire btcoex.
  2. Probe dev.ath.0.%parent registers: read AR_GPIO_OE_OUT (0x404c) after attach β€” bits for the btcoex input pins read 0b11 (drive) instead of 0b00 (tristate).

./verify_static.sh (host-side) prints the decisive source lines and the twin implementations for contrast.

VERDICT.md
↓ download raw

DF-5092 VERDICT β€” ar5416GpioCfgInput drives instead of tristating

Status: untested (no reproduction path in the audit guest β€” hardware-only code path; Info-class finding, Phase V verification not mandated). Impact: none at the kernel-memory level; hardware-functional defect (input pins actively driven), root-gated, upstream-inherited.

Root cause, line-accurate

  • sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:146 β€” comment states the intent: "If configured as input, set output to tristate".
  • sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:150-151 β€” code clears the 2-bit drive field then immediately sets it to AR_GPIO_OE_OUT_DRV_ALL (0x3 = "drive always"), the opposite of tristate.
  • sys/dev/netif/ath/ath_hal/ar5416/ar5416reg.h:570-574 β€” AR_GPIO_OE_OUT_DRV_NO (0x0, "tristate") is the correct value; it is what the clear-only sequence would leave in place if line 151 did not exist.
  • Twin implementations that do it correctly: ar5212_gpio.c:57-66 (CR_N = no drive), ar5211_misc.c:213-225 (CR_N).
  • Consumers that pass externally driven pins: ar5416_btcoex.c:355-356 and ar5416_btcoex.c:388 (BT sense inputs), ar5416_btcoex.c:237 (WLAN active pin released on btcoex disable).
  • Upstream provenance: DragonFly's file has a single import commit (6cc80ee9); the file is byte-identical to FreeBSD CURRENT sys/dev/ath/ath_hal/ar5416/ar5416_gpio.c (fetched 2026-09-27), including this bug β†’ inherited, fix owner upstream.

Why not verified on the guest

  • ar5416 GPIO functions are only reachable after an Atheros PCI/PCIe device attaches and ath_hal_attach() succeeds; they program device MMIO (AR_GPIO_OE_OUT @ 0x404c). The QEMU/KVM audit guest emulates no Atheros NIC: dfbsd-qemu/boot.log has zero ath(4)/Atheros probe lines (single "ath" substring is an ldconfig path). ath_hal is compiled into GENERIC (sys/config/X86_64_GENERIC:263-266) but never executes.
  • Not a memory-corruption/privesc/hammer2 finding and not a trivial local PoC (requires physical combo-NIC hardware + oscilloscope or register probe) β†’ Phase V skip criteria apply; recorded as untested per the batch's status enumeration (no skipped value allowed).

Pass-2 negatives for this file (checked and killed, not re-reported)

  • DF-2098 (gpio index unvalidated in CfgOutput/CfgInput/Set/SetIntr, HALASSERT is #define HALASSERT(_x) no-op without AH_ASSERT β€” ah_internal.h:665-676) β€” known, NOT re-reported. Consumers verified: if_ath_sysctl.c:210-221 (hw.ath.N.ledpin, RW, unbounded, root-only) flows into ath_led_config() (if_ath_led.c:128) β†’ confirms the DF-2098 attack chain is root-gated only.
  • DF-2099 (missing else at ar5416_gpio.c:192-194 overwrites the AR9287 value with the pre-9280 AR_GPIO_IN_VAL extraction on KIWI) β€” known, NOT re-reported. (Also present in FreeBSD CURRENT, byte-identical.)
  • ar5416GpioSetIntr (ar5416_gpio.c:207-278): dead code in this tree β€” the sole reference is the ath_hal_gpioSetIntr macro (ah_internal.h:452-453); no caller exists in sys/dev/netif/ath/ath/*.c. Residual quirks there (pin-13 polarity silently dropped because AR_GPIO_INTR_POL_VAL is 0x1FFF = 13 bits while AR5416 advertises 14 pins (ar5416_attach.c:985); RMW of W1C AR_INTR_SYNC_CAUSE re-clearing all pending GPIO bits, ar5416_gpio.c:238-241) are therefore unreachable.
  • In-range shifts are safe: halNumGpioPins = 14/10/12/10 (ar5416_attach.c:985, ar9280_attach.c:882, ar9285_attach.c:529, ar9287_attach.c:435) β†’ gpio ≀ 13 β†’ 1 << gpio ≀ bit 13; the intr-enable fields (0xFFFC0000, S=18) hold 14 bits; gpio % 6) * 5 ≀ 25 in cfgOutputMux. UB shifts need an out-of-range index = DF-2098.
  • cfgOutputMux MUX1 readback WAR (lines 67-77) matches the documented Owl/Pre-Merlin2 PRDATA quirk; verified identical upstream; no new defect.
  • RMW races on AR_GPIO_IN_OUT / AR_GPIO_OE_OUT between the LED callout (if_ath_led.c:171,182) and config ioctls: lost-update LED glitch at worst; no memory-safety consequence.

Fix

One-line deletion (leaves the cleared field = DRV_NO = tristate), matching the ar5210/5211/5212 semantics and the function's own comment β€” see fix.diff. Should be upstreamed to FreeBSD (same bug there).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff (one-line deletion, verified to apply cleanly against the read-only sys/ tree) restores tristate semantics, but kernel-level fix validation requires the same AR5416-family hardware absent from the audit guest, hence not_testable. Fix validated by inspection only: after the deletion the 2-bit field remains 0x00 = AR_GPIO_OE_OUT_DRV_NO, exactly the ar5212/ar5211 behavior.

findings/poc/DF-5092/fix.diff (applies to ar5416_gpio.c:146-152); semantics cross-checked against ar5212_gpio.c:61-63 and ar5416reg.h:571-574
↓ fix.diffper-fix-DF-5092

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5092/verify_static.out β€” decisive lines: ar5416_gpio.c:146 comment ('set output to tristate') vs :151 'reg |= AR_GPIO_OE_OUT_DRV_ALL << gpio_shift;' vs ar5412_gpio.c:62-63 tristate twin and ar5416_btcoex.c:355-356 consumers; ar5416reg.h:571/574 value meanings

PoC changes

No seeded PoC existed; authored verify_static.sh (host-side static trace) because the code path is hardware-only (device MMIO at AR_GPIO_OE_OUT 0x404c, requires an attached Atheros NIC).

Verified recommended fix

Delete the 'reg |= AR_GPIO_OE_OUT_DRV_ALL << gpio_shift;' line in ar5416GpioCfgInput so the cleared field stays AR_GPIO_OE_OUT_DRV_NO (tristate), matching the ar5210/5211/5212 twins; upstream the same fix to FreeBSD.

Verdict

Static proof certain: ar5416GpioCfgInput (ar5416_gpio.c:150-151) clears the 2-bit drive field of AR_GPIO_OE_OUT and then immediately sets AR_GPIO_OE_OUT_DRV_ALL (0x3 = drive always), the opposite of the tristate its own line-146 comment promises and the opposite of the ar5210/5211/5212 twins (ar5212_gpio.c:57-66 and ar5211_misc.c:213-225 tristate via CR_N). Consequence: consumers that configure externally-driven sense pins (ar5416_btcoex.c:355-356, 388 BT active/priority inputs; ar5416_btcoex.c:237 WLAN-active released on disable) actively drive those pins β€” electrical contention with the BT module and broken btcoex sampling. No kernel-memory impact; reachable only after ath_hal attach on real AR5416-family silicon, driven by root-gated config (EEPROM pin selects, hw.ath.N sysctls). Runtime verification impossible on the audit guest: QEMU emulates no Atheros NIC (zero ath(4) probes in dfbsd-qemu/boot.log); ath_hal is compiled into GENERIC (X86_64_GENERIC:263-266) but the path never executes without hardware, so the finding is honestly recorded as untested rather than not_reproduced. Byte-identical to FreeBSD CURRENT (checked 2026-09-27): upstream-inherited; fix should go upstream.