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

miibus leaks every kmalloc'd newbus ivars (mii_attach_args per PHY child, mii_probe_args per bus) β€” nothing in the kernel ever frees them

Summary

Newbus never frees ivars and no kfree exists anywhere in sys/ - every failed mii_probe (bge retries 5x per attach) and every NIC detach/hot-removal leaks the bus args + one per PHY (~104-152B/cycle). Bounded exhaustion, no UAF composition. Free-in-detach+ENXIO fix (compiles -Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5002 Β· 5 files
FileTypeDescriptionSize
fix.diff β€” 1.4 KB view raw
fixcheck-build.log β€” 825 B view raw
verdict.json β€” 3.5 KB view raw
VERDICT.md β€” 2.7 KB ↓ raw
README.md β€” 2.5 KB ↓ raw

DF-5002 β€” miibus leaks every kmalloc'd device ivars (mii_attach_args /

mii_probe_args); nothing in the kernel ever frees them

What

miibus_probe() kmallocs one struct mii_attach_args per probed PHY address and stores it as the child's newbus ivars (mii.c:157-161); mii_probe() kmallocs one struct mii_probe_args and stores it as the miibus device's ivars (mii.c:263-267). Newbus does not own or free device ivars: device_delete_child() (sys/kern/subr_bus.c:1284-1310) frees the device structure only and simply drops the ivars pointer (set to NULL only at creation, subr_bus.c:1216). No code anywhere in sys/ kfrees these allocations (verified: the only kfrees in the whole mii_layer are unrelated devlist/M_TEMP frees in tlphy.c:183 and mlphy.c:218,426).

Leak sites:

  1. Every failed mii_probe() β€” the ENXIO path (mii.c:274-278) deletes the miibus child and drops args (~56 bytes). bge retries this path up to 5 times per attach (if_bge.c:2582-2589 "Probe MII again" loop) β†’ up to 5 leaks per attach of a bge card with an unresponsive PHY.
  2. Every miibus destroy (NIC detach: module unload, CardBus/USB hot-removal β€” e.g. if_rue USB NICs use miibus via ruephy) β€” leaks the miibus's mii_probe_args plus one mii_attach_args per PHY child (~104-152 bytes per detach cycle). USB dongle replug is an unprivileged-with-physical-access repeatable trigger.

Pure resource leak (CWE-401): the memory stays allocated forever (no UAF angle β€” nothing retains the pointer). Impact is slow kernel heap exhaustion at ~10^7+ cycles; no confidentiality/integrity impact.

Status

