# 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's `SMBUS_*` calls dispatch on;
* with smbacpi built as a module, `kldunload` additionally 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.
