# DF-5179 — atkbd(4) ISA attach: unchecked IRQ resource allocation → boot-time kernel panic

- **File:** `sys/dev/misc/atkbd/atkbd_isa.c`
- **Severity:** Low (boot-time DoS; trigger requires privileged config / resource exhaustion)
- **Confidence:** certain (source-proven panic path)
- **Verification:** source-confirmed only — trigger is boot-time and gated on
  kernel-config/loader hints (root-only) or ISA IRQ rman exhaustion; there is
  no unprivileged runtime entry point to exercise in the QEMU guest, so no
  guest run was attempted (`verdict.json` status `untested`, attempts 0).

## Root cause

`atkbdattach()` (`sys/dev/misc/atkbd/atkbd_isa.c:118-124`) allocates the
legacy IRQ resource and hands it straight to `BUS_SETUP_INTR()` without any
NULL check:

```c
rid = 0;
sc->intr = bus_alloc_legacy_irq_resource(dev, &rid, irq, RF_ACTIVE);
BUS_SETUP_INTR(device_get_parent(dev), dev, sc->intr, INTR_MPSAFE,
               atkbd_isa_intr, kbd, &sc->ih, NULL, NULL);
```

Three things go wrong when the allocation fails:

1. `sc->intr == NULL` is forwarded down the bus tree
   (atkbdc → `bus_generic` → isa → nexus) and `nexus_setup_intr()` **panics
   on NULL by design** — `sys/platform/pc64/x86_64/nexus.c:505-506`:

   ```c
   /* somebody tried to setup an irq that failed to allocate! */
   if (irq == NULL)
       panic("%s: NULL irq resource!", __func__);
   ```

   A driver-caused NULL irq is supposed to be a driver bug caught before
   this point; atkbd_isa is exactly such an unchecked driver.

2. Even for the non-NULL late-failure path (`register_int()` returns NULL →
   `EINVAL`), the `BUS_SETUP_INTR()` return value is ignored, the irq
   resource is leaked, and `atkbdattach()` still `return 0` — the driver
   reports a healthy attach with no interrupt plumbed.

3. `atkbdresume()` (`atkbd_isa.c:141`) later calls
   `rman_get_start(sc->intr)` which dereferences the resource pointer
   (`(r)->r_start`) — a second NULL-deref site if the attach bug is ever
   fixed by ignoring the error instead of failing the attach.

## Trigger path

`irq` comes from the parent atkbdc ivar. When no `hint.atkbd.N.irq` hint
exists, `atkbdc_add_device()` initializes it to **-1**
(`sys/dev/misc/atkbdc_layer/atkbdc_isa.c:221-224`), which is passed as
`(u_long)-1` to `bus_alloc_legacy_irq_resource()` →
`BUS_ALLOC_RESOURCE(..., start = ULONG_MAX, end = ULONG_MAX, ...)` → the ISA
IRQ rman cannot satisfy the range → NULL → panic above. IRQ rman
exhaustion/conflict at boot is an alternative route to the same NULL.

Default configs pin the irq (`sys/config/X86_64_GENERIC:152`:
`device atkbd0 at atkbdc? irq 1`), so the panic needs a custom kernel
config / `/boot/loader.conf` hints (root-only) or IRQ pressure — hence Low.

## In-tree reference for the correct pattern

The sibling driver on the same parent does it right
(`sys/dev/misc/psm/psm.c:2014-2022`):

```c
sc->intr = bus_alloc_legacy_irq_resource(dev, &rid, irq, RF_ACTIVE);
if (sc->intr == NULL) {
        return (ENXIO);
}

error = BUS_SETUP_INTR(device_get_parent(dev), dev, sc->intr,
                INTR_NOPOLL, psmintr, sc, &sc->ih, NULL, NULL);
if (error)
        goto out;
```

## Fix

`fix.diff` in this directory — NULL-check the allocation (return `ENXIO`),
check the `BUS_SETUP_INTR()` result, unwind the resource on failure.
Not applied to `sys/` (audit tree is read-only); not guest-validated
(`fix_status: not_testable` — trigger is boot-config-gated).

## Artifacts

- `VERDICT.md` — this narrative
- `trigger.txt` — the privileged config that triggers the panic
- `fix.diff` — the verified-by-inspection fix
- `manifest.json`, `verdict.json`
