# DF-5521 VERDICT — REPRODUCED (kernel UAF / freed-module-text execution; fix validated)

## The bug

`iicsmb_detach()` (`sys/bus/iicbus/iicsmb.c:164-175`) has no quiesce of any
kind: no busy count, no drain, no check whether the bridge currently owns
the underlying iicbus, and it ignores `bus_generic_detach()`'s error before
unconditionally deleting its smbus child.  An SMBus client transaction
routed through the bridge (every `/dev/smb0` ioctl, every ipmi_ssif
request, every kernel smbus client) holds the iicbus for the whole
transaction (`smbioctl` -> `smbus_request_bus` -> `iicsmb_callback`
-> `iicbus_request_bus(parent, iicsmb0)` sets `iicbus_softc->owner`,
`smb.c:240-243,356`) and can be asleep inside `iicbus_read()/iicbus_write()`
(controller polling / bit-bang delays) below the bridge.

`devclass_delete_driver()` (`sys/kern/subr_bus.c:853-899`) force-calls
`device_detach()` for every device of the driver and, on success, the
kldunload completes and frees the module text.  Because iicsmb.ko also
registers the **smbus driver on the iicsmb bus** (`iicsmb.c:510`,
`DRIVER_MODULE(smbus, iicsmb, ...)`), unloading iicsmb.ko force-detaches
`smbus0`/`smb0` through that registration *without ever calling
`iicsmb_detach`* — so even a guard in `iicsmb_detach` alone does not close
the window (verified: two crashes through exactly that path during fix
development).

## Reproduction (baseline, stock modules)

Rig: software iicbus controller on isa (`df5521_rig.c`) -> production
chain `iicbus0/iicsmb0/smbus0/smb0` with `/dev/smb0` (0600 root).
`hw.dfa.rdelay=300` makes the controller's READ sleep 300 ms so a BREAD
ioctl is observably in flight.  PoC `race`: child loops SMB_BREAD; parent
`kldunload` mid-transaction.  Captured serial (`panic.txt`):

    READ req_len=255  <== bridge *count
    smb0: detached
    smbus0: detached
    iicsmb0: detached
    df5521-wire: READ gave 255 bytes: 08 a0 a1 a2 ...
    Fatal trap 12: page fault while in kernel mode
    fault virtual address   = 0xffffffff8261f0bf
    fault code              = supervisor read instruction, page not present
    current process         = 989
    Stopped at      iicbus_devclass+0x1e3e7:

The faulting address `0xffffffff8261f0bf` lies inside iicsmb.ko's unloaded
text range (`kldstat` before the unload: `iicsmb.ko` at
`0xffffffff8261f000`, size `0xe000`): the thread that was asleep in the
controller read **resumed executing freed module code** after
`kldunload iicsmb` completed.  A second crash mode (observed twice during
fix development, same race): the woken sleeper returns into
`iicsmb_bread`, then `smbioctl` calls `smbus_release_bus()` on the
already-freed `smbus0` device -> `Fatal trap 12 ... fault virtual address
= 0x0 ... Stopped at smbus_release_bus+0x78`.

Additionally, after any successful unload `iicbus_softc->owner` keeps
pointing at the orphaned iicsmb0 device whose driver was unhooked, so the
next `iicbus_intr()` dispatches through a driverless kobj
(`iiconf.c:50-51`).

## Threat model

Triggering requires root (open `/dev/smb0` 0600 + `kldunload`, or root's
kernel clients racing an admin unload — e.g. ipmi_ssif transactions from
`/dev/ipmi0` or the SSIF poller).  It is a genuine kernel UAF (execution
of freed text + freed-device dereference) but privileged-only: severity
Low, family-consistent with DF-5508 (amdsmb no-quiesce) which this audit
rates Info for the weaker lock-only variant.

## Fix (validated)

`fix.diff` (3 files):

  * `iiconf.c`/`iiconf.h`: new `iicbus_bus_owned(bus, dev)` accessor.
  * `iicsmb.c` `iicsmb_detach()`: return EBUSY while the bridge owns the
    iicbus; propagate `bus_generic_detach()` errors; NULL-safe child delete.
  * `smbus.c` `smbus_detach()`: return EBUSY while a client holds the
    smbus — this closes the `smbus/iicsmb` driver-module unload path that
    bypasses `iicsmb_detach` entirely (iicsmb.c:510 registration).

Fix validation (guest, patched iicbus.ko/iicsmb.ko/smb.ko + rebuilt
kernel because smbus is compiled into X86_64_GENERIC):

    poc: >>> kldunload iicsmb mid-BREAD (expect EBUSY) <<<
    kldunload: can't unload file: Device busy
    poc: child still alive? yes (in-flight ioctls completing normally)
    poc: >>> kldunload iicsmb after client stopped (expect success) <<<
    rc=0
    poc: firing INTR_STOP via iicbus_intr after clean unload ...
    poc: racefixed done -- GUEST ALIVE, fix validated

Baseline (no fix): panic in freed text.  Patched: unload refused during
the transaction, clean unload once quiesced, post-unload interrupt is a
no-op (owner cleared).  Residual: a *complete* fix would also pin the
module across request/release (refcount) rather than refusing detach;
the EBUSY guards close every demonstrated window.

## Cross-references

* `sys/bus/iicbus/iicsmb.c:164-175` (no-quiesce detach), `:509-510`
  (dual driver-module registrations).
* `sys/kern/subr_bus.c:853-899` (force-detach on unload).
* `sys/bus/iicbus/iiconf.c:45-54,85-148` (owner lifecycle, intr dispatch).
* `sys/dev/smbus/smb/smb.c:240-243,356` (transaction holds the bus).

## 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.
