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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3835 Β· 6 files
FileTypeDescriptionSize
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.md
↓ download raw

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_est returns 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_testable
baseline no→ patch + rebuild →patched clean

Baseline #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
↓ fix.diffper-fix-DF-3835

Confirmed kernel references

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).