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

smbus_attach unconditionally re-creates the generic child and the whole 96-address probe child set on every attach: children survive every teardown path, so each detach+re-attach cycle leaks the full set of struct bsd_device and mints duplicate remapped /dev/smbN[-XX] nodes whose forced-slave address encoding no longer matches the device it probed

Summary

No first-attach guard and no deletion counterpart; children survive kldunload (detaches-only) and the viapm_586b asymmetric bridge teardown, so every re-attach mints a SECOND full set while devclass remaps the duplicates' wired units. Reproduced on the production-code rig: nchildren 4->44 after 10 cycles (+4/cycle), /dev/smb* nodes 4->44; the re-minted probe children land on remapped units whose ioctl path forces a slave address nothing answers, while duplicate plain nodes pile onto one physical bus. Root-gated unbounded M_BUS leak (~0x2c0 x up to 97 per cycle) + topology confusion for cyapa/atmel/ipmi. DF-5633 twin; first-attach-guard fix validated (constant across 9 cycles).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5642 Β· 13 files
FileTypeDescriptionSize
df5642_rig.c β€” 13.9 KB view raw
df5642ctl.c β€” 1.5 KB view raw
Makefile β€” 267 B ↓ download
build.log β€” 1.2 KB view raw
run_a.sh β€” 1.6 KB view raw
run_a.log β€” 1.3 KB view raw
run_a2.log β€” 896 B view raw
run_a_fixed.log β€” 190 B view raw
README.md β€” 3.6 KB ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
verdict.json β€” 5.1 KB view raw
fix.diff β€” 1.5 KB view raw
env.txt β€” 1.1 KB view raw

DF-5642 β€” smbus_attach unconditionally re-creates the whole child set on every attach β†’ unbounded child leak + duplicate /dev/smbN[-XX] nodes on one bus

  • File under audit: sys/bus/smbus/smbus.c (pass 2)
  • Class: resource leak / device-topology corruption (DF-5633 twin at the smbus layer)
  • Severity: Low (root-gated: kldunload/kldload of a bridge with surviving children, rescan/rebind)
  • Verdict: REPRODUCED (4β†’44 children after 10 cycles, +4/cycle; /dev/smb* 4β†’44; duplicate forced-slave nodes with corrupted address encoding), fix validated (children constant across cycles on the patched kernel)

The defect

smbus_attach (smbus.c:94-97) populates the bus unconditionally:

device_add_child(dev, NULL, device_get_unit(dev));      /* generic smb child */
for (addr = 16; addr < 112; ++addr)
        smbus_probe_device(dev, addr);                  /* per-address smb children */

with no re-creation guard, while smbus_detach (smbus.c:105-115) only runs bus_generic_detach and never deletes the children. Children survive every teardown path:

  • devclass_delete_driver (the kldunload path, subr_bus.c:888-897) detaches devices but never deletes them;
  • the DF-5633-class asymmetric bridge detach: viapm_586b_detach (viapm.c:423-430) detaches the shared-attach smbus child (viapm.c:362) but only deletes iicbb;
  • any rescan/rebind that detaches and re-attaches the smbus device.

Every later device_probe_and_attach(smbus0) (exactly what bus_generic_driver_added, subr_bus.c:3012-3015, does on driver re-registration) re-runs smbus_attach and mints a second full child set:

  • +1 struct bsd_device (generic child) + one per responding address (probe loop) per cycle, unbounded (M_BUS leak, ~0x2c0 bytes each);
  • the new children take remapped units (devclass_alloc_unit, subr_bus.c:1072-1081), so each cycle mints duplicate /dev/smbN[-XX] character devices on ONE physical bus β€” e.g. the second copy of the address-0x10 probe child lands at unit 1041 and is exposed as /dev/smb0-11, whose ioctl path (smb.c:229-231) then forces slave address 0x11 β€” an address nothing responds on: the node-name ↔ physical-address contract (smb.c:134-141, cyapa.c:466-472) is corrupted.

Reproduction (guest, stock kernel + production-code KLD rig)

