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

jmphy_attach 'no media present' branch skips mii_phy_add_media, so miibus_mediainit calls ifmedia_set on an EMPTY media list β†’ DragonFly panic("ifmedia_set") at attach (DF-4930/DF-4957 family)

Summary

The third member of the empty-media family (after atphy DF-4930 and ciphy DF-4957): the no-media branch skips the only population call while still triggering mediainit, and jme implements no mediainit method so nothing else fills the list - DFly's ifmedia_set panics on the empty list. Kernel sink PROVEN live on guest (jmsink KLD: 'no match for 0x0' -> panic, guest down; the IFM_NONE-fallback path survives). Hw-fault precondition (stuck-reset PHY or SMI timeout returning 0). Unconditional-add-media fix (family alternative: harden miibus_mediainit for all ~20 siblings).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4981 Β· 14 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 4.8 KB ↓ raw
jmsink.c β€” 4.2 KB view raw
Makefile β€” 52 B ↓ download
build.sh β€” 194 B view raw
run.sh β€” 290 B view raw
build.log β€” 879 B view raw
run.log β€” 53 B view raw
run.2.log β€” 323 B view raw
panic.txt β€” 1.4 KB view raw
env.txt β€” 658 B view raw
fix.diff β€” 542 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 3.6 KB view raw

DF-4981 β€” jmphy "no media present" branch β†’ empty media list β†’ panic("ifmedia_set") at attach

File: sys/dev/netif/mii_layer/jmphy.c:124-129 Family: DF-4930 (atphy), DF-4957 (ciphy) β€” same construct, same sink. Parent NIC: jme(4) (sys/dev/netif/jme/if_jme.c) β€” mii_phy_probe at :1028, NO miibus_mediainit method (:218-220), so nothing but the PHY populates the media list.

Chain

jmphy_attach reads BMSR (jmphy.c:119); if the PHY reports zero media bits AND (BMSR_EXTSTAT clear, or EXTSR media bits zero), the branch at jmphy.c:124-127 prints "no media present" and skips mii_phy_add_media(), then jmphy.c:131 calls MIIBUS_MEDIAINIT β†’ miibus_mediainit (mii.c:228-246) walks an EMPTY ifm_list, media stays 0, and mii.c:244 calls ifmedia_set(&mii->mii_media, 0) β†’ sys/net/if_media.c:168-175: ifmedia_match() on an empty list returns NULL β†’ panic("ifmedia_set").

Realistic trigger: a JMicron JMP211/JMP202 PHY whose ID registers still match (miidevs: oui JMICRON 0x001b8c, models 0x21/0x22) while BMSR reads 0 β€” e.g. jme's SMI read timing out returns 0 (if_jme.c:303-308), or a stuck-reset / partially-powered-down PHY. Not emulatable on QEMU.

Build / run (on the guest)

mkdir -p /root/df4981 && cd /root/df4981   # jmsink.c + Makefile there
sh build.sh                                # -> jmsink.ko
sh run.sh                                  # kldload; sysctl hw.jmsink_fire=1
                                          # EXPECTED: panic("ifmedia_set"),
                                          # ssh dies, guest down

Fix-mechanism check (after vm.sh reset with-src)

kldload /root/df4981/jmsink.ko && sysctl hw.jmsink_fixcheck=1
# EXPECTED: survives, ifm_cur non-NULL (the IFM_NONE fallback entry that
# unconditional mii_phy_add_media() installs β€” mii_physubr.c:235-237)

Success criterion

Baseline: panic: ifmedia_set on the serial console, guest down. Fixcheck: survives with non-NULL ifm_cur.

VERDICT.md
↓ download raw

DF-4981 VERDICT β€” jmphy "no media present" β†’ empty media list β†’ panic("ifmedia_set") at attach

File: sys/dev/netif/mii_layer/jmphy.c:124-129 (+ :131) Family: DF-4930 (atphy), DF-4957 (ciphy) β€” identical construct, identical sink. This pack independently re-proves the sink on the current kernel.

Reproduced?

NOT reproduced end-to-end; the kernel-side sink is PROVEN on this exact kernel (verbatim method, same as the family precedent):

The full trigger requires a JMicron JMP211/JMP202 PHY, behind a jme(4) NIC, whose ID registers still match miidevs (oui JMICRON 0x001b8c, models 0x21/0x22 β€” miidevs:227-228) while PHY_READ(sc, MII_BMSR) (jmphy.c:119) returns zero media bits and MII_EXTSR (jmphy.c:120-121) contributes none β€” e.g. a stuck-reset/powered-down PHY, or jme's SMI read timing out, which returns 0 (if_jme.c:303-308). No such PHY exists or is emulatable on the QEMU guest (env.txt), so jmphy_attach itself cannot be driven into the branch here.

What WAS executed on the guest, using the real kernel ifmedia_* functions, is the exact kernel-side tail of the chain β€” the state miibus_mediainit is left in when jmphy's no-media branch skips mii_phy_add_media():

  1. jmphy_attach (jmphy.c:119-121): capabilities/extcapabilities both have zero media bits β†’ jmphy.c:124-127 prints "no media present" and SKIPS mii_phy_add_media(sc) β†’ the mii->mii_media.ifm_list stays EMPTY.
  2. jmphy.c:131 MIIBUS_MEDIAINIT(sc->mii_dev) β†’ miibus_mediainit (mii.c:228-246): the parent poke MIIBUS_MEDIAINIT(parent) (mii.c:235) is a no-op for jme β€” jme implements only miibus_{readreg,writereg,statchg} (if_jme.c:218-220) and attaches the bus via mii_phy_probe (if_jme.c:1028) β€” so nothing else populates the list.
  3. The list walk (mii.c:236-240) finds nothing; media stays 0; mii.c:244 calls ifmedia_set(&mii->mii_media, 0).
  4. ifmedia_set (sys/net/if_media.c:168-175): ifmedia_match() on an empty list returns NULL β†’ panic("ifmedia_set").