untested dynamically: triggering requires a miibus-attached NIC (none exists on the QEMU guest β€” its NICs don't use miibus); the defect is nevertheless provable by inspection (allocation sites above; absence of any free verified by grep over sys/; newbus ivars semantics verified in subr_bus.c). The proposed fix.diff compiles clean under the kernel's full -Werror set as a KLD source on the guest (fixcheck-build.log).

Fix

fix.diff: free the per-PHY mii_attach_args in miibus_detach() (children are still enumerated there; every NIC driver deletes the miibus child on detach β€” verified across if_bge/if_nfe/if_xl/if_vr/...), free the bus's own mii_probe_args at the end of miibus_detach(), and free them on the mii_probe() ENXIO path. device_set_ivars(..., NULL) after each kfree to keep any re-probe from reading a dangling pointer.

VERDICT.md
↓ download raw

DF-5002 VERDICT β€” UNTESTED dynamically; defect certain by inspection

Bottom line: the two ivars allocations in mii.c (mii_attach_args per PHY child at mii.c:157-161, mii_probe_args for the bus at mii.c:263-267) have no corresponding free anywhere in the kernel β€” newbus never frees ivars (device_delete_child, sys/kern/subr_bus.c:1284-1310, drops the pointer; only ever set to NULL at device creation, subr_bus.c:1216). Every failed mii_probe() leaks one mii_probe_args immediately (mii.c:274-278; bge retries the probe up to 5Γ— per attach, if_bge.c:2582-2589), and every miibus destroy (NIC detach β€” module unload or CardBus/USB hot-removal; if_rue USB NICs use miibus) leaks the bus args plus one child args per PHY (~104-152 bytes per cycle).

Why untested (and why that is honest)

  • The QEMU guest has no miibus-attached NIC (its NICs don't use miibus), so neither leak path can be driven dynamically on this guest. Creating a synthetic miibus would require a custom fake NIC driver whose miibus_readreg/writereg emulate a PHY β€” that exercises my test scaffold, not the shipping code, so the static proof plus the compile-checked fix is the stronger evidence.
  • Static proof: (a) allocation sites mii.c:157-161 / mii.c:263-267 (M_DEVBUF, M_WAITOK); (b) rg 'kfree' sys/dev/netif/mii_layer/ yields only tlphy.c:183 / mlphy.c:218,426 (devlist, M_TEMP β€” unrelated); (c) newbus ivars semantics: subr_bus.c:1216 (init NULL), 1284-1310 (delete frees device struct only), 1809-1820 (get/set ivars β€” plain pointer ops); (d) NIC detach paths all delete the miibus child (if_bge.c:2727, if_nfe.c:678, if_xl.c:1581, if_vr.c:831, if_vge.c:1086, ...), confirming the free-in-miibus_detach placement is reached on every destroy.

Impact ceiling

Slow, bounded kernel-heap exhaustion (CWE-401). The leaked blocks are never referenced again (no UAF composition). Realistic amplifiers: bge 5Γ—-retry on PHY-less cards at every attach; USB dongle replug cycles (physical access) on if_rue. No C/I impact; severity Low.

Fix

fix.diff (applies cleanly; compiles under -Werror as a KLD source on the guest β€” fixcheck-build.log): - miibus_detach(): free each child's mii_attach_args (children still enumerated there), then the bus's own mii_probe_args; NULL the ivars pointers via device_set_ivars() after each free. - mii_probe() ENXIO path: free args before device_delete_child().

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 (free per-PHY mii_attach_args and the bus mii_probe_args in miibus_detach with device_set_ivars(NULL) after each free; free args on the mii_probe ENXIO path) applies cleanly and compiles under -Werror as a KLD source on the guest (fixcheck-build.log). End-to-end exercise requires miibus hardware (absent); placement verified against every NIC driver's detach path (all delete the miibus child: if_bge.c:2727, if_nfe.c:678, if_xl.c:1581, ...).

fixcheck-build.log (miifix2.ko linked from patched mii.c); fix.diff (git apply --check OK)
↓ fix.diffper-fix-DF-5002

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (full static proof with path:line for every claim); fixcheck-build.log (fix.diff's patched mii.c compiles clean under the kernel -Werror set as a KLD source on the guest); fix.diff (git apply --check OK)

PoC changes

No runtime PoC (see verdict): triggering requires miibus hardware absent from the guest; a synthetic fake-NIC scaffold would test the scaffold, not the code. Static evidence pack + compile-checked fix instead.

Verified recommended fix

Free the ivars miibus owns: kfree(child mii_attach_args) in miibus_detach, kfree(bus mii_probe_args) at detach end and on the mii_probe ENXIO path, NULLing ivars after each free (fix.diff).

Verdict

UNTESTED dynamically (no miibus-attached NIC exists on the QEMU guest, so neither leak path can be driven without a custom fake-NIC scaffold), but the defect is certain by inspection: miibus_probe() kmallocs one mii_attach_args per PHY child (mii.c:157-161) and mii_probe() one mii_probe_args for the bus (mii.c:263-267), both stored as newbus ivars; newbus never frees ivars (device_delete_child, subr_bus.c:1284-1310, frees the device struct and drops the pointer; NULL only at creation, subr_bus.c:1216) and no kfree for these allocations exists anywhere in sys/ (only tlphy.c:183 / mlphy.c:218,426 free unrelated M_TEMP devlists). Consequently every failed mii_probe() leaks one mii_probe_args immediately (mii.c:274-278; bge retries up to 5x per attach, if_bge.c:2582-2589) and every miibus destroy -- NIC detach via module unload or CardBus/USB hot-removal (if_rue USB NICs use miibus) -- leaks the bus args plus one child args per PHY (~104-152 bytes/cycle). Pure CWE-401 resource leak, slow bounded heap exhaustion only; the blocks are never referenced again, so no UAF composition and no C/I impact. Severity Low.