tlphy_attach NULL-deref: strcmp(device_get_name(sibling), "tlphy") panics on not-yet-probed miibus sibling
Summary
The sibling capability walk passes device_get_name() of children that miibus pre-added but bus_generic_attach has not yet probed (devclass==NULL -> name NULL) straight into strcmp - boot-time NULL-deref panic when a 2nd+ TLAN10T PHY attaches with any higher-addressed PHY still unprobed. Normally masked (the stock dual-PHY card has the external PHY first); compiled into stock GENERIC. Physical-hardware-gated; NULL-read-only class. NULL-guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4923 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 6.0 KB | β raw | |
| check_hardware.sh | β | 812 B | view raw | |
| run.log | β | 195 B | view raw | |
| env.txt | β | 664 B | view raw | |
| fix.diff | β | 655 B | view raw | |
| manifest.json | β | 859 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-4923 β tlphy_attach NULL-deref: strcmp(device_get_name(sibling), "tlphy") panics on unprobed sibling PHY
- File:
sys/dev/netif/mii_layer/tlphy.c:177 - Severity: Low β CWE-476 NULL pointer dereference (boot/device-attach-time panic)
- Confidence: speculative (source-proven trigger chain; no hardware available to exercise it)
- PoC status: not_reproduced β the QEMU guest has no ThunderLAN NIC (verified, see env.txt)
Build
Nothing to build. check_hardware.sh (runs on the DragonFly guest) proves the
trigger precondition (a TI ThunderLAN controller, PCI vendor 0x100c) is absent,
which is why the code path cannot be exercised on this guest.
Run
./check_hardware.sh # on the guest, as root
Expected output (on this guest)
TLPHY-DF-4923: no ThunderLAN (vendor 0x100c) or Atheros (0x168c) PCI device present TLPHY-DF-4923: tl/miibus compiled into X86_64_GENERIC but no device to attach -> path unreachable TLPHY-DF-4923: finding NOT reproduced (hardware absent)
What the trigger WOULD be (real hardware)
A ThunderLAN NIC whose MII bus has: tlphy at address A (attaches 1st, instance 0),
tlphy at address B > A (attaches 2nd β takes the mii->mii_instance != 0 walk at
tlphy.c:170-184), and any third PHY at address C > B that is still in the
not-yet-probed state when tlphy@B attaches. device_get_name() returns NULL for
that sibling (sys/kern/subr_bus.c:1632-1637), and strcmp(NULL, "tlphy")
at tlphy.c:177 faults reading address 0 β kernel panic during device attach.
Full source trace in VERDICT.md.
DF-4923 VERDICT β not_reproduced (hardware absent); source trace confirms the NULL-deref chain
One-paragraph bottom line
tlphy_attach() walks all sibling children of the miibus when the tlphy being
attached is the 2nd+ PHY on the bus, and passes each child's device_get_name()
straight into strcmp() without a NULL check (tlphy.c:175-183). Children are
all added up front by miibus_probe() (mii.c:130-167) and only
probed/attached one-by-one afterwards by bus_generic_attach()
(subr_bus.c:bus_generic_attach), so any sibling later in the child TAILQ than
the currently-attaching tlphy still has devclass == NULL while the walk runs;
device_get_name() returns NULL for exactly that state
(sys/kern/subr_bus.c:1632-1637), and strcmp(NULL, "tlphy") dereferences
address 0 β kernel panic at device-attach time. The bug is real in the source
but is gated on an exotic ThunderLAN topology (β₯2 TLAN10T PHYs plus a later-
addressed PHY on one MII bus) that the audit guest (and virtually all modern
hardware) does not have, so it is filed Low/speculative and could not be
reproduced at runtime. Fix is a one-line NULL guard.
Full source trace (attacker-input β sink)
-
sys/dev/netif/mii_layer/mii.c:130-167βmiibus_probe()iteratesma.mii_phyno = 0..MII_NPHY-1and doesdevice_add_child(dev, NULL, -1)for every address whose MDIO read succeeds (miibus_no_phy()atmii.c:118-127returns false). All sibling children therefore exist in the miibusdev->childrenTAILQ before any of them is probed. Children are ordered by ascending MII address. -
miibus_attach()(mii.c:169-183) ends withbus_generic_attach(dev), which walks the child TAILQ in order and runsdevice_probe_and_attach(child)sequentially (sys/kern/subr_bus.c,bus_generic_attach). While child N attaches, children N+1.. havedevclass == NULL(a child's devclass is only set once a driver is selected for it βdevice_probe_child()sets/clears it transiently during probing atsubr_bus.c:1412-1445and persists it only for the winner). -
sys/dev/netif/mii_layer/tlphy.c:170-184β when a tlphy is the 2nd+ PHY attaching on the bus (mii->mii_instance != 0, incremented at tlphy.c:186), attach fetches the full child list:
c
device_get_children(sc->sc_mii.mii_dev, &devlist, &devs);
for (i = 0; i < devs; i++) {
if (strcmp(device_get_name(devlist[i]), "tlphy")) { /* line 177 */
other = device_get_softc(devlist[i]);
capmask &= ~other->mii_capabilities;
break;
}
}
The walk only breaks on a non-tlphy name. If every earlier sibling is
a tlphy and ANY later sibling exists (still unprobed β devclass == NULL),
the loop reaches it.
sys/kern/subr_bus.c:1632-1637:
c
const char *
device_get_name(device_t dev)
{
if (dev->devclass)
return devclass_get_name(dev->devclass);
return(NULL);
}
β strcmp(NULL, "tlphy") at tlphy.c:177 β page fault on address 0 β
kernel panic (panic: page fault in strcmp called from tlphy_attach).
Why the walk's break usually saves it (and when it doesn't)
- On the stock dual-PHY ThunderLAN card (internal TLAN10T + external non-TI 100baseTX PHY): the external PHY normally sits at the lower address, so the walk hits an already-attached non-tlphy sibling and breaks immediately β no crash. This is why the bug has never been seen in the field.
- Crash requires: tlphy@A (1st), tlphy@B>A (2nd, takes the walk), and any PHY
@C>B. I.e. at least two TLAN10T PHYs on one bus β hardware I cannot confirm
ever shipped, hence
speculativeconfidence. - Note DFly's
device_get_children()cannot fail (subr_bus.c:1595-1617,M_INTWAIT|M_ZERO), so the unchecked return attlphy.c:175is harmless here; the NULL-name case is the actual defect.
Mitigating facts
ukphybinds (ukphy_probereturns -100,ukphy.c) to every readable PHY, so no child stays unbound after the full attach pass β the NULL window is only the not-yet-probed state of later siblings.- Attach-time only: no runtime/unprivileged trigger; requires physically inserting the exotic NIC (or a PCI hot-plug event during boot attach).
- Softc is
M_ZERO-allocated (subr_bus.c:1952-1954), sosc_need_acompstarts 0 and theother->mii_capabilitiesread is in-bounds for any PHY softc (all begin withstruct mii_softc).
Reproduction attempts on the audit guest
dfbsd-qemu/vm.sh statusβ up (stockX86_64_GENERIC, built 2026-07-02).pciconf -lβ 7 PCI devices, zero with vendor 0x100c (TI ThunderLAN) or 0x168c (Atheros).tl/miibus/tlphyare compiled into the kernel (sys/config/X86_64_GENERIC:223-243) but never attach.- Conclusion: the code path is unreachable on this guest; runtime reproduction
requires physical ThunderLAN hardware with the multi-PHY topology above.
Status honestly recorded as
not_reproduced, impactnoneon this guest.
Fix (authored, not applied β sys/ is read-only)
tlphy.c:177 β skip children that have no devclass yet:
--- a/sys/dev/netif/mii_layer/tlphy.c
+++ b/sys/dev/netif/mii_layer/tlphy.c
@@ -174,7 +174,9 @@ tlphy_attach(device_t dev)
device_get_children(sc->sc_mii.mii_dev, &devlist, &devs);
for (i = 0; i < devs; i++) {
- if (strcmp(device_get_name(devlist[i]), "tlphy")) {
+ const char *dname = device_get_name(devlist[i]);
+ if (dname != NULL && strcmp(dname, "tlphy") != 0) {
other = device_get_softc(devlist[i]);
capmask &= ~other->mii_capabilities;
break;
(fix.diff carries the same change; fix validation not possible without the
trigger hardware β fix_status: not_testable.)
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_testablefix.diff adds the NULL guard at tlphy.c:177, but baseline itself cannot be triggered without the exotic ThunderLAN multi-PHY hardware, so patched-kernel validation is impossible on this guest; fix correctness is established by the source trace alone
findings/poc/DF-4923/fix.diff
Confirmed kernel references
- sys/dev/netif/mii_layer/tlphy.c:177
- sys/kern/subr_bus.c:1632
- sys/dev/netif/mii_layer/mii.c:160
- sys/config/X86_64_GENERIC:243
Detail
Evidence (decisive lines)
findings/poc/DF-4923/VERDICT.md carries the full path:line trace (mii.c:130-167 child pre-addition vs sequential bus_generic_attach; tlphy.c:170-186 walk; subr_bus.c:1632 NULL return); run.log shows 7 PCI devices and zero vendor 0x100c/0x168c matches on the stock X86_64_GENERIC guest
PoC changes
no seed PoC existed; replaced with a hardware-precondition check (check_hardware.sh) plus a source-trace proof since the trigger is physical-NIC topology
Verified recommended fix
Skip siblings whose device_get_name() is NULL in tlphy_attach's capability walk
Verdict
tlphy_attach() passes each miibus sibling's device_get_name() straight into strcmp() (tlphy.c:177); siblings not yet probed have devclass==NULL so device_get_name() returns NULL (subr_bus.c:1632-1637) and strcmp(NULL, ...) panics at device-attach time. Trigger needs >=2 TLAN10T PHYs plus a later-addressed PHY on one ThunderLAN MII bus; the audit guest has no TI 0x100c PCI device at all (pciconf evidence in run.log/env.txt), so the path is unreachable there and the finding stays speculative/Low. Fix: skip NULL-named siblings (fix.diff).
No comments yet.