# DF-4410 — acpi_asus LED state changes run in an undrained async task: detach/led_destroy frees the softc under a pending acpi_asus_led_task (use-after-free)

- File: `sys/dev/acpica/acpi_asus/acpi_asus.c` (+ `sys/dev/misc/led/led.c` interplay)
- Class: CWE-416 (use after free) / CWE-362 (race window)
- Severity: Low — the write side is root-only (`/dev/led/*` are created 0600
  root:wheel, `led.c:305-306`), and the free side requires root (device
  detach / module unload). No unprivileged path.

## Defect (path:line)

LED write path (root writes `/dev/led/bled` etc., or `led_create_state` at
attach calls `sc->func(...)` directly, `led.c:312`):

```c
acpi_asus_led()            acpi_asus.c:922-935
        if (led->busy)             /* racy check, no lock */
                return;
        led->busy = 1;
        led->state = state;
        AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_asus_led_task, led);  /* async */
```

The task (`acpi_asus.c:877-920`) later dereferences `led->sc`,
`sc->model->bled_set`, `sc->handle` and calls `acpi_SetInteger`, then
`led->busy = 0`.

Nothing drains that pending task at detach:

```c
acpi_asus_detach()   acpi_asus.c:832-874
        led_destroy(sc->s_bled.cdev);   /* frees the ledsc immediately;
                                           led.c:318-341 has no OSL-queue
                                           flush and does not know about the
                                           acpi_asus_led_task */
        ...
        sysctl_ctx_free(&sc->sysctl_ctx); /* softc itself freed by newbus
                                             right after detach returns */
```

The tree's ACPICA OSL has no `AcpiOsWaitEvents()`-style drain
(`sys/dev/acpica/Osd/OsdSchedule.c` only implements `AcpiOsExecute`), so a
task queued microseconds before `led_destroy` runs after both the `ledsc`
and the `acpi_asus_softc` are freed → `led->sc` / `sc->model->bled_set` /
`acpi_SetInteger(sc->handle, ...)` all operate on freed memory → UAF read +
AML engine call with attacker-uncontrolled-but-recycled pointers.

Secondary: `led->busy` check/set (`acpi_asus.c:928-931`) is unlocked, so two
concurrent LED writes can both queue a task (benign duplication, but shows
the glue has no serialization at all); and the `default:` arm of
`acpi_asus_led_task` (`acpi_asus.c:912-915`) returns without clearing
`led->busy`, permanently wedging that LED.

## Build / run

Not runnable on the audit guest: no `/dev/led` consumers exist (no ASUS
nodes — `env.txt`), and both sides of the race are root-only. On matching
hardware: `echo 1 > /dev/led/bled & kldunload acpi_asus` in a loop → panic
in `acpi_asus_led_task` on freed softc.

## Recommended fix

Track outstanding LED tasks in the softc and drain them in detach before
`led_destroy` (dying flag + counter + wait), see `fix.diff`.
