# VERDICT — DF-5104

**Status: inconclusive** (runtime entry point absent on the audit guest;
defect statically certain) · **Severity: Low** (root-only, non-GENERIC
kernel config + specific Super I/O hardware) · **Confidence: certain**
(code-path proof below is 6 lines long and fully type-checkable).

## Reproduced?

Not on this guest — and provably not *possible* on this guest:

* `run.log`: `open("/dev/gpio/pc873660/master")` → ENOENT; `/dev/gpio` and
  `/dev/led` do not exist.
* `env.txt`: guest kernel is a stock `X86_64_GENERIC` build;
  `/usr/src/sys/config/X86_64_GENERIC` contains `device gpio_acpi` /
  `device gpio_intel` (the separate `bus/gpio` newbus framework) and **no**
  `device gpio`, **no** `device nsclpcsio`.
* `sys/conf/files:2119-2120` compiles `dev/misc/gpio/{gpio,gpio_led}.c` only
  when `optional gpio` is configured → gpio_led.c is not even in the guest
  kernel image.
* The only in-tree controller registering a `struct gpio` is
  `nsclpcsio_isa.c:365-372` (PC87366 Super I/O); QEMU does not emulate its
  GPIO logical device, so even a rebuilt kernel would expose no master node.

A custom kernel (`device gpio` + `device nsclpcsio`) on PC87366 hardware —
or any out-of-tree controller driver using this framework — makes the path
live: `GPIOATTACH(arg_type=GPIO_TYPE_INT, lint=1)` ⇒ panic in
`strlen()/kstrdup()`.

## Static proof (path:line)

1. `sys/kern/sys_generic.c:674-686` — ioctl data copyin'd into kernel
   buffer (`ubuf.stkbuf[128]` on the caller's kernel stack, or kmalloc'd
   `M_IOCTLOPS` if bigger; GPIOATTACH is 64 bytes ⇒ stack buffer).
2. `sys/dev/misc/gpio/gpio.c:471-478` — `case GPIOATTACH:` calls
   `gpio_consumer_attach(gpaa->consumer_name,
       (gpaa->arg_type == GPIO_TYPE_INT) ? ((void *)gpaa->consumer_arg.lint)
                                         : (gpaa->consumer_arg.string), ...)`.
   The consumer API (`gpio.h:103`) passes `void *arg` with **no arg_type
   discriminator** — a consumer cannot tell integer from string.
3. `sys/dev/misc/gpio/gpio.c:112-117` — the registered consumer is found
   (`consumer_name == "led"`) and `gcp->consumer_attach(gp, arg, pin, mask)`
   is invoked.
4. `sys/dev/misc/gpio/gpio_led.c:186-187` — only guard: `if (arg == NULL)
   return 1;` (rejects `lint == 0` only).
5. `sys/dev/misc/gpio/gpio_led.c:193` — `sc->name = kstrdup((char *)arg,
   M_LED);` → `strlen()` on the integer-as-pointer (panic for small values,
   kernel-memory-as-string copy for mapped addresses).
6. Publication of the copied string: `gpio_led.c:206-207`
   (`make_dev(..., "led/%s", sc->name)` — world-listable devfs node) and
   `gpio_led.c:211-212` (`kprintf("gpio_led: Attached led '%s' ...")`).
7. Unterminated-string variant: `sys/dev/misc/gpio/gpio.h:119`
   (`char string[32]` inside the 64-byte ioctl struct staged in the 128-byte
   stack buffer) — a 32-byte non-NUL `string` makes `kstrdup()` read stale
   kernel stack past offset 64 (CWE-170 → CWE-125).

Not this finding (known, pass 1): DF-2112 (led_attach gpio_map-fail leak),
DF-2113 (unimplemented led_detach UAF family).

## Why the value/write path is NOT a finding (killed)

* `led_write` bounds: `uio_resid > sizeof(int)` unsigned compare makes
  negative residuals EINVAL (gpio_led.c:115-116); `uiomove` copies ≤ 4 bytes
  into a 4-byte `int` (gpio_led.c:120) — no overflow, no OOB.
* Derived value ∈ [-128, 207] (`gpio_led.c:124-128`) is passed raw to
  `gpio_pin_write()` (gpio_led.c:131) unlike `gpio_write()` which validates
  `GPIO_PIN_LOW|HIGH` (gpio.c:356-357) — but the only in-tree controller
  treats every value ∉ {0,1} as a no-op (`nsclpcsio_isa.c:608-611:
  if (value == 0) ... else if (value == 1)`), so no memory-safety or
  register-corruption consequence exists in-tree (Info-level hardening only).
* Map range: `gpio_map()` validates `offset + i` against `gp->npins` and
  rejects mapped/opened pins (gpio.c:211-226); LED maps are size 1
  (`mask=1`, gpio_led.c:197) so `map->map[0]` (gpio_led.c:131,152,237) is
  always in bounds.
* No hints parsing exists anywhere for gpio consumers (attach is
  GPIOATTACH-only), no callout/blink machinery, `led_ioctl` is a no-op
  (gpio_led.c:162-169).
* Lock order: nesting is one-way `gpiodev_lock → gpio_lock → led_lock`;
  the theoretical `led_lock → gpio_map → gpio_lock` inversion is defused
  because `led_attach` is only ever entered with `gpio_lock` already held
  and `gpio_map()`'s lockstatus() probe (gpio.c:196-199) then skips
  re-acquisition. Latent fragility, not a reachable deadlock.

## Fix

Two parts (framework + consumer). See `fix.diff`.

`code_hash` (final trigger source):
`1ab846571d9969a91d211e2c36c3c216926df2885dcf3c29749e9a3eae5c983b`

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