Family-standard rig (df5642_rig.c): a fake SMBus controller on nexus registers the real in-kernel smbus driver (DRIVER_MODULE(smbus, fakesmbb, smbus_driver, ...), mirroring intpm.c:903) and creates smbus0 exactly like intpm.c:331-337. The emulated controller answers addresses 0x10/0x50/0x67, so each attach mints 1 generic + 3 probe children, claimed by the real in-kernel smb driver β†’ real make_dev nodes.

run_a.sh / run_a2.log β€” production APIs only (device_detach(smbus0) + device_probe_and_attach(smbus0)):

[df5642] attach: nchildren=4                      (smb0 + smb1040/1104/1127)
node_count= 4                                     (/dev/smb0 smb0-10 smb0-50 smb0-67)
[df5642] after-cycle: nchildren=8                 (+4 per cycle, deterministic)
[df5642] after-cycle: nchildren=44                (10 cycles)
FINAL_node_count= 44
/dev/smb0 ... smb0-11 smb0-12 smb0-13 ... smb1 smb2 smb3 ...  (duplicates)

Each cycle's new probe children land on remapped units (1041..) β†’ duplicate smb0-XX nodes whose -XX no longer names the address they probed.

Fix (validated)

fix.diff populates the bus only when it has no children yet (device_get_children guard in smbus_attach). On the patched kernel (run_a_fixed.log): nchildren stays 4 across cycles, node count stays 4.

VERDICT.md
↓ download raw

DF-5642 VERDICT β€” smbus_attach unguarded child re-creation (leak + duplicate /dev nodes)

Status: REPRODUCED, fix validated. Impact: dos (unbounded kernel heap leak + duplicate/corrupted /dev/smbN[-XX] topology). Root-gated trigger.

What was proven, on what code

The rig (df5642_rig.c, this pack) registers the REAL in-kernel smbus driver (DRIVER_MODULE(smbus, fakesmbb, smbus_driver, smbus_devclass, ...) β€” the same registration intpm.c:903 performs for the PIIX4) under a fake controller on nexus, and creates smbus0 exactly like intpm.c:331-337 (device_add_child(dev, "smbus", -1) + device_probe_and_attach). The production smbus_attach (smbus.c:94-97) then runs unmodified: it adds the unnamed generic child and drives 96 smbus_trans probes against the emulated controller (which answers 0x10/0x50/0x67), creating the per-address children; the real smb driver claims them and smb_attach (smb.c:139-141) mints real /dev/smbN[-XX] devfs nodes. Nothing in the chain is stubbed except the wire.

/dev/df5642 ioctls call the production APIs device_detach(smbus0) + device_probe_and_attach(smbus0) β€” the same functions the module handlers invoke on kldunload/kldload (devclass_delete_driver, subr_bus.c:888-897, detaches but never deletes children; bus_generic_driver_added, subr_bus.c:3012-3015, re-probes them).

Baseline (stock kernel, run_a.log + run_a2.log)

  • initial: nchildren=4 (smb0, smb1040, smb1104, smb1127) β†’ /dev/smb0 smb0-10 smb0-50 smb0-67;
  • each cycle: device_detach=0, device_probe_and_attach=0, after-cycle: nchildren grows by exactly 4 (1 generic + 3 probe children) β€” 10 cycles: 4 β†’ 44, node count 4 β†’ 44;
  • the re-minted probe children take remapped units (devclass_alloc_unit, subr_bus.c:1072-1081): e.g. the second copy of the address-0x10 probe child lands at unit 1041 β†’ /dev/smb0-11, and smbioctl then FORCES slave 0x11 on it (smb.c:229-231) β€” an address no device answers. The node-name ↔ physical-address contract (smb.c:134-141, cyapa.c:466-472) is corrupted while duplicate plain nodes (smb1, smb2, ...) pile up on one physical bus.

Root cause is exactly smbus.c:94-97 (no first-attach guard) + smbus.c:105-115 (detach never deletes children) + the family's teardown asymmetry (viapm_586b_detach, viapm.c:423-430, deletes iicbb but not the smbus child created at viapm.c:362).

