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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5643 Β· 12 files
FileTypeDescriptionSize
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+0x78 via the exact smbioctl exit path; plus a parked waiter woken on the FREED softc whose smbus_request_bus then 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:

  1. frees the softc: device_detach β†’ device_set_driver(dev, NULL) (subr_bus.c:2150) β†’ kfree(dev->softc) (subr_bus.c:1941-1946);
  2. under the owner (e.g. a smbioctl in flight on /dev/smbN, which holds the bus from smb.c:240 to smb.c:356): its exit path smbus_release_bus(bus, smbdev) derefs device_get_softc(bus) == NULL at smbconf.c:166 β†’ page fault;
  3. under parked SMB_WAIT requesters (tsleep'ing on sc, smbconf.c:89/93; in-tree: ipmi_ssif.c, cyapa.c): nothing wakes them (smbus_detach has no wakeup), they sleep on freed memory forever; any stray wakeup(sc) (e.g. smbconf.c:177 broadcasts from another client on a reallocated softc) resumes them INTO the freed block: sc->owner read at smbconf.c:128, sc->owner = dev write at smbconf.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.

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

With 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)
↓ fix.diffper-fix-DF-5643

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.