iicbus_detach swallows bus_generic_detach()'s error: a child that fails to detach leaves the bus marked detached + driverless with its softc freed while the child is still attached β the child's next iicbus_request_bus faults on device_get_softc(bus)==NULL (sc->owner at NULL+8)
Summary
bus_generic_detach's return is discarded and detach returns 0 unconditionally: a child refusing EBUSY still lets device_detach complete the teardown (softc freed, driver NULL) while the child stays attached - its next iicbus_request_bus reads sc->owner at NULL+8. Reproduced on stock: detach returned 0 despite REFUSING(EBUSY), post-state driverless/softc=0 with child still attached, then Fatal trap 12 fault VA 0x8 at iicbus_request_bus+0x89. The DF-5563 lead answered: the iiconf drain protocol does require this detach to wake/drain waiters (DF-5563's fix adds it); this is the independent swallowed-error defect at the same lines. propagate fix validated (returns 16, bus stays attached, no panic).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5635 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 1.6 KB | β raw | |
| verdict.json | β | 4.4 KB | view raw | |
| manifest.json | β | 968 B | view raw | |
| fix.diff | β | 5.5 KB | view raw | |
| panic_5635_stock.txt | β | 2.5 KB | view raw | |
| run_c_panic_driver.log | β | 518 B | view raw | |
| run_d_fixed.log | β | 1.1 KB | view raw | |
| run_d_fixed_dmesg.txt | β | 2.0 KB | view raw | |
| build.log | β | 30.8 KB | view raw | |
| env.txt | β | 354 B | view raw |
DF-5635 β iicbus_detach swallows bus_generic_detach() failure β driverless NULL-softc bus with an attached child β NULL-page fault in iicbus_request_bus
- File under audit:
sys/bus/iicbus/iicbus.c(pass 2) - Class: lifecycle error-handling β kernel NULL-page out-of-bounds read β panic (local DoS)
- Severity: Low (requires a child whose detach fails β root-gated setup; in-tree iicsmb's DF-5521-guarded detach is the natural producer)
- Verdict: REPRODUCED (Fatal trap 12, fault VA 0x8, at
iicbus_request_bus+0x89: movq 0x8(%r12),%rax=sc->owner, iiconf.c:101), fix validated
The defect
static int
iicbus_detach(device_t dev) /* iicbus.c:156-162 */
{
iicbus_reset(dev, IIC_FASTEST, 0, NULL);
bus_generic_detach(dev); /* return value DISCARDED */
return (0); /* always success */
}
bus_generic_detach (subr_bus.c) detaches children and returns the first
child's error (e.g. EBUSY from a busy client). iicbus_detach ignores it
and returns 0, so device_detach(iicbus0) believes the driver detached
cleanly and completes the teardown:
device_set_driver(iicbus0, NULL)β softc kfree'd (subr_bus.c,device_set_driver) and kobj switched tonull_class;dev->state = DS_NOTPRESENT.
β¦while the failing child is still DS_ATTACHED under the now-driverless
bus. The child's very next bus operation,
iicbus_request_bus(bus, dev, how) (iiconf.c:86-117), does
struct iicbus_softc *sc = device_get_softc(bus); /* iiconf.c:88: NULL */
...
if (sc->owner && sc->owner != dev) /* iiconf.c:101: *0x8 */
β read at address offsetof(struct iicbus_softc, owner) == 8 on a NULL
base β Fatal trap 12.
The DF-5563 question ("does the iiconf drain/dying protocol need anything
from this file's detach?") is answered yes: DF-5563's validated fix
already patches iicbus_detach with the dying-flag + wakeup(sc) + drain
loop this file owes the waiter protocol β that half is DF-5563's and is
not re-reported here. This finding covers the independent defect living
at the same lines: the swallowed child-detach error.
Reproduction (guest, stock modules)
run_c_panic.sh / panic_5635_stock.txt β the rig attaches a client
child (df5632k0) whose detach returns EBUSY on demand, then drives the
production APIs:
[df5632] df5632k0 detach: REFUSING (EBUSY) iicbus0: detached [df5632] DETACH_BUS: device_detach(iicbus0) returned 0 (child df5632k0 busy=1) [df5632] DETACH_BUS-post: ... driver=(NONE-DRIVERLESS) state=0 softc=0 [df5632] DETACH_BUS-post: child[3] devclass=df5632k unit=0 driver=df5632k state=3 [df5632] REQBUS: calling iicbus_request_bus(iicbus0, self, WAIT|INTR) Fatal trap 12: page fault while in kernel mode fault virtual address = 0x8 Stopped at iicbus_request_bus+0x89: movq 0x8(%r12),%rax
The swallow (returned 0 while the child refused), the inconsistent state
(driverless bus with softc=0 + attached child), and the predicted NULL-deref
(fault VA exactly offsetof(owner)=8) are all on the serial console.
Fix (validated)
fix.diff propagates the error: error = bus_generic_detach(dev);
return (error); (and propagates iicbus_reset's error too; composes
trivially with DF-5563's drain block). Fixed module
(run_d_fixed.log): device_detach returned 16 (EBUSY), the bus stays
driver=iicbus state=3 softc=0xfffff8008d581130 (attached, alive), the
child stays attached, and iicbus_request_bus on the still-attached bus
returns 0 (request+release clean). No panic anywhere.
DF-5635 VERDICT β REPRODUCED (swallowed child-detach error β NULL-softc deref panic), fix validated
See README.md for the full narrative. Decisive evidence (stock, panic_5635_stock.txt):
[df5632] df5632k0 detach: REFUSING (EBUSY) iicbus0: detached [df5632] DETACH_BUS: device_detach(iicbus0) returned 0 (child df5632k0 busy=1) <- THE SWALLOW [df5632] DETACH_BUS-post: iicbus=... driver=(NONE-DRIVERLESS) state=0 softc=0 <- softc freed [df5632] DETACH_BUS-post: child[3] devclass=df5632k unit=0 driver=df5632k state=3 <- child still attached Fatal trap 12: page fault while in kernel mode fault virtual address = 0x8 <- offsetof(iicbus_softc, owner) Stopped at iicbus_request_bus+0x89: movq 0x8(%r12),%rax <- iiconf.c:101 sc->owner
Fixed (run_d_fixed.log): device_detach(iicbus0) returned 16 (EBUSY),
bus stays driver=iicbus state=3 softc=0xfffff8008d581130, REQBUS:
returned 0 on the attached bus, clean unload β the inconsistent state and
the panic are gone.
DF-5563 lead answer: the iiconf drain/dying protocol DOES require this file's detach to wake/drain waiters β DF-5563's validated fix.diff already adds that block to iicbus_detach; this finding is the independent swallowed-error defect at the same lines.
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
fixedWith iicbus.ko rebuilt from fix.diff, the same busy-child scenario returns EBUSY (16) from device_detach(iicbus0); the bus remains attached (driver=iicbus state=3, softc non-NULL), the child stays attached, and iicbus_request_bus on the attached bus succeeds (rc=0, clean release). The inconsistent-state NULL-softc panic path is eliminated.
run_d_fixed.log ('dfctl detach_bus: kernel rc=16', 'dfctl reqbus: kernel rc=0'); run_d_fixed_dmesg.txt ('DETACH_BUS: device_detach(iicbus0) returned 16', 'driver=iicbus state=3 softc=0xfffff8008d581130', 'RUN-D-DONE')
Confirmed kernel references
- sys/bus/iicbus/iicbus.c:156-162
- sys/bus/iicbus/iiconf.c:88
- sys/bus/iicbus/iiconf.c:101
- sys/kern/subr_bus.c:device_detach
- sys/kern/subr_bus.c:device_set_driver
- sys/bus/iicbus/iicsmb.c:155-171
Detail
Exploit chain
root: attach an iicbus client child that can refuse detach (rig df5632k0; in-tree: iicsmb while bridge-owned per DF-5521) -> force bus driver removal (kldunload path / devclass_delete_driver / device_detach) -> iicbus_detach swallows the child's EBUSY -> newbus completes teardown: softc freed, driverless, state DS_NOTPRESENT, child still attached -> any subsequent iicbus_request_bus(child) reads sc->owner at NULL+8 -> kernel page fault, panic.
Evidence (decisive lines)
panic_5635_stock.txt ('df5632k0 detach: REFUSING (EBUSY)', 'iicbus0: detached', 'device_detach(iicbus0) returned 0', 'driver=(NONE-DRIVERLESS) state=0 softc=0' with 'child[3] ... driver=df5632k state=3', then 'Fatal trap 12', 'fault virtual address = 0x8', 'Stopped at iicbus_request_bus+0x89: movq 0x8(%r12),%rax'); run_d_fixed.log ('DETACH_BUS: device_detach(iicbus0) returned 16', bus stays 'driver=iicbus state=3 softc=0xfffff8008d581130', 'REQBUS: returned 0')
PoC changes
Rig lives in ../DF-5632/ (df5632_rig.c): df5632k child driver whose detach returns EBUSY under hw.df5632.childbusy=1; /dev/df5632 ioctls drive production device_detach(iicbus0) and iicbus_request_bus().
Verified recommended fix
Propagate the failure: error = bus_generic_detach(dev); if (error) return error; (plus propagate iicbus_reset's error) -- see fix.diff; composes trivially with DF-5563's drain block at the head of the same function.
Verdict
REPRODUCED on stock iicbus.ko: iicbus_detach (iicbus.c:156-162) discards bus_generic_detach()'s error and returns 0 unconditionally, so a child that refuses to detach (EBUSY -- in-tree producer: DF-5521-guarded iicsmb) leaves iicbus0 marked detached + driverless with its softc kfree'd (device_set_driver(NULL)) while the child remains DS_ATTACHED; the child's next iicbus_request_bus derefs device_get_softc(bus)==NULL at iiconf.c:88/:101 -- Fatal trap 12, fault VA 0x8 (= offsetof(struct iicbus_softc, owner)), Stopped at iicbus_request_bus+0x89. Also answers the DF-5563 lead: yes, iiconf's drain/dying protocol requires the wake/drain from THIS detach, which DF-5563's validated fix already adds (not re-reported). fix.diff propagates the error; fixed: device_detach returns EBUSY, bus stays attached and servicable.
No comments yet.