Fix validation (fix.diff, run_a_fixed.log)

fix.diff populates the bus only when device_get_children reports it empty. Patched kernel (rebuilt with nativekernel, boot #1 Fri Oct 9 04:55, smb.ko loaded first): 9 cycles β†’ after-cycle: nchildren=5 every time (the 5th is the load-order identify child), /dev/smb* count constant at 5. Baseline growth eliminated.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

With fix.diff (first-attach guard) built into the guest kernel, 9 detach/re-attach cycles keep nchildren=5 and /dev/smb* count constant at 5 (baseline: +4 per cycle, 4->44); unload/reload clean, no trap.

run_a_fixed.log ('after9: 5 nodes'; dmesg 9x 'after-cycle: nchildren=5'; final ls shows exactly smb0 smb1 smb0-10 smb0-50 smb0-67)
↓ fix.diffper-fix-DF-5642

Confirmed kernel references

Detail

Exploit chain

root: detach+re-attach smbus0 (kldunload/kldload of a bridge whose smbus child survives -- viapm 586b, out-of-tree bridges, rescan/rebind, or devclass_delete_driver of the smbus driver with a resident parent) -> each cycle device_add_child mints 1+N struct bsd_device (M_BUS, ~0x2c0 bytes each) while the survivors re-attach -> unbounded heap leak + duplicate remapped /dev/smbN[-XX] nodes on one physical bus with corrupted slave-address binding (smb.c:229-231 forces slave=unit&1023).

Evidence (decisive lines)

run_a2.log ('attach: nchildren=4' -> 'after-cycle: nchildren=8..44', FINAL_node_count=44; duplicate node list smb0-11..13/51..53/68..6a + smb1..6); run_a.log (per-cycle device_detach=0/reattach=0 with nchildren growth); run_a_fixed.log (patched: 'after9: 5 nodes', 9x 'after-cycle: nchildren=5'); env.txt (stock vs patched uname); panic_raw.txt also holds the serial history.

PoC changes

Rig in this pack (df5642_rig.c): fake SMBus controller on nexus registers the real in-kernel smbus driver (DRIVER_MODULE(smbus, fakesmbb, ...) mirroring intpm.c:903) and creates smbus0 like intpm.c:331-337; /dev/df5642 ioctls STAT/CYCLE n drive the production device_detach/device_probe_and_attach APIs. Two build iterations fixed ioccom include / dev_ops prototypes / 64-bit respond mask; smb.ko is in-kernel on the stock guest kernel (the rebuilt fix-validation kernel needs kldload /boot/kernel/smb.ko first: repo config lacks 'device smb').

Verified recommended fix

Populate the bus only when it has no children yet (device_get_children guard in smbus_attach); optionally also device_delete_children in smbus_detach.

Verdict

REPRODUCED on the stock kernel via a production-code KLD rig hosting the real in-kernel smbus driver (registered on a fake controller exactly like intpm.c:903, smbus0 created exactly like intpm.c:331-337): smbus_attach (smbus.c:94-97) re-creates the generic child AND the whole address-probe child set on EVERY attach, while smbus_detach (smbus.c:105-115) and every teardown path (devclass_delete_driver subr_bus.c:888-897; viapm_586b_detach viapm.c:423-430 deletes iicbb but not the smbus child created at viapm.c:362) only detach children, never delete them. Each device_detach(smbus0)+device_probe_and_attach(smbus0) cycle (the production kldunload/kldload choreography) leaked the full set: nchildren 4->44 and /dev/smb node count 4->44 after 10 cycles (+4/cycle deterministic); re-minted probe children take remapped units (devclass_alloc_unit subr_bus.c:1072-1081) so duplicate forced-slave nodes appear with corrupted address encoding (second copy of address 0x10 lands at unit 1041 -> /dev/smb0-11, whose ioctl path forces slave 0x11 -- an address nothing answers). fix.diff guards population on first attach (device_get_children); on the patched kernel (nativekernel rebuild, boot #1 Oct 9) 9 cycles keep nchildren=5 and node count constant -- baseline growth eliminated.