Observed (run.log + panic.txt, from the serial console):

jmsink: loaded; sysctl hw.jmsink_fire=1 to panic
jmsink: ifmedia_init (empty list)
jmsink: list walk done, media=0x0; calling ifmedia_set (mii.c:244)
ifmedia_set: no match for 0x0/0xffffffff
panic: ifmedia_set
cpuid = 1
Trace ... ifmedia_set() at ifmedia_set+0x53
        jmsink_fire() at jmsink_fire+0x8e
        sysctl_root ... userland_sysctl ... sys___sysctl
Debugger("panic")

The guest died mid-sysctl (ssh never returned; vm.sh status β‡’ down) β€” a kernel-triggered fatal event at the exact sink the finding cites, on the unpatched kernel. Note this is DragonFly-specific: FreeBSD's ifmedia_set prints a warning and returns on no-match; DragonFly's panics (sys/net/if_media.c:172-175).

Impact ceiling

Boot/attach-time kernel panic (DoS) on jme(4)+JMP211/JMP202 hardware whose PHY misreports zero media capability. No unprivileged-user trigger; requires a hardware fault state (BMSR=0 with matching ID regs). Memory-safety impact: none β€” the panic is an intentional panic() guard, reached via a logic gap. Severity Low, consistent with family precedent DF-1973/DF-4923/DF-4930/DF-4957.

Fix validation

fix.diff (authored after verification, never applied to the read-only sys/ tree) makes jmphy_attach call mii_phy_add_media() unconditionally. With zero capabilities that installs exactly the IFM_NONE fallback entry (mii_physubr.c:235-237 β€” if ((sc->mii_flags & MIIF_NOISOLATE) == 0) ADD( IFM_ETHER|IFM_NONE ...); jmphy never sets MIIF_NOISOLATE and jme passes no such flag through mii_phy_probe), so miibus_mediainit's ifmedia_set() finds a match and survives, and ifm_cur is non-NULL for later jmphy_service() (jmphy.c:138 dereferences mii->mii_media.ifm_cur).

Fixcheck executed on the guest after vm.sh reset with-src (run.2.log):

jmsink-fix: ifmedia_init + IFM_NONE fallback entry (the fixed jmphy path)
jmsink-fix: calling ifmedia_set(IFM_ETHER|IFM_NONE) -- must NOT panic
jmsink-fix: survived; ifm_cur=0xfffff8008d481c80 (non-NULL keeps jmphy_service safe)

End-to-end kernel build with the patch was not performed: the bug is a one-line call-site change whose operative mechanism was validated with the real kernel functions on this kernel; a full jme+jmphy attach cannot exist on this guest. fix_status: not_testable (as persisted for the family), with the mechanism proven as above.

Guest was reset (vm.sh reset with-src) after both runs; left clean/up.

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.diff (jmphy.c: call mii_phy_add_media unconditionally; with zero capabilities it installs the IFM_NONE fallback entry per mii_physubr.c:235-237) could not be exercised end-to-end (no jme/JMicron hardware). Its operative mechanism WAS validated on the guest with the real kernel functions: with the fallback entry present, the identical ifmedia_set sequence survives and leaves ifm_cur non-NULL (run.2.log: 'jmsink-fix: survived; ifm_cur=0xfffff8008d481c80').

VERDICT.md 'Fix validation' section; fix.diff; run.2.log (fixcheck output)
↓ fix.diffper-fix-DF-4981

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (sink panic: 'ifmedia_set: no match for 0x0/0xffffffff' -> panic, full trace), run.log (sysctl hw.jmsink_fire=1; ssh never returned; vm.sh status => down), run.2.log (fixcheck after reset: 'jmsink-fix: survived; ifm_cur=0xfffff8008d481c80'), VERDICT.md (jmphy.c:119-131 -> mii.c:228-246 -> sys/net/if_media.c:168-175, parent jme adds no media: if_jme.c:218-220, mii_phy_probe at :1028, SMI timeout returns 0: if_jme.c:303-308)

PoC changes

PoC authored fresh (no seed): jmsink KLD replaying the sink with jmphy-accurate citations; needed device_if.h/bus_if.h copied from the kernel objdir into the module dir for the kmod build.

Verified recommended fix

jmphy_attach: call mii_phy_add_media() unconditionally so the IFM_NONE fallback entry always exists (see fix.diff); or make miibus_mediainit tolerate an empty media list.

Verdict

NOT REPRODUCED end-to-end: the trigger requires JMicron JMP211/JMP202 PHY hardware (behind a jme(4) NIC) whose BMSR/EXTSR report zero media capability while its ID registers still match, and no such PHY exists on the QEMU guest (PHY register behavior is not emulatable). The kernel-side sink of the chain is nonetheless PROVEN on this exact kernel: replaying miibus_mediainit's terminal ifmedia_set() on an EMPTY media list (what jmphy_attach's no-media branch at jmphy.c:124-129 leads to via mii.c:228-246) yields panic("ifmedia_set") with trace ifmedia_set<-jmsink_fire<-sysctl_root<-userland_sysctl<-sys___sysctl and kills the guest (panic.txt). DragonFly's ifmedia_set panics on no-match where FreeBSD returns. The finding is therefore certain as a code path and likely as a field event (hardware-fault precondition), severity Low (boot/attach-time DoS on jme+JMicron hardware, no user-controlled trigger).