# DF-5104 — gpio_led.c `led_attach()` kstrdup() of GPIOATTACH consumer arg
(integer-as-pointer dereference + unterminated 32-byte string over-read)

## What

`led_attach()` treats the GPIOATTACH `consumer_arg` unconditionally as a
NUL-terminated C string:

* `sys/dev/misc/gpio/gpio_led.c:193` — `sc->name = kstrdup((char *)arg, M_LED);`

But the gpio master ioctl delivers `arg` as a **raw integer cast to a
pointer** whenever `arg_type == GPIO_TYPE_INT`:

* `sys/dev/misc/gpio/gpio.c:473-477` — `(gpaa->arg_type == GPIO_TYPE_INT) ? ((void *)gpaa->consumer_arg.lint) : (gpaa->consumer_arg.string)`

The only guard in `led_attach` is `arg == NULL` (gpio_led.c:186-187), i.e.
`lint == 0`. Any other integer reaches `kstrdup()` → `strlen()` on an
attacker-chosen kernel address.

## Impact (on a system where the misc/gpio framework is live)

* `lint = 1` (or any small non-zero value) → `strlen((char *)1)` → guaranteed
  kernel page fault / panic. Trivial root-triggered DoS.
* `lint = <mapped kernel address>` → `kstrdup()` copies kernel memory as a
  string; that string is then published **world-readably** twice:
  * as the devfs node name `make_dev(..., "led/%s", sc->name)`
    (gpio_led.c:206-207) — anyone can `ls /dev/led/`;
  * via `kprintf("gpio_led: Attached led '%s' ...")` (gpio_led.c:211-212) to
    the console / message buffer.
  → kernel-memory disclosure (as a NUL-terminated string) from a root ioctl.
* String path: `consumer_arg.string[32]` (gpio.h:119) need not be
  NUL-terminated. DFly `mapped_ioctl()` stages the 64-byte ioctl argument in
  a 128-byte **kernel stack buffer** (`sys/kern/sys_generic.c:572-574`,
  `674-679`) with only 64 bytes initialized — `kstrdup()` walks past the
  struct into stale kernel stack and leaks it into the node name/console the
  same way (CWE-170 / CWE-125).

## Preconditions (why Low, not High)

* ioctl on `/dev/gpio/<ctrl><unit>/master` — mode 0600 root:wheel
  (gpio.c:574) → **root only**.
* Kernel must be built with the misc/gpio framework: `device gpio`
  (sys/conf/files:2119-2120) — **not** in `X86_64_GENERIC` (that kernel has
  only the unrelated `bus/gpio` `gpio_acpi`/`gpio_intel` framework,
  sys/config/X86_64_GENERIC:332-333).
* A registered controller must exist — in-tree that is only `nsclpcsio(4)`
  (sys/conf/files:2599), PC87366 Super I/O GPIO hardware.

## Reproduce

```
./build.sh                       # cc -Wall -o gpioled_int_kstrdup gpioled_int_kstrdup.c
./run.sh                         # as root on a gpio-equipped system
                                 # -> expect immediate kernel panic (strlen @ addr 1)
```

`run.sh` takes an optional master-node path (default
`/dev/gpio/pc873660/master`, the node nsclpcsio unit 0 would create).

## What happened on the audit guest (2026-09-27)

Built and ran the trigger as unprivileged `maxx`: **no `/dev/gpio` and no
`/dev/led` exist** — the guest kernel (`uname`: X86_64_GENERIC build) does
not compile the misc/gpio framework at all and QEMU does not emulate the
PC87366 GPIO logical device, so the kernel code path has no entry point on
this guest. See `build.log`, `run.log`, `env.txt`. Verdict:
**inconclusive** (trigger exercised, kernel path unreachable in this
environment; the defect itself is statically certain).
