smbus_detach never checks sc->owner: the bus detaches (and device_detach frees the softc) underneath the owning client and parked requesters β the owner's release (the smbioctl exit path) takes Fatal trap 12, and a parked SMB_WAIT requester woken on the freed softc performs a post-free read+write and returns success
Summary
The detach ignores the owner state entirely: under an owner, the release path reads sc->owner with sc==NULL - reproduced Fatal trap 12 at smbus_release_bus+0x78; under parked SMB_WAIT requesters nothing wakes them (they sleep on freed memory unkillably) and a stray wakeup - deliverable in production by the release-broadcast once the softc is reallocated - resumes them inside the loop: reproduced post-free read + 8-byte device_t WRITE into the freed M_BUS block returning 0 (a grant the waiter believes it owns). Root-gated detach + owned/parked client precondition. owner-EBUSY guard fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5643 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| run_b.sh | β | 715 B | view raw | |
| run_c.sh | β | 1.2 KB | view raw | |
| run_b.log | β | 207 B | view raw | |
| run_c.log | β | 1.5 KB | view raw | |
| run_b_fixed.log | β | 325 B | view raw | |
| run_c_fixed.log | β | 1.3 KB | view raw | |
| panic.txt | β | 1.1 KB | view raw | |
| panic_raw.txt | β | 1.6 KB | view raw | |
| fix.diff | β | 1.5 KB | view raw | |
| README.md | β | 4.0 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| verdict.json | β | 5.3 KB | view raw |
DF-5643 β smbus_detach never checks sc->owner: the bus (and its softc) is torn down under the owning client and under parked requestes β NULL-deref panic + post-free write
- File under audit:
sys/bus/smbus/smbus.c(pass 2) - Class: lifetime/UAF (DF-5635 twin at the smbus layer; DF-5508 "detach no-drain" twin)
- Severity: Low (root-gated detach: kldunload/rescan/hot-removal)
- Verdict: REPRODUCED (Fatal trap 12 in
smbus_release_bus+0x78via the exactsmbioctlexit path; plus a parked waiter woken on the FREED softc whosesmbus_request_busthen returned success β a post-free read+write), fix validated (detach vetoes with EBUSY)
The defect
struct smbus_softc carries device_t owner (smbus.h:34) β the
client exclusion state written by smbus_request_bus (smbconf.c:134).
smbus_detach (smbus.c:105-115) ignores it completely:
static int
smbus_detach(device_t dev)
{
int error;
error = bus_generic_detach(dev); /* no owner check, no drain */
if (error)
return (error);
return (0);
}
A successful device_detach(smbus0) then:
- frees the softc:
device_detachβdevice_set_driver(dev, NULL)(subr_bus.c:2150) βkfree(dev->softc)(subr_bus.c:1941-1946); - under the owner (e.g. a
smbioctlin flight on/dev/smbN, which holds the bus fromsmb.c:240tosmb.c:356): its exit pathsmbus_release_bus(bus, smbdev)derefsdevice_get_softc(bus) == NULLatsmbconf.c:166β page fault; - under parked SMB_WAIT requesters (tsleep'ing on
sc,smbconf.c:89/93; in-tree:ipmi_ssif.c,cyapa.c): nothing wakes them (smbus_detachhas nowakeup), they sleep on freed memory forever; any straywakeup(sc)(e.g.smbconf.c:177broadcasts from another client on a reallocated softc) resumes them INTO the freed block:sc->ownerread atsmbconf.c:128,sc->owner = devwrite atsmbconf.c:134.
Reproduction (guest, stock kernel + production-code KLD rig)
Same rig as DF-5642 (/dev/df5642 ioctls drive the production APIs).
RUN B (run_b.sh / run_b.log + panic.txt): GRAB sets a
verifiable owner, then device_detach(smbus0), then the owner's
release β the smb.c:356 sequence:
[df5642] GRAB: request(fakesmbb0, DONTWAIT)=0 owner=0xfffff8011745d1e0 [df5642] VETO: pre-detach owner=0xfffff8011745d1e0 (must be non-NULL) [df5642] VETO: device_detach(smbus0)=0 <<< BUS DETACHED UNDER ITS OWNER [df5642] VETO: post-detach softc=0 (NULL = freed under the owner) [df5642] RELFREE: calling smbus_release_bus(smbus0, fakesmbb0) with softc=0 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x0 Stopped at smbus_release_bus+0x78: cmpq %r13,(%r12) <- sc->owner == dev, sc=NULL
RUN C (run_c.sh / run_c.log): parked waiter on the freed softc:
[df5642] GRAB: ... owner=0xfffff8011745d1e0
[df5642] PARK-T: calling request(nexus0, WAIT|NOINTR) softc=0xfffff8008d560cc8
[df5642] PARK: waiter started=1 done=0 (parked on softc=0xfffff8008d560cc8)
-1 B5 smbreq df5642_park <- ps: parked, never woken by detach
[df5642] VETO: device_detach(smbus0)=0 <<< BUS DETACHED UNDER ITS OWNER
[df5642] VETO: post-detach softc=0 (NULL = freed under ... the parked waiter)
[df5642] WAKUAF: wakeup(freed sc=0xfffff8008d560cc8)
[df5642] PARK-T: request RETURNED 0 (post-free resume) <- read+write on freed M_BUS
The waiter read the freed sc->owner (garbage), took the grant branch,
wrote its device_t into the freed block (smbconf.c:134), and
returned SUCCESS β a 8-byte pointer write into freed M_BUS memory
(whatever reallocated that size class is now corrupted; with
smbus_release_bus's unconditional wakeup(sc) broadcast at
smbconf.c:177, the stray wake is deliverable by any other client's
release on the reallocated softc).
Fix (validated)
fix.diff (shared with DF-5642's pack) makes smbus_detach refuse
with EBUSY while a client owns the bus. On the patched kernel
(run_b_fixed.log): device_detach=16 (EBUSY), softc intact, the
owner's release returns 0 β no trap.
DF-5643 VERDICT β smbus_detach ignores sc->owner: softc freed under owner and parked waiters (trap + post-free write)
Status: REPRODUCED, fix validated. Impact: panic (Fatal trap 12) + post-free read/write on M_BUS (demonstrated), root-gated detach trigger.
What was proven, on what code
Same rig as DF-5642 (see that pack). struct smbus_softc.owner
(smbus.h:34) is the exclusion state smbus_request_bus maintains
(smbconf.c:126-141); smbus_detach (smbus.c:105-115) never reads it,
so device_detach(smbus0) succeeds under a verifiable owner, and
device_detach β device_set_driver(dev, NULL) (subr_bus.c:2150) β
kfree(dev->softc) (subr_bus.c:1941-1946) frees the softc underneath:
RUN B (baseline, run_b.log + panic.txt) β the owner's release is
the exact smbioctl exit path (smb.c:356):
[df5642] GRAB: request(fakesmbb0, DONTWAIT)=0 owner=0xfffff8011745d1e0 [df5642] VETO: pre-detach owner=0xfffff8011745d1e0 (must be non-NULL) [df5642] VETO: device_detach(smbus0)=0 <<< BUS DETACHED UNDER ITS OWNER [df5642] VETO: post-detach softc=0 (NULL = freed under the owner) [df5642] RELFREE: calling smbus_release_bus(smbus0, fakesmbb0) with softc=0 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x0 Stopped at smbus_release_bus+0x78: cmpq %r13,(%r12) (sc->owner == dev; sc = NULL; smbconf.c:166)
RUN C (baseline, run_c.log) β parked waiter (tsleep(sc),
smbconf.c:93; ps shows df5642_park on smbreq):
[df5642] PARK-T: calling request(nexus0, WAIT|NOINTR) softc=0xfffff8008d560cc8 [df5642] VETO: device_detach(smbus0)=0 <<< ... post-detach softc=0 [df5642] WAKUAF: wakeup(freed sc=0xfffff8008d560cc8) [df5642] PARK-T: request RETURNED 0 (post-free resume)
The waiter resumed INSIDE smbus_request_bus on freed memory: read
sc->owner (smbconf.c:128) from the freed block, took the grant
branch, wrote its device_t into the freed block (smbconf.c:134),
and returned SUCCESS β an 8-byte pointer write into freed M_BUS heap.
In production the stray wake is deliverable by any other client's
release: smbus_release_bus broadcasts wakeup(sc) unconditionally
(smbconf.c:177), and the reallocated softc sits at the same address
the parked waiter holds. Nothing in smbus_detach wakes or drains
parked requesters (they also park unkillably per DF-5638's shape).
Fix validation (fix.diff shared with DF-5642, run_b_fixed.log,
run_c_fixed.log)
smbus_detach now returns EBUSY while sc->owner != NULL:
[df5642] VETO: device_detach(smbus0)=16 (vetoed: GOOD) [df5642] VETO: post-detach softc=0xfffff8008d382380 (intact) [df5642] RELFREE: returned 0 (no trap)
and on the parked-waiter choreography (run_c_fixed.log): the waiter
parks on the LIVE softc (same address before/after the vetoed detach),
the owner releases cleanly, the waiter is granted properly (error=0 on
the live softc). Baseline trap and post-free write 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
fixedWith fix.diff's EBUSY owner veto in the guest kernel: device_detach under a verifiable owner returns 16, the softc remains allocated (same address), the owner's release returns 0 (no trap), and a parked SMB_WAIT requester is granted correctly on the live softc after the owner's release. Baseline trap + post-free write eliminated.
run_b_fixed.log ('device_detach(smbus0)=16 (vetoed: GOOD)'; 'post-detach softc=0xfffff8008d382380'; 'RELFREE: returned 0'); run_c_fixed.log ('WAKUAF: after wakeup: done=1 error=0' on the live softc, same address pre/post veto)
Confirmed kernel references
Detail
Exploit chain
root (or hot-removal/rescan): kldunload of the bridge or any smbus0 detach while (a) a /dev/smbN ioctl holds the bus (smb.c:240..356) or (b) an ipmi_ssif/cyapa client owns/parks on it -> smbus_detach returns 0 -> softc freed -> owner's smbus_release_bus NULL-derefs (Fatal trap 12, proven) / parked waiter sleeps unkillably on freed memory and, once woken by any release's wakeup(sc) broadcast on the reallocated block, reads garbage owner and writes its device_t into freed M_BUS heap (post-free write, proven) -- with M_BUS reuse the 8-byte pointer write lands in the next same-sizeclass allocation (softc-sized kernel object).
Evidence (decisive lines)
run_b.log + panic.txt ('VETO: device_detach(smbus0)=0 <<< BUS DETACHED UNDER ITS OWNER', 'post-detach softc=0', 'Fatal trap 12 ... Stopped at smbus_release_bus+0x78: cmpq %r13,(%r12)', fault virtual address 0x0); run_c.log ('PARK-T: request RETURNED 0 (post-free resume)' after 'WAKUAF: wakeup(freed sc=...)', ps 'df5642_park smbreq'); run_b_fixed.log ('device_detach(smbus0)=16 (vetoed: GOOD)', 'post-detach softc=0xfffff8008d382380' intact, 'RELFREE: returned 0'); run_c_fixed.log (waiter granted on LIVE softc).
PoC changes
Rig shared with DF-5642 (see ../DF-5642/df5642_rig.c); ioctls GRAB/VETO/RELFREE/PARK/WAKUAF drive smbus_request_bus / device_detach / smbus_release_bus / kthread park + wakeup on the saved softc pointer. run_b.sh/run_c.sh in this pack.
Verified recommended fix
Refuse detach while a client owns the bus: in smbus_detach, return EBUSY if ((struct smbus_softc *)device_get_softc(dev))->owner != NULL (fix.diff).
Verdict
REPRODUCED on the stock kernel (same rig as DF-5642): smbus_detach (smbus.c:105-115) never checks struct smbus_softc.owner (smbus.h:34), so device_detach(smbus0) succeeds under a verifiable owner and device_detach -> device_set_driver(dev, NULL) (subr_bus.c:2150) -> kfree(dev->softc) (subr_bus.c:1941-1946) frees the softc underneath. RUN B: the owner's release -- the exact smbioctl exit path (smb.c:356) -- page-faults reading sc->owner with sc=NULL: 'Fatal trap 12 ... Stopped at smbus_release_bus+0x78: cmpq %r13,(%r12)' (smbconf.c:166), guest down in DDB. RUN C: an SMB_WAIT requester parked in tsleep(sc) (smbconf.c:93, ps shows df5642_park on smbreq) is never woken by the detach and keeps sleeping on the freed softc; a wakeup of the freed address resumes it INSIDE smbus_request_bus: it re-reads freed sc->owner (smbconf.c:128), takes the grant branch and WRITES its device_t into the freed M_BUS block (smbconf.c:134), returning SUCCESS ('PARK-T: request RETURNED 0 (post-free resume)') -- a demonstrated post-free read+write; in production the stray wake is deliverable by any client's release because smbus_release_bus broadcasts wakeup(sc) unconditionally (smbconf.c:177) and the reallocated softc occupies the same address. fix.diff adds the EBUSY owner veto; on the patched kernel device_detach under owner returns 16, the softc stays intact (same address pre/post), the owner's release returns 0 (no trap) and a parked waiter is granted properly on the live softc.
No comments yet.