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

Unbounded PHY-reset poll in xe_mii_init(): card insertion / ifconfig up hangs the kernel in crit + serializer

Summary

xe_mii_init polls PHY_BMCR_RESET with a bare while loop - no iteration budget, no timeout, no bailout. It runs inside crit_enter() and the ifnet serializer at PCMCIA card insertion and every ifconfig up. A PHY that acknowledges MII but never clears BMCR_RESET (faulty silicon or a deliberately malicious attacker-inserted PCMCIA card) pins the CPU forever; no watchdog can fire. xe_phy_readreg returns 0 on MII ack failure so an absent PHY exits - the hang needs a present-but-stuck PHY, exactly what hostile hardware produces. FreeBSD 10.3's copy carries the identical loop. Bounded-poll fix with treat-as-absent fallback.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4160 Β· 5 files
FileTypeDescriptionSize
README.md β€” 791 B ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
fix.diff β€” 531 B view raw
manifest.json β€” 611 B view raw
verdict.json β€” 1.8 KB view raw

DF-4160 β€” Unbounded PHY-reset poll in xe_mii_init()

File: sys/dev/netif/xe/if_xe.c:1637-1639 Β· Low Β· CWE-835 Β· hardware-gated

while(xe_phy_readreg(scp, PHY_BMCR) & PHY_BMCR_RESET); has no timeout and runs in crit_enter() + ifnet serializer at PCMCIA card insertion (xe_attach β†’ xe_init, if_xe.c:397) and on every interface up. A PHY that acks MII but never clears RESET (faulty or malicious card β€” PCMCIA is attacker-insertable hardware) hangs the kernel permanently.

  • Full analysis: VERDICT.md
  • Fix: fix.diff (bounded poll, stuck PHY treated as absent β†’ existing 10Mbit fallback path)
  • No runtime verification possible: the QEMU audit guest has no PCMCIA/Xircom hardware. Status untested, per-audit convention for hardware-gated DoS findings.
VERDICT.md
↓ download raw

DF-4160 β€” Unbounded PHY-reset poll in xe_mii_init(): attach/init hard hang

File: sys/dev/netif/xe/if_xe.c:1637-1639 Β· Severity: Low Β· CWE-835

Root cause

/* Reset the PHY */
xe_phy_writereg(scp, PHY_BMCR, PHY_BMCR_RESET);
DELAY(500);
while(xe_phy_readreg(scp, PHY_BMCR) & PHY_BMCR_RESET);   /* if_xe.c:1639 */

The poll has no iteration budget, no timeout, and no bailout. It executes:

  • during xe_attach() β†’ xe_init() (if_xe.c:397) β€” i.e. at PCMCIA card insertion / first ifconfig xe0 up β€” inside crit_enter() and the ifnet serializer, and
  • on every later interface up-transition.

A PHY that acknowledges MII transactions but never clears PHY_BMCR_RESET (e.g. broken silicon, or a deliberately malicious PCMCIA card β€” the card is external, attacker-insertable hardware) pins the kernel in this loop forever: crit section + serializer held, CPU spinning on bit-banged MDIO. The system hangs at card insertion with no watchdog able to fire.

Note the escape hatches that do NOT save you: if the MII transaction fails to ack, xe_phy_readreg() returns 0 (xe_mii_readreg zeroed mii_data, ack failure returns garbage-free 0) and the loop exits β€” the hang requires a PHY that acks and keeps reporting RESET. That is exactly the "present but stuck" case a hostile or faulty card produces.

Fix

fix.diff bounds the poll (1000 Γ— 1 ms) and treats a stuck PHY as absent (xe_mii_init then returns 0 β†’ scp->phy_ok = 0), which the driver already handles gracefully (10Mbit-only fallback in xe_setmedia_serialized).

Verification

Hardware-gated (needs a Xircom-class PCMCIA adapter with a misbehaving PHY); no such hardware exists in the QEMU guest, so no runtime verification is possible. Source-level certainty: the loop body and exit condition are fully visible; no hidden bound exists. FreeBSD 10.3's copy of this driver carries the identical unbounded loop (shared 1999-vintage bug).

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 bounds the poll to 1000x1ms and returns phy_ok=0 on timeout, a state the driver already handles (10Mbit fallback); kernel-level validation impossible without the hardware

fix.diff
↓ fix.diffper-fix-DF-4160

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (source trace), fix.diff (bounded poll + graceful fallback)

PoC changes

n/a (new finding, pass 2); no harness - the unbounded loop is directly visible in source

Verified recommended fix

Bound the PHY reset poll in xe_mii_init() and treat a stuck-reset PHY as absent.

Verdict

xe_mii_init() polls PHY_BMCR_RESET with an unbounded while loop (if_xe.c:1639, no timeout, no bailout) inside crit_enter() + ifnet serializer, reached at card insertion (xe_attach -> xe_init, if_xe.c:397) and on every interface up. A PHY that acks MII but never clears RESET (faulty or malicious PCMCIA card) hangs the kernel permanently. Hardware-gated: no Xircom PCMCIA hardware in the QEMU guest, so no runtime verification; source-level certainty (exit condition fully visible, no hidden bound). FreeBSD 10.3 carries the identical bug.