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)
PoC verification
Evidence pack
findings/poc/DF-5009 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 commandsbuild.logβ module build outputrun.logβ baseline (stock kernel) transcript β panicrun.fixed.logβ patched kernel transcript β survivespanic.txtβ serial console capture of the panic + backtraceenv.txtβ guest/kernel/compiler infofix.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
fixedfix.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'
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.
No comments yet.