smbus_acpi_detach does not quiesce in-flight GSBUS address-space handler dispatches: AcpiRemoveAddressSpaceHandler frees the handler object and its held ContextMutex, and newbus frees the softc/parent device underneath the running handler (UAF, panic/heap corruption)
Summary
GSBUS handlers run with the interpreter exited while holding the ContextMutex; detach only calls AcpiRemoveAddressSpaceHandler, which has no in-flight tracking: it unlinks and frees the HandlerObj + the ContextMutex the dispatch still holds, then newbus frees the softc (the HandlerContext) and parent device. Rig with the REAL driver under a fake ig4iic + planted SSDT driven through real ACPICA field machinery: dispatch parked mid-transfer, teardown run underneath -> stock kernel panicked 3/3 (2x spin_lock_ex indefinite wait on the freed semaphore, 1x slab chunk_mark_free assertion - heap metadata corruption from the write through freed memory); busy/detaching drain fix validated (parked_at_detach=0, no panic). SMBus sibling of DF-5518/5521/5563. Privileged trigger, memory-corruption DoS.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5582 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 5.7 KB | β raw | |
| VERDICT.md | β | 9.8 KB | β raw | |
| verdict.json | β | 5.4 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| df5582.dsl | β | 3.0 KB | β download | |
| df5582_rig.c | β | 19.5 KB | view raw | |
| Makefile.rig | β | 459 B | β download | |
| trigger.c | β | 580 B | view raw | |
| build.sh | β | 130 B | view raw | |
| run.sh | β | 95 B | view raw | |
| build.log | β | 6.7 KB | view raw | |
| run.control.log | β | 350 B | view raw | |
| run.race.stock.log | β | 859 B | view raw | |
| run.race.fixed.log | β | 916 B | view raw | |
| panic.txt | β | 1.4 KB | view raw | |
| panic.spin.txt | β | 5.2 KB | view raw | |
| fix.diff | β | 2.9 KB | view raw | |
| env.txt | β | 414 B | view raw |
DF-5582 β smbacpi: driver detach does not quiesce in-flight GSBUS
address-space handler dispatches (UAF: freed handler-object/ContextMutex/ softc under the running handler)
- File under audit:
sys/bus/smbus/smbacpi/smbacpi.c(pass 2, GLM 5.3) - Class: kernel use-after-free / lifecycle race (memcorrupt)
- Severity: Low (root-gated trigger: device teardown / kldunload of the ig4/smbacpi stack racing a concurrent AML GSBUS access; the SMBus sibling of the verified DF-5518 (gpio_acpi) and DF-5521/DF-5563 (iic) no-quiesce findings)
- Verdict: REPRODUCED β 3 natural kernel panics across 3 boots, two distinct signatures; fix validated (drain witness flips 1 β 0, guest survives the identical schedule).
What the code does
smbus_acpi_space_handler() (smbacpi.c:120-242) is the ACPI
GenericSerialBus (GSBUS) operation-region handler that smbacpi installs
on its ig4iic parent's ACPI node (smbacpi.c:89-103). Every AML GSBUS
field access is dispatched to it by ACPICA with the AML interpreter
exited (AcpiEvAddressSpaceDispatch, evregion.c:399-401) and with
the handler-object's ContextMutex held across the call
(evregion.c:429-447) β the handler sleeps inside the SMBus transfer
(SMBUS_* on ig4 sleeps in wait_status).
smbus_acpi_detach() (smbacpi.c:268-276) only calls
AcpiRemoveAddressSpaceHandler() and returns 0. Nothing waits for
in-flight dispatches:
AcpiRemoveAddressSpaceHandler(evxfregn.c:290-408) takes only ACPI_MTX_NAMESPACE, unlinks the HandlerObj and drops its last reference β the handler object and its ContextMutex β which the in-flight dispatch still holds β are freed underneath the running handler;- newbus then frees the smbacpi softc (the handler's
HandlerContext,&sc->space_handler_data) and, as the parent detaches, the ig4iic device (data->dev) that the handler'sSMBUS_*calls dispatch on; - with smbacpi built as a module,
kldunloadadditionally frees the module text the in-flight handler will resume into (the DF-5521 / DF-5563 freed-text mechanism).
On the stock kernel the resumed handler releases the freed
ContextMutex: AcpiEvAddressSpaceDispatch β AcpiOsReleaseMutex β
AcpiOsSignalSemaphore on struct acpi_semaphore memory already
returned to the M_ACPISEM zone β observed as (a) an indefinite
spin-lock wait panic and (b) slab metadata corruption.
Rig
df5582_rig.ko brings the REAL production handler up and drives it
through the REAL ACPICA field machinery:
nexus -> df5582rig0 (rig bus; bus_read_ivar answers ACPI_IVAR_HANDLE
with \_SB.DFS β what ig4_acpi gives ig4iic on
real hardware)
-> ig4iic0 (rig: fake ig4iic SMBus controller; implements
the smbus_if methods with a controllable park)
-> smbacpi0 (REAL smbacpi driver, unmodified kernel)
A crafted SSDT (df5582.dsl) is loaded at rig attach with
AcpiLoadTable():
\_SB.DFS : OpRegion(SMB0, GenericSerialBus) + I2cSerialBus Connection
Field FLD0 (AttribBytes(16)) -> BYTES read -> SMBUS_TRANS
Method TST0 (read FLD0), TST1 (BLOCK write, len 0xFF)
hw.df5582.go=1 evaluates TST0; the fake ig4iic's trans method
parks (tsleep) while the REAL smbus_acpi_space_handler is on the
stack, interpreter exited, ContextMutex held. ./trigger (ioctl
DF5582_IOC_DETACH on /dev/df5582) then tears the fake ig4iic down,
running smbus_acpi_detach() underneath the parked handler.
Note: the guest's X86_64_GENERIC has device smbacpi compiled in
(sys/config/X86_64_GENERIC:336), so the production code under test
is the in-kernel copy; the fix validation therefore rebuilds the
kernel, not just the module.
Reproduce
# stage this directory to the guest as /root/df5582, then (as root): sh build.sh # iasl SSDT + rig kld + trigger(1) sh run.sh control # sanity: real handler dispatches (count>0, AE_OK) sh run.sh race # stock kernel: panic (see below) # after vm.sh reset with-src + staging: # apply fix.diff in /usr/src, make nativekernel+installkernel, reboot sh run.sh race # fixed kernel: drain witness 0, STILL ALIVE
Expected output
Control (run.control.log):
[df5582] TRANS slave=0x10 cmd=0 op=0xc wcount=0 rcount=16 (#1) [df5582] eval done: AE_OK hw.df5582.count: 1 hw.df5582.status: 0
Race, stock kernel (run.race.stock.log + panic.txt / panic.spin.txt):
-- parked=1 (handler in-flight, interpreter exited)
-- detached=2 parked_at_detach=1 <- teardown returned while parked
panic: spin_lock_ex: AcpiOsSignalSemaphore, indefinite wait! (2 boots)
panic: assertion "(((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0"
failed in chunk_mark_free at kern_slaballoc.c:1675 (1 boot)
Race, fixed kernel (run.race.fixed.log):
-- parked=1 (handler in-flight, interpreter exited) -- detached=2 parked_at_detach=0 <- teardown WAITED (drain) -- STILL ALIVE after race (expected on the FIXED module)
parked_at_detach is sampled in-kernel at teardown completion
(console prints reorder across CPUs on DF, so this is the authoritative
ordering witness): 1 = teardown finished while the handler was still
in-flight (stock), 0 = teardown waited for it (fixed).
Bonus (not a new finding)
sh run.sh bonus (TST1) exercises the KNOWN pass-1 finding
DF-1112/DF-2332: a GSBUS BLOCK write whose buffer length byte is 0xFF
drives memcpy(buf, gsb->data, gsb->len) (smbacpi.c:207-208) with
buf[32] on the handler's stack β a 255-byte stack smash fully
controlled by AML. Not re-reported here; the rig field exists so the
known class can be re-demonstrated live if wanted.
DF-5582 β VERDICT
Status: reproduced (panic, 3/3 stock boots). Fix validated (A/B on the same rig schedule).
The finding (pass-2 lifecycle class)
smbus_acpi_detach() (sys/bus/smbus/smbacpi/smbacpi.c:268-276) is the
entire detach method:
static int
smbus_acpi_detach(device_t dev)
{
struct smbus_acpi_softc *sc = device_get_softc(dev);
smbus_acpi_remove_address_space_handler(sc);
return (0);
}
It never quiesces in-flight smbus_acpi_space_handler() dispatches. The
dispatch context is what makes this a use-after-free rather than a
benign race:
AcpiEvAddressSpaceDispatch(contrib .../events/evregion.c:399-401) exits the AML interpreter before calling non-default address-space handlers β because they may block. The GSBUS handler therefore runs unlocked and sleeping (theSMBUS_*calls into ig4 sleep inwait_status), while ordinary AML and the detach path continue.- The same dispatcher fills the shared handler context
(
info->Connection/Length/AccessLength,evregion.c:429-444) underHandlerObj->AddressSpace.ContextMutexand holds that mutex across the handler call (evregion.c:447). ACPICA protects the context this way, but nothing protects the mutex itself from removal. AcpiRemoveAddressSpaceHandler(.../events/evxfregn.c:290-408) takes only ACPI_MTX_NAMESPACE (free, the interpreter is exited), detaches the regions, unlinks the HandlerObj and drops its last reference:AcpiUtRemoveReferencefrees the HandlerObj, and its destructor deletes the ContextMutex β while the in-flight dispatch still holds that mutex. (Verified in-tree: no in-flight tracking exists anywhere on that path.)- The moment detach returns, newbus frees the smbacpi softc β which IS
the handler's
HandlerContext(&sc->space_handler_data,smbacpi.c:68,96-98) β and the parent ig4iic teardown freesdata->dev, the device every remainingSMBUS_*macro in the handler would dispatch on.
So an in-flight dispatch resumes holding a freed mutex, with its context
and bus device freed or about to be. That is exactly the
detach-without-drain defect family already verified for the siblings:
DF-5518 (gpio_acpi, same handler architecture), DF-5521/DF-5563 (iic
stack, freed-text variant).
Why the trigger is realistic (and why severity is Low)
- In-tree, only
ig4iicparents get a smbacpi child (DRIVER_MODULE(smbacpi, ig4iic, ...)atsmbacpi.c:295, ig4 adds the child atichiic/ig4_iic.c:607). ig4 detach / kldunload of the stack (or PCI hot-remove) is a root/privileged operation. - The concurrent AML side needs no attacker: on real ig4-connected
hardware, GSBUS fields are read by thermal zones, battery/
_DSMmethods and event notifications at arbitrary times. ACPI notifies are precisely the concurrent-AML source (ACPICA worker threads evaluate them with the ACPI lock model, not the caller's). - Consequence observed: kernel panic (two signatures) / heap-metadata corruption β a privileged-trigger memory-corruption DoS. Not an unprivileged escalation path: Low, consistent with the family precedent (DF-5518/DF-5521/DF-5563 all Low).
Reproduction (rig, real production code path)
The rig (df5582_rig.c + df5582.dsl) instantiates the REAL smbacpi
driver under a fake ig4iic parent whose ACPI ivar points at a planted
\_SB.DFS device, loads the crafted SSDT via AcpiLoadTable(), and
evaluates TST0 β a GSBUS AttribBytes(16) field read β via
AcpiEvaluateObject from a kernel thread. The control run proves the
production path end-to-end:
[df5582] TRANS slave=0x10 cmd=0 op=0xc wcount=0 rcount=16 (#1) [df5582] eval done: AE_OK
(op=0xc = SMB_TRANS_NOCNT|SMB_TRANS_7BIT, rcount=16 = the field's
AccessLength β exactly smbacpi.c:217-219.)
The race parks the fake controller's trans method mid-dispatch, tears
the fake ig4iic down (running the real smbus_acpi_detach underneath),
and lets the dispatch resume.
Stock kernel: reproduced 3/3 boots, two panic signatures
run.race.stock.log, panic.txt, panic.spin.txt:
-- parked=1 (handler in-flight, interpreter exited) -- detached=2 parked_at_detach=1
Boot A and B (freed-ContextMutex release, deterministic):
panic with 1 spinlocks held panic: spin_lock_ex: AcpiOsSignalSemaphore, indefinite wait! cpuid = 5 _spin_lock_contested _spin_lock_contested AcpiOsSignalSemaphore <- signalling the FREED ContextMutex AcpiEvAddressSpaceDispatch <- evregion.c:447 (ContextMutex release) AcpiExAccessRegion AcpiExReadSerialBus <- the GSBUS field read (TST0) ... AcpiEvaluateObject df5582_eval_thread
Boot C (heap metadata corruption β the UAF write into freed M_ACPISEM memory tripping the slab garbage collector):
panic: assertion "(((intptr_t)chunk ^ (intptr_t)z) & ZoneMask) == 0"
failed in chunk_mark_free at /usr/src/sys/kern/kern_slaballoc.c:1675
chunk_mark_free -> slab_cleanup -> slotimer_callback -> softclock_handler
(On boot C the freed semaphore's memory had been reused, so
AcpiOsSignalSemaphore wrote through the dangling pointer into a live
slab chunk β the corruption was detected asynchronously. Two independent
manifestations of the same underlying free-under-running-handler.)
Fixed kernel: validated
fix.diff (authored after reproduction, never applied to the read-only
sys/ tree) adds the DF-5518-style drain to smbacpi: a busy count +
detaching flag in struct acpi_i2c_handler_data; the handler refuses
new dispatches once detaching and accounts in-flight ones; detach sets
the flag, drains in-flight dispatches, and only then removes the
handler (the order matters: AcpiRemoveAddressSpaceHandler itself frees
the ContextMutex).
Applied in the guest /usr/src, kernel rebuilt
(make nativekernel KERNCONF=X86_64_GENERIC, kern.version
#1: Thu Oct 8 02:57:28 UTC 2026), rebooted, identical rig schedule:
-- parked=1 (handler in-flight, interpreter exited) -- detached=2 parked_at_detach=0 <- teardown WAITED for the handler -- STILL ALIVE after race (expected on the FIXED module)
parked_at_detach is sampled in-kernel at teardown completion (immune
to DF's cross-CPU console reordering): stock = 1 (teardown returned
while the handler was still parked β the frees happened under it β
panic), fixed = 0 (teardown blocked until the dispatch completed on live
objects β nothing to corrupt). Guest survived; the same eval returned
AE_OK afterwards.
Rig caveats documented for honesty: (a) DF's SYSCTL_XLOCK takes every
per-CPU gd_sysctllock (kern_sysctl.c:1642-1650), so the teardown must
be triggered from /dev/df5582 ioctl, not a sysctl handler β doing it
from a sysctl wedges the sysctl tree (rig artifact, unrelated to the
finding; production kldunload holds no sysctl locks); (b) the guest
kernel has device smbacpi compiled in, so the fix had to be validated
with a kernel rebuild β an earlier module-only install silently changed
nothing (caught by the in-kernel ordering witness).
Exploit chain (why this is not uid0)
Trigger is privileged (driver teardown) β this is a privileged-user
memory-corruption/panic, not an unprivβroot path. The AML-controlled
inputs that reach this handler (connection templates, lengths) are all
bounded by ACPICA's fixed per-protocol buffers (see negative results) β
the only AML-driven memory-safety bug in this file remains the known
DF-1112/DF-2332 buf[32] class.
Negative results (classes hunted and killed)
- GSBUS destination writes: ACPICA sizes the Value buffer per
protocol (
AcpiProtocolLengths[]+ 2: SEND_RECEIVE/BYTE=3, WORD=4, BLOCK/BYTES=257;exfield.c:170-188,exserial.c:320-355,466-500). Everygsb->...write in the handler (smbacpi.c:169,180,192,204-205, 221,236) lands inside those fixed bounds β no AML-declared field-size dependence. Killed. - BLOCK read OOB: ig4 (the only in-tree parent) caps the device
count byte at the requested 32 (
ichiic/ig4_iic.c:474-475:if (rcount > (u_char)last) rcount = (u_char)last;with rcount preloaded 32) β no in-tree read-side overflow. Killed. - Unsupported protocol attributes (QUICK/PROCESS_CALL/
BLOCK_PROCESS_CALL/RAW_BYTES/RAW_PROCESS): rejected with
AE_BAD_PARAMETER (
smbacpi.c:229-233). Killed. - WORD byte-order: ig4 assembles SMBus words LSB-first and the
handler stores LE (
ig4_iic.c:888/smbacpi.c:187-196) β wire-order round-trip is consistent; no swap bug. Killed. - Shared-context race: concurrent dispatches are serialized by
ACPICA's ContextMutex (always created at install,
evhandler.c:680-695) β no cross-field info race. Killed. - iiconf ownership classes (DF-5563/5564) applied to this consumer:
smbacpi issues only single-shot
SMBUS_*method calls; ig4 serializes each internally (lwkt_serialize_enter(&sc->slz)); no multi-call bus ownership sequences, no waiters parked on any bus lock. Not applicable β clean. Address/BitWidth: u64βchar cmdtruncation only, no memory effect (smbacpi.c:177-226); BitWidth unused. Killed.- Install-failure paths: attach ignores install failure (cosmetic);
AcpiBufferToResourcefailure paths free correctly (smbacpi.c:143- 149,238-239). Killed.
Fix
fix.diff β drain in-flight GSBUS dispatches in smbus_acpi_detach
before AcpiRemoveAddressSpaceHandler (busy/detaching accounting in
struct acpi_i2c_handler_data, 30 s bounded wait; handler aborts new
dispatches once detaching). Validated live (witness 1β0, panic gone,
guest alive).
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
fixedBaseline (stock kernel): parked_at_detach=1 and panic in 3/3 boots (AcpiOsSignalSemaphore indefinite-wait x2, chunk_mark_free slab assertion x1). Patched (fix.diff applied to guest /usr/src, kernel rebuilt as #1 Oct 8 02:57:28 UTC 2026, rebooted): identical rig schedule, parked_at_detach=0 β smbus_acpi_detach waited for the parked dispatch to complete before removing the handler β no panic, guest alive, eval returned AE_OK.
run.race.stock.log vs run.race.fixed.log (parked_at_detach 1 -> 0); panic.txt + panic.spin.txt (baseline panics); fix.diff; VERDICT.md 'Fixed kernel' section
Confirmed kernel references
- sys/bus/smbus/smbacpi/smbacpi.c:268-276
- sys/bus/smbus/smbacpi/smbacpi.c:120-141
- sys/bus/smbus/smbacpi/smbacpi.c:89-103
- sys/contrib/dev/acpica/source/components/events/evregion.c:399-447
- sys/contrib/dev/acpica/source/components/events/evxfregn.c:290-408
- sys/contrib/dev/acpica/source/components/events/evhandler.c:680-695
- sys/dev/acpica/Osd/OsdSynch.c:80-121
- sys/bus/smbus/ichiic/ig4_iic.c:607
Detail
Evidence (decisive lines)
panic.txt (slab-corruption panic: chunk_mark_free assertion, kern_slaballoc.c:1675); panic.spin.txt (spin panic: 'panic: spin_lock_ex: AcpiOsSignalSemaphore, indefinite wait!' with the AcpiExReadSerialBus->AcpiEvAddressSpaceDispatch->AcpiOsSignalSemaphore trace); run.race.stock.log ('-- detached=2 parked_at_detach=1'); run.race.fixed.log ('-- detached=2 parked_at_detach=0' + 'STILL ALIVE'); run.control.log (real-handler dispatch proof: 'TRANS slave=0x10 cmd=0 op=0xc rcount=16'); VERDICT.md (full trace + negative results); fix.diff (drain patch)
PoC changes
Replaced the pass-1-style sketch entirely: rig kld instantiates the REAL smbacpi under a fake 'ig4iic' device whose bus_read_ivar answers ACPI_IVAR_HANDLE (what ig4_acpi provides on hardware), loads a crafted SSDT via AcpiLoadTable (GSBUS opregion + I2cSerialBus Connection + AttribBytes(16)/AttribBlock fields + TST0/TST1), evaluates TST0 from a kthread, parks the fake SMBUS_TRANS mid-dispatch, and tears the device down via /dev/df5582 ioctl (sysctl-triggered teardown deadlocks on DF's per-CPU sysctl locks β rig artifact). In-kernel parked_at_detach witness added because DF console prints reorder across CPUs. Fix validation required a kernel rebuild (guest X86_64_GENERIC has device smbacpi compiled in; a module-only install silently changed nothing β caught by the witness).
Verified recommended fix
Drain in-flight GSBUS dispatches in smbus_acpi_detach before removing the handler: busy/detaching accounting in struct acpi_i2c_handler_data, handler refuses new dispatches once detaching, detach waits for busy==0 then calls AcpiRemoveAddressSpaceHandler (fix.diff).
Verdict
smbus_acpi_detach() (sys/bus/smbus/smbacpi/smbacpi.c:268-276) removes the GSBUS address-space handler without quiescing in-flight smbus_acpi_space_handler() dispatches, which ACPICA runs with the interpreter exited and the handler-object ContextMutex held. AcpiRemoveAddressSpaceHandler frees the HandlerObj and that ContextMutex under the running dispatch, and newbus then frees the softc (the handler Context) and the parent ig4iic device. Reproduced 3/3 stock boots with a rig driving the real production path (fake ACPI-ivared ig4iic parent + AcpiLoadTable SSDT + AcpiEvaluateObject): two AcpiOsSignalSemaphore indefinite-wait panics (freed ContextMutex release, trace through AcpiEvAddressSpaceDispatch/AcpiExReadSerialBus) and one chunk_mark_free slab assertion (heap metadata corruption from the write through the freed semaphore). Root-gated trigger (ig4/smbacpi teardown racing concurrent AML), so Low severity, memcorrupt family β same defect class as verified DF-5518 (gpio_acpi) and DF-5521/DF-5563 (iic). fix.diff (busy/detaching drain before handler removal) validated with an in-kernel ordering witness: stock parked_at_detach=1 + panic; fixed kernel parked_at_detach=0, drain held, guest alive, identical schedule.
No comments yet.