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

rlphy_service() entry KASSERT panics stock GENERIC kernels on multi-PHY MII buses (DF-1973 family)

Summary

The KASSERT assumes ifm_cur always belongs to instance 0, but the miibus dispatch hands EVERY PHY the shared ifm_cur (mii.c:330-333/346-348); a sibling PHY (combo/fiber/T4) selected by the operator fires the assert every 1Hz mii_tick and on any unprivileged SIOCGIFMEDIA - panic on stock INVARIANTS GENERIC. REPRODUCED on guest with a synthetic dual-PHY KLD (panic backtrace rlphy_service<-mii_tick); foreign-instance guard fix rebuilt as kernel and validated (same trigger survives). Same guard shape as ukphy/xmphy.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5009 Β· 12 files
FileTypeDescriptionSize
fakemic.c β€” 8.5 KB view raw
Makefile β€” 152 B ↓ download
build.sh β€” 98 B view raw
run.sh β€” 133 B view raw
build.log β€” 827 B view raw
run.log β€” 1.8 KB view raw
run.fixed.log β€” 1.0 KB view raw
panic.txt β€” 2.4 KB view raw
env.txt β€” 241 B view raw
fix.diff β€” 836 B view raw
VERDICT.md β€” 4.1 KB ↓ raw
verdict.json β€” 3.4 KB view raw
VERDICT.md
↓ download raw

DF-5009 β€” rlphy_service() entry KASSERT panics stock GENERIC kernels on multi-PHY MII buses

What

rlphy_service() opens with

KASSERT(IFM_INST(ife->ifm_media) == sc->mii_inst,
    ("rlphy_service: can't isolate RealTek PHY"));   /* rlphy.c:200-201 */

before the switch (cmd), i.e. for MII_MEDIACHG, MII_TICK and MII_POLLSTAT alike. The miibus dispatch routines call every PHY's service routine with the one shared mii->mii_media.ifm_cur (mii.c:309-318, 330-333, 346-348). rlphy enforces instance 0 for itself at attach (rlphy.c:145-148) but nothing stops a different PHY driver from attaching afterwards as instance 1 (miibus_probe creates a child for every address with media bits in BMSR, mii.c:138-162). When the currently selected media belongs to that sibling instance, the KASSERT fires β€” on any INVARIANTS kernel, which the default X86_64_GENERIC config is (sys/config/X86_64_GENERIC:56).

Sibling selection is reachable in two ways: * the operator selects a media word unique to the sibling (combo boards: fiber PHY with 1000SX, T4-capable PHY, …). Note ifmedia_match() treats the instance field as don't-care (ifm_mask seeded with IFM_IMASK at miibus_attach, mii.c:180; if_media.c:393-413), so an instance-1 word is only selectable when no first-PHY entry shares its subtype β€” exactly the unique-subtype case. * the default ifmedia_set() in miibus_mediainit() (mii.c:238-244) stays with instance 0, so plain autoselect does NOT trigger; the sibling must be selected explicitly.

After the selection the panic recurs automatically once per second (MII_TICK from the MAC watchdog) and on every unprivileged SIOCGIFMEDIA (ifconfig read β†’ mii_pollstat β†’ MII_POLLSTAT).

Reproduced

Stock X86_64_GENERIC guest (DragonFly 6.5-DEVELOPMENT, built 2026-07-02). A synthetic out-of-tree KLD ("fakemic") manufactures the dual-PHY hardware precondition: canned MII register image with a RealTek RTL8201L at address 0 (β†’ rlphy, instance 0) and an unknown-vendor PHY at address 1 (β†’ ukphy, instance 1) that additionally advertises 100baseT4 (a subtype rlphy never adds). sysctl -w kern.fakemic_arm=1 emulates ifconfig fakemic0 media 100baseT4 (the sibling's unique word) and then invokes the watchdog mii_tick():

panic: rlphy_service: can't isolate RealTek PHY
rlphy_service() at rlphy_service+0x29e
mii_tick() at mii_tick+0x3a
fakemic_arm_sysctl() at ...
sysctl_root ...

Full serial-console capture in panic.txt; procedure in run.log / run.sh. The KLD substitutes ~30 lines of register-canning for the (rare, unemulatable) physical dual-PHY board; everything from mii_phy_probe() down is the real in-kernel mii/rlphy/ukphy stack.

Fix validated

fix.diff replaces the KASSERT with the standard foreign-instance guard (the same shape as ukphy.c:200-202 and xmphy.c:171-184/225-227): if (IFM_INST(ife->ifm_media) != sc->mii_inst) return (0); Built as a kernel (make nativekernel), installed, rebooted, identical trigger re-run: no panic, mii_tick survived, guest healthy (run.fixed.log).

Severity

Low β€” kernel DoS (panic), requires an rlphy-compatible PHY co-resident with a second PHY on one MII bus (RTL8201L / ICPLUS IP101 combo boards, 8129 cards with dual PHYs) plus an operator selecting the sibling's unique media. Same family and mechanism class as DF-1973 (mlphy). CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H (4.5).

Contents

  • fakemic.c, Makefile β€” synthetic dual-PHY MII MAC KLD (the PoC)
  • build.sh / run.sh β€” exact commands
  • build.log β€” module build output
  • run.log β€” baseline (stock kernel) transcript β†’ panic
  • run.fixed.log β€” patched kernel transcript β†’ survives
  • panic.txt β€” serial console capture of the panic + backtrace
  • env.txt β€” guest/kernel/compiler info
  • fix.diff β€” the verified one-hunk fix

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src copy, nativekernel+installkernel+reboot; identical trigger re-run: no panic, 'fakemic: mii_tick survived -- rlphy KASSERT did NOT fire', guest healthy (run.fixed.log)

run.fixed.log; kernel uname '#1: Fri Sep 25 20:48:43 UTC 2026' vs baseline '#0: Thu Jul 2'
↓ fix.diffper-fix-DF-5009

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (serial console: panic + rlphy_service/mii_tick backtrace); run.log (baseline procedure); run.fixed.log (patched kernel survives); build.log; VERDICT.md (path:line narrative)

PoC changes

authored from scratch (no seed): out-of-tree KLD manufacturing the dual-PHY MII bus; three iterations were needed (nexus ivars via BUS_ADD_CHILD; attach moved from sysctl context to device_identify because device_sysctl_init self-deadlocks the sysctl lock; sibling given a media subtype unique vs rlphy because ifmedia_match ignores the instance field)

Verified recommended fix

Replace the entry KASSERT in rlphy_service() with the standard foreign-instance early-return guard used by ukphy/xmphy: if (IFM_INST(ife->ifm_media) != sc->mii_inst) return (0);

Verdict

rlphy_service() KASSERTs instance equality at entry for all commands (rlphy.c:200-201), but the miibus dispatch calls every PHY's service with the shared ifm_cur (mii.c:309-333, 346-348); on a dual-PHY MII bus (rlphy@inst0 + sibling@inst1) with the sibling's unique media word selected, the stock INVARIANTS X86_64_GENERIC kernel panics 'rlphy_service: can't isolate RealTek PHY' on every 1 Hz mii_tick and every unprivileged SIOCGIFMEDIA. Reproduced end-to-end on the QEMU guest with an out-of-tree synthetic dual-PHY KLD (fakemic.c): panic backtrace rlphy_service <- mii_tick captured in panic.txt. fix.diff (standard foreign-instance guard, same shape as ukphy/xmphy) built as a kernel and validated: identical trigger survives, guest stays up.