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

miibus_mediainit calls ifmedia_set() on an EMPTY media list β†’ panic("ifmedia_set") at NIC attach β€” the hub owning the DF-4930/DF-4957/DF-4981 empty-media family panic (family fix belongs here)

Summary

The walk finds no media when every PHY took its no-media branch and the parent added nothing - ifmedia_set panics on the no-match. REPRODUCED live on guest (verbatim walk replay -> panic, guest down; the fixed walk survives with ifm_cur non-NULL). PASS-2 INSIGHT: the family leaf-fix pattern is a NO-OP for the MIIF_NOISOLATE drivers (ciphy/truephy - the mii_phy_add_media IFM_NONE fallback is gated) - this hub guard is the only fix covering them. Guard+autoselect-default fix (compiles -Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5001 Β· 15 files
FileTypeDescriptionSize
miihub.c β€” 5.0 KB view raw
Makefile β€” 73 B ↓ download
build.sh β€” 111 B view raw
run.sh β€” 187 B view raw
run_fix.sh β€” 426 B view raw
build.log β€” 2.6 KB view raw
run.log β€” 683 B view raw
run.2.log β€” 1.4 KB view raw
panic.txt β€” 2.3 KB view raw
env.txt β€” 563 B view raw
fixcheck-build.log β€” 899 B view raw
fix.diff β€” 894 B view raw
verdict.json β€” 5.0 KB view raw
VERDICT.md β€” 4.8 KB ↓ raw
README.md β€” 2.3 KB ↓ raw

DF-5001 β€” miibus_mediainit panics on an empty media list (the hub of the

DF-4930 / DF-4957 / DF-4981 "no media present" family)

What

miibus_mediainit() (sys/dev/netif/mii_layer/mii.c:227-245) pokes the parent NIC for its own media, then walks mii->mii_media.ifm_list looking for IFM_ETHER|IFM_AUTO, and unconditionally calls ifmedia_set(&mii->mii_media, media) (mii.c:244). If the list is EMPTY (every probed PHY took its "no media present" branch and the parent added nothing), the walk body never runs, media stays 0 (mii.c:232), and ifmedia_set(ifm, 0) finds no match and executes panic("ifmedia_set") (sys/net/if_media.c:174). DragonFly panics where a graceful attach failure is warranted.

Reproduce (on the QEMU guest)

# build (guest):      sh build.sh        # or: see build.log
# baseline run:       sh run.sh          # expect panic + guest down
# after vm.sh reset:  sh run_fix.sh      # fixed walk must survive

The KLD miihub.ko replays the hub walk verbatim (mii.c:238-244) against a real struct ifmedia with an empty list:

  • sysctl hw.miihub_fire=1 β€” baseline: ifmedia_set: no match for 0x0/... then panic: ifmedia_set (see panic.txt).
  • sysctl hw.miihub_fixcheck=1 β€” the fixed walk (fix.diff): survives, ifm_cur non-NULL (see run.2.log).

Expected output

Baseline (serial console, panic.txt):

miihub: ifmedia_init (EMPTY ifm_list -- ...)
miihub: running miibus_mediainit walk verbatim (mii.c:238-244)
ifmedia_set: no match for 0x0/0xfffffff
panic: ifmedia_set
Trace: ifmedia_set() <- miihub_fire() <- sysctl_root <- userland_sysctl <- sys___sysctl

Fixcheck (run.2.log):

miihub-fix: survived; ifm_cur=0xfffff8008d481d60 media=0x20
miihub-fix: ifm_cur->ifm_media=0x20 (service deref safe)

Trigger precondition (why this is Low, not High)

The empty list requires real PHY hardware whose BMSR/EXTSR report zero media capability while still answering the probe (the DF-4930 atphy, DF-4957 ciphy, DF-4981 jmphy, DF-5003 truephy attach branches β€” plus the ~20 ukphy_attach-shaped siblings), behind a parent NIC whose mediainit method adds no media of its own. No unprivileged userland path can drive the bus into this state; it is an attach/boot-time DoS on faulty or malicious hardware-in-the-loop. The sink itself (the hub walk + terminal ifmedia_set) is proven on this kernel by the replay above.

VERDICT.md
↓ download raw

DF-5001 VERDICT β€” REPRODUCED (panic, replay of the hub statements)

Bottom line: miibus_mediainit() (mii.c:227-245) unconditionally ends in ifmedia_set(&mii->mii_media, media) (mii.c:244). On an empty media list media is still 0 (mii.c:232) and DragonFly's ifmedia_set() executes panic("ifmedia_set") (sys/net/if_media.c:166-174, panic at :174) β€” taking the whole system down at NIC attach time. The walk was executed verbatim on the running guest kernel (KLD replay via sysctl) and panicked exactly as predicted, killing the guest; the fixed walk (fix.diff) survives the identical scenario and leaves ifm_cur non-NULL.

How it was reproduced

miihub.c contains a verbatim copy of the hub walk (mii.c:238-244) operating on a real struct ifmedia initialized with ifmedia_init() β€” the exact state of mii->mii_media after miibus_attach() (mii.c:180-181) when every probed PHY has taken its "no media present" branch (family: atphy.c DF-4930, ciphy.c DF-4957, jmphy.c DF-4981, truephy.c DF-5003, plus the ukphy_attach-shaped siblings β€” ukphy.c:166-170, amphy, brgphy, ...) and the parent NIC's MIIBUS_MEDIAINIT method added nothing (mii.c:235).

  1. sh build.sh β€” builds miihub.ko on the guest with the kernel's full -Werror warning set (build.log). Needed SRCS+= device_if.h bus_if.h (the generated-interface headers are absent from the guest's /sys after vm.sh reset with-src; DF-4930's run had them pre-generated).
  2. sh run.sh β†’ sysctl hw.miihub_fire=1 β€” panic: ifmedia_set: no match for 0x0/0xfffffff β†’ panic: ifmedia_set, trace ifmedia_set() <- miihub_fire() <- sysctl_root <- userland_sysctl <- sys___sysctl, guest down (panic.txt, full serial-console capture; vm.sh status = down immediately after).
  3. vm.sh reset with-src; sh run_fix.sh β†’ sysctl hw.miihub_fixcheck=1 β€” the FIXED walk (fix.diff shape: empty list β†’ ifmedia_add(IFM_ETHER|IFM_AUTO) β†’ ifmedia_set) survives: miihub-fix: survived; ifm_cur=0xfffff8008d481d60 media=0x20 and ifm_cur->ifm_media=0x20 β€” 0x20 = IFM_ETHER|IFM_AUTO β€” proving the ife = mii->mii_media.ifm_cur; IFM_INST(ife->ifm_media) dereference every *_service() performs on the next tick (e.g. truephy.c:185) stays safe (run.2.log).
  4. The patched mii.c itself (fix.diff applied) compiles clean under -Werror as a KLD source on the guest (fixcheck-build.log) β€” LIST_EMPTY/ifmedia_add are already in mii.c's include set.

Why the end-to-end path is hardware-preconditioned (severity Low)

mii_probe() (mii.c:258-282) only creates the miibus when at least one PHY address passes miibus_no_phy() (mii.c:105-116) β€” which requires a BMSR with some media/EXTSTAT bits β€” and ukphy always attaches (ukphy.c:126-131). The empty-list state therefore requires a PHY that PASSES the probe (BMSR_MEDIAMASK|BMSR_EXTSTAT != 0 at mii.c:113-115) but then reports zero media capabilities when its driver re-reads BMSR/EXTSR at attach (e.g. truephy.c:163-172). That is faulty or malicious-hardware-in-the-loop behavior, not an unprivileged userland input. Consequence: attach/boot-time kernel panic (full DoS of the node), same class and severity as the family findings (DF-4930/4957/4981, all Low). No escalation angle exists: the primitive is a deterministic panic() on a failed list match β€” no memory unsafety, no controllable write.

Why the hub fix is the right family fix

  • One guard in miibus_mediainit covers every current and future PHY driver that can add zero media (atphy, ciphy, jmphy, truephy, ukphy shape, and the ~20 siblings).
  • It is the ONLY fix that covers the MIIF_NOISOLATE drivers: the mii_phy_add_media() IFM_NONE fallback is gated on MIIF_NOISOLATE being CLEAR (mii_physubr.c:230-232), but ciphy (ciphy.c:132) and truephy (truephy.c:157) always set MIIF_NOISOLATE β€” for them, "just call mii_phy_add_media unconditionally" adds nothing and the panic remains. (DF-4957's pack already works around this with an explicit ifmedia_add; DF-4930's atphy leaf fix works only because atphy does not set MIIF_NOISOLATE.)
  • Non-empty-but-no-AUTO lists keep the existing behavior (set the last entry's media), which always self-matches.

Fix

fix.diff β€” in miibus_mediainit(), after the walk: if no entry was found and the list is empty, ifmedia_add(IFM_ETHER|IFM_AUTO) and set media accordingly, then ifmedia_set() as before. The bus then attaches (degraded, autoselect-only) instead of panicking the system.

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 reproduced→ patch + rebuild →patched clean

fix.diff (miibus_mediainit: empty list -> ifmedia_add(IFM_ETHER|IFM_AUTO) before ifmedia_set) could not be exercised through the real in-kernel miibus_mediainit on this guest (no miibus-attached NIC exists in QEMU, so the real code path cannot be driven end-to-end). Its operative mechanism WAS validated on the running kernel: the identical empty-list scenario replayed through the FIXED walk survives with ifm_cur non-NULL and ifm_cur->ifm_media=0x20 (run.2.log), and the patched mii.c itself compiles clean under -Werror as a KLD source (fixcheck-build.log).

run.2.log ('miihub-fix: survived; ifm_cur=0xfffff8008d481d60 media=0x20'), fixcheck-build.log (miifix.ko linked from patched mii.c), fix.diff (applies cleanly: git apply --check OK)
↓ fix.diffper-fix-DF-5001

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (serial console: 'ifmedia_set: no match for 0x0/0xfffffff' -> 'panic: ifmedia_set' -> trace ifmedia_set<-miihub_fire<-sysctl_root<-userland_sysctl<-sys___sysctl; vm.sh status=down right after), run.log (baseline, ssh died at panic), run.2.log (fixcheck: 'miihub-fix: survived; ifm_cur=0xfffff8008d481d60 media=0x20' + 'ifm_cur->ifm_media=0x20 (service deref safe)', guest up), build.log (clean -Werror KLD build), fixcheck-build.log (patched mii.c compiles as KLD source), fix.diff (git-apply-able, verified), VERDICT.md (full narrative)

PoC changes

PoC authored fresh (no seed). Two build iterations: the generated-interface headers (device_if.h/bus_if.h) are absent from the guest /sys after 'vm.sh reset with-src', so the Makefile needed SRCS+= device_if.h bus_if.h (kmod.mk's makeobjops.awk rules generate them). Baseline run killed the guest before ssh could flush; decisive output captured from the serial console (vm.sh log).

Verified recommended fix

miibus_mediainit: if the media list is empty after the parent poke, ifmedia_add(IFM_ETHER|IFM_AUTO) before ifmedia_set() (fix.diff) -- fixes the whole family in one place, including the MIIF_NOISOLATE drivers.

Verdict

REPRODUCED: miibus_mediainit (mii.c:227-245) ends in an unconditional ifmedia_set(&mii->mii_media, media) (mii.c:244); on an EMPTY media list the walk (mii.c:238-243) never runs, media stays 0 (mii.c:232), and DragonFly's ifmedia_set() executes panic("ifmedia_set") (sys/net/if_media.c:174). A KLD replaying the hub walk verbatim against a real struct ifmedia (state = every probed PHY took its 'no media present' branch: atphy DF-4930 / ciphy DF-4957 / jmphy DF-4981 / truephy DF-5003, parent NIC added nothing) panicked the running guest exactly as predicted ('ifmedia_set: no match for 0x0/0xfffffff' -> panic, trace ifmedia_set<-miihub_fire<-sysctl) and the guest went down (panic.txt); the fixed walk (fix.diff: empty list -> ifmedia_add(IFM_ETHER|IFM_AUTO) -> ifmedia_set) survived the identical scenario with ifm_cur non-NULL (run.2.log), and the patched mii.c compiles clean under -Werror (fixcheck-build.log). End-to-end trigger is hardware-preconditioned (a PHY that passes the BMSR probe at mii.c:113-115 but reports zero media capabilities at driver attach), i.e. attach/boot-time DoS on faulty or malicious hardware-in-the-loop -- same class as the family leaves (all Low); no memory unsafety, no escalation angle. This hub guard is the family-wide fix and the ONLY one covering the MIIF_NOISOLATE drivers ciphy and truephy, for whom the mii_phy_add_media IFM_NONE fallback is gated off (mii_physubr.c:230-232).