# DF-5541 — gpio_intel teardown methods trust the cookie: stale/double `GPIO_FREE_INTR` reaches the backend with `pin == -1` → `uint16_t` wrap to 65535 → OOB KVA read+write ~4.5MB past the BAR mapping (KKASSERT panic on INVARIANTS kernels)

**Target:** `sys/bus/gpio/gpio_intel/gpio_intel.c` (GPIO method layer; backend:
`sys/bus/gpio/gpio_intel/gpio_cherryview.c`)
**Class:** missing input validation on a kernel-internal API → OOB MMIO
register access (memory corruption primitive) / KKASSERT panic
**Severity:** Info (defense-in-depth; trigger requires kernel-internal
cookie misuse — root KLD today, driver-consumer bug / DF-5518-class teardown
race in the field)
**Bucket:** memcorrupt (primitive) / base:hw (reachability)

## Reproduce

```sh
cd findings/poc/DF-5541
sh run.sh            # boots the DF-5518 planted-ACPI rig, builds gfree2.ko,
                     # kldloads it: CONTROL (documented alloc/teardown/free)
                     # passes, then the second GPIO_FREE_INTR on the stale
                     # cookie fires the defect
```

Expected on the stock guest kernel (INVARIANTS build):

```
gfree2[CONTROL]: documented order done; map5 now: pin=-1 intidx=-1 (freed cleanly)
gfree2[DEFECT]: calling GPIO_FREE_INTR on the stale cookie again (map->pin == -1)...
panic: assertion "gpio_intel_pin_exists(sc, map->pin)" failed in gpio_intel_free_intr at /usr/src/sys/bus/gpio/gpio_intel/gpio_intel.c:256
```

guest dead in DDB (`panic.txt`, `run.log`).

On a production (non-INVARIANTS) kernel the KKASSERT is compiled out
(sys/sys/systm.h:98/118) and the same call proceeds into
`gpio_cherryview_unmap_intr` (gpio_cherryview.c:380-414) whose
`uint16_t pin = map->pin;` maps -1 → 65535, so the register restore
reads `PIN_CTL1(65535)` = resource KVA + 0x448804 and conditionally writes
`PIN_CTL0(65535)`/`PIN_CTL1(65535)` = KVA + 0x448800/0x448804 — ~4.5MB past
the 0x8000-sized `pmap_mapdev()` mapping (harness prints the exact KVA and
offsets; see `run.log` / `run.fixed.log` header lines).

## Fix validation

`fix.diff` replaces the KKASSERTs in `gpio_intel_free_intr` /
`gpio_intel_setup_intr` / `gpio_intel_teardown_intr` with real
`map->pin < 0 || !pin_exists()` guards, closes the `*cookiep` contract hole
in `gpio_intel_alloc_intr` (backend success without a registered map now
returns ENXIO instead of success-with-uninitialized-cookie), and rejects
pins > 0xFFFF in `gpio_intel_alloc_io_pin` (the `pin_exists()` parameter
truncates `u_int` → `uint16_t`).

Applied in the guest's `/usr/src` copy, kernel rebuilt
(`fix_build.log`, `BUILD-RC=0`), rebooted (`#1: Wed Oct 7 07:32:27 UTC 2026`),
same `gfree2.ko` re-loaded — **no panic, guest alive**:

```
gfree2[DEFECT]: calling GPIO_FREE_INTR on the stale cookie again (map->pin == -1)...
gpio_intel0: gpio_intel_free_intr: stale cookie (pin -1 not mapped)
```

(`run.fixed.log`; the harness's unconditional "SURVIVED" print follows the
kernel guard line, which proves `unmap_intr` never ran.)

## Files

* `gfree2.c`, `Makefile.gfree2` — production-code harness (stock kernel)
* `build.sh` / `run.sh` — exact build & run commands
* `build.log` — kmod build output; `fix_build.log` — patched-kernel build
* `run.log` — decisive baseline console (boot + harness + panic)
* `run.fixed.log` — patched-kernel run (guard fires, no panic)
* `panic.txt` — panic signature + backtrace
* `env.txt` — guest uname
* `gpio_chain.bin` — planted ACPI chain (copied from DF-5518)
* `fix.diff` — the verified fix (never applied to the audit tree's `sys/`)
