bwn_phy_lp_calc_rx_iq_comp divides by hardware-controlled ie_ipwr under a sum-only guard (ie_ipwr==0 with ie_qpwr>=2 passes) β kernel #DE panic from the init/60s calibration path
Summary
The guard 'if (ie.ie_ipwr + ie.ie_qpwr < 2) goto done' does not exclude ie_ipwr==0 with ie_qpwr>=2; the coefficient macros then divide by terms derived from _z = ie_ipwr == 0 -> x86 divide-error trap -> kernel panic. ie_ipwr/ie_qpwr are raw PHY IQ power accumulators read while the RX front-end is force-overridden; a dead/muted I-path yields exactly zero. Called from bwn_phy_lp_calib at init and every 60s via the task - recurring panic. FreeBSD head has the identical defect. LP PHY radio fault reporting zero I-power -> persistent local DoS. One-clause guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3835 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| verdict.json | β | 3.1 KB | view raw | |
| fix.diff | β | 462 B | view raw | |
| env.txt | β | 1.1 KB | view raw | |
| manifest.json | β | 715 B | view raw |
DF-3835 β bwn_phy_lp_calc_rx_iq_comp divides by hardware-controlled ie_ipwr that is only sum-guarded β kernel #DE panic from the 60-second calibration path
- File:
sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c(pass 2) - Class: CWE-369 divide-by-zero β kernel panic (DoS), hardware-state-gated
- Severity: Medium (panic; radio-dependent reachability), confidence: likely
- Upstream-inherited (FreeFreeBSD head has the identical guard and macros).
Build / Run / Expected
Hardware-gated (env.txt: no Broadcom wireless on the audit guest). On hardware with an LP PHY, any condition that makes the I-channel power accumulator read back 0 while the Q accumulator is β₯ 2 (dead/muted I-path, RX chain fault, aggressive front-end override during calibration) hits the divide in the very next calibration cycle:
# automatic: bwn_phy_lp_calib runs at init and every 60 s (bwn_phy_lp_task_60s)
Expected: kernel trap 0 (divide error) β panic.
Root cause
bwn_phy_lp_calc_rx_iq_comp β if_bwn_phy_lp.c:2329-2346:
ret = bwn_phy_lp_rx_iq_est(mac, sample, 32, &ie);
if (ret == 0)
goto done;
if (ie.ie_ipwr + ie.ie_qpwr < 2) { /* only the SUM is guarded */
ret = 0;
goto done;
}
CALC_COEFF (tmp[0], bwn_nbits(ie.ie_iqprod), ie.ie_iqprod, ie.ie_ipwr);
CALC_COEFF2(tmp[1], bwn_nbits(ie.ie_qpwr), ie.ie_qpwr, ie.ie_ipwr);
Both macros divide by expressions of _z = ie.ie_ipwr (lines 2308-2324):
... / (_z >> _t) or ... / (_z << -_t). With ie_ipwr == 0 (and ie_qpwr >= 2, which
passes the sum guard) the divisor is 0 β x86 #DE β kernel panic.
ie.ie_ipwr/ie.ie_qpwr are raw 32-bit accumulations read from
BWN_PHY_IQ_I_PWR_ACC_HI/LO registers after the estimator run (if_bwn_phy_lp.c:3529-3537) β
device-controlled values. The call sites are bwn_phy_lp_calib (if_bwn_phy_lp.c:752, comment
at 751 even flags the ignored return value) which runs from bwn_phy_lp_init (482) and from
the periodic bwn_phy_lp_task_60s (610-614), so a radio in this state panics the host
repeatedly (init + every 60 s).
VERDICT β DF-3835
status: inconclusive (arithmetic certain, hardware reachability inferred) β reproduced: 0 β guest impact: none.
Arithmetic proof
Guard (if_bwn_phy_lp.c:2340): if (ie.ie_ipwr + ie.ie_qpwr < 2) { ret = 0; goto done; }
Take ie_ipwr = 0, ie_qpwr = 2 (both are plain uint32_t register accumulations,
if_bwn_util.h / if_bwn_phy_lp.c:3529-3537): guard passes (sum = 2).
CALC_COEFF(tmp[0], _x=bwn_nbits(ie_iqprod), _y=ie_iqprod, _z=ie_ipwr=0) β lines 2308-2316:
_t = _x - 20;
- _t >= 0: _v = ((_y << (30 - _x)) + (_z >> (1 + _t))) / (_z >> _t);
β (_z >> _t) = 0 β idivl by zero β x86 #DE β DragonFly kernel panic.
- _t < 0: ... / (_z << -_t) β 0 << k = 0 β same.
CALC_COEFF2 (2317-2324) divides by (_z >> _t) / (_z << -_t) identically.
No other check on ie_ipwr exists anywhere between the register read (3532-3534) and the
macros.
Reachability reasoning (why 'likely', not 'speculative')
bwn_phy_lp_rx_iq_estreturns 1 (estimator ran, 3518-3527) with all-zero or asymmetric accumulators whenever the PHY's IQ estimator block is misconfigured or the I-path is physically dead β the estimator counter register just accumulates 0.- The call is made while the RX front-end is forced into an artificial override state (trsw_over TX=1 RX=0 at 735, forced RF_OVERRIDE_0 0x800 at 748-749) β precisely the regime where a marginal radio reads pathological values.
- It runs at every
bwn_phy_lp_init(line 482) and every 60 seconds (bwn_phy_lp_task_60sβbwn_phy_lp_calibβ line 752; the driver even marks the ignored return value/* XXX no checking return value? */at 751) β so one occurrence bricks the system repeatedly. - Not software-reachable: no user input reaches
ie_ipwr; requires the radio to actually report 0 I-power. Fault-injection on the PCIe device (a compromised b43 firmware/dongle attack) would make it deterministic.
Guest
No Broadcom wireless hardware (env.txt) β bwn never attaches; path untestable there.
Fix
fix.diff extends the guard with || ie.ie_ipwr == 0 (git apply --check passes). Upstream
FreeBSD has the identical defect and needs the same guard.
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_testableBaseline #DE and patched behavior require an LP PHY reporting zero I-power; no Broadcom hardware on the audit guest (env.txt). fix.diff is git-apply-able (verified).
fix.diff
Confirmed kernel references
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2340
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2345
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2346
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2308
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2324
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:3529
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:610
- sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:752
Detail
Exploit chain
no privilege gain: LP-PHY radio fault/fault-injection (e.g. failing I-path LNA) -> ie_ipwr==0, ie_qpwr>=2 -> kernel divide-by-zero panic at init and every 60 s -> persistent local DoS until the radio state changes.
Evidence (decisive lines)
README.md (guard line 2340 vs divisors in macros 2308-2324; callers 482/610-614/752); env.txt; fix.diff (extend guard with ie.ie_ipwr == 0, git-apply --check passes)
PoC changes
No PoC β the divisor comes from PHY hardware accumulators, not from any software-reachable input; would need LP PHY hardware fault injection.
Verified recommended fix
Extend the guard: 'if (ie.ie_ipwr + ie.ie_qpwr < 2 || ie.ie_ipwr == 0)' before invoking the CALC_COEFF macros (fix.diff).
Verdict
bwn_phy_lp_calc_rx_iq_comp (sys/dev/netif/bwn/bwn/if_bwn_phy_lp.c:2306) guards its hardware-derived IQ estimates with only 'ie.ie_ipwr + ie.ie_qpwr < 2' (line 2340) and then divides by expressions of ie.ie_ipwr inside CALC_COEFF/CALC_COEFF2 (macros at 2308-2324, invocations at 2345-2346): '(_y << (30-_x)) / (_z >> _t)' with _z = ie_ipwr. ie_ipwr == 0 together with ie_qpwr >= 2 satisfies the guard and produces a division by zero β x86 #DE, kernel panic. ie_ipwr/ie_qpwr are raw 32-bit accumulators read from the PHY IQ power registers (3529-3537), i.e. radio-state-controlled; a dead or muted I-path during the calibration override (trsw_over 1/0, RF_OVERRIDE forced at 748-749) plausibly yields exactly 0. Reachability: bwn_phy_lp_calib at init (482) and every 60 s via bwn_phy_lp_task_60s (610-614) β a radio in that state panics the host repeatedly. Arithmetic is certain; the hardware state is inferred, hence 'likely' not 'certain'. Not exercisable on the audit guest (no Broadcom PCI device, env.txt). Same defect exists in FreeBSD head (identical guard and macros).
No comments yet.