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)
PoC verification
Evidence pack
findings/poc/DF-5092 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
ar5212GpioCfgInput()βsys/dev/netif/ath/ath_hal/ar5212/ar5212_gpio.c:57-66ORs inAR_GPIOCR_CR_N(gpio)(no-drive/tristate).ar5211GpioCfgInput()βsys/dev/netif/ath/ath_hal/ar5211/ar5211_misc.c:213-225ORs inAR_GPIOCR_0_CR_N << (gpio * CR_SHIFT).
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):
sysctl hw.ath.0.btcoex=...per if_ath docs to enable 2/3-wire btcoex.- Probe
dev.ath.0.%parentregisters: 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.
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 toAR_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-356andar5416_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.loghas zero ath(4)/Atheros probe lines (single "ath" substring is an ldconfig path).ath_halis 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
untestedper the batch's status enumeration (noskippedvalue 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 intoath_led_config()(if_ath_led.c:128) β confirms the DF-2098 attack chain is root-gated only. - DF-2099 (missing
elseat ar5416_gpio.c:192-194 overwrites the AR9287 value with the pre-9280AR_GPIO_IN_VALextraction 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 theath_hal_gpioSetIntrmacro (ah_internal.h:452-453); no caller exists insys/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. cfgOutputMuxMUX1 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_testablefix.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
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:146
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:150
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_gpio.c:151
- sys/dev/netif/ath/ath_hal/ar5416/ar5416reg.h:571
- sys/dev/netif/ath/ath_hal/ar5416/ar5416reg.h:574
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_gpio.c:61
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_btcoex.c:355
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_btcoex.c:388
- sys/config/X86_64_GENERIC:263
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.
No comments yet.