# DF-5841 — mapped_ioctl() stack-buffer overread primitive (IOC_VOID|IOC_OUT with len>128)

## What

`sys/kern/sys_generic.c:mapped_ioctl()` sizes the ioctl scratch buffer by a
condition that skips the allocation for any command with the `IOC_VOID`
(0x20000000) direction bit:

```c
:668	size = IOCPARM_LEN(com);
:669	if (size > IOCPARM_MAX) { error = ENOTTY; goto done; }
:674	if ((com & IOC_VOID) == 0 && size > sizeof(ubuf.stkbuf)) {
:675		memp = kmalloc(size, M_IOCTLOPS, M_WAITOK);
:676		data = memp;
:677	} else {
:678		memp = NULL;
:679		data = ubuf.stkbuf;          /* 128 bytes on the kernel stack */
:680	}
:681	if (com & IOC_VOID) {
:682		*(caddr_t *)data = uspc_data;  /* only 8 bytes initialized */
```

but the copy-back to userland is guarded only by direction/size:

```c
:729	if (error == 0 && (com & IOC_OUT) != 0 && size != 0)
:730		error = copyout(data, uspc_data, (size_t)size);
```

A command with **both `IOC_VOID` and `IOC_OUT`** (or `IOC_VOID|IOC_IN|IOC_OUT`)
set and `IOCPARM_LEN` in (128, 8191] therefore makes the kernel `copyout` up
to 8191 bytes from a 128-byte kernel-stack buffer — an up-to-~8 KB adjacent
kernel stack disclosure (return addresses, saved registers, adjacent frames).
For sizes ≤ 128 the same command shape leaks the uninitialized remainder of
`ubuf.stkbuf` (the bzero at :691-696 is only reached when `IOC_VOID` is clear).
Because `com` is the raw syscall argument, any unprivileged user can issue the
crafted command.

## Reachability gate — and why this is filed as hardening

The copyout only executes when the underlying `fo_ioctl()` returns **0** for
the crafted command.  Every `fo_ioctl` reachable with default devices switches
on exact ioctl constants and returns ENOTTY/EINVAL for anything else
(vn_ioctl vfs_vnops.c:983, pipe_ioctl sys_pipe.c:993, soo_ioctl
sys_socket.c:140+, kqueue_ioctl kern_event.c:1660, ttioctl tty.c:853 — the
IOCGROUP('i'/'r') socket branches likewise dispatch to exact-constant
switches in ifioctl/rtioctl).

The included scan (`ioctl_scan.c`) swept 11 unprivileged fd types × 256
groups × 2 numbers × 2 direction combos (58,496 crafted ioctls) on the audit
guest (kernel-only config: kernel + ehci.ko + xhci.ko): **0 successes**.

So the code defect is certain, the disclosure primitive is real, but it is
currently gated behind "some fo_ioctl returns 0 for an unknown/masked
command".  One future permissive driver turns it into an ~8 KB kernel stack
disclosure.  Filed as **Info / hardening**, verdict `not_reproduced`
(reachability-gated on this configuration).

## Reproduce

```
build.sh   # cc -O -o /tmp/ioctl_scan /tmp/ioctl_scan.c   (on the guest)
run.sh     # /tmp/ioctl_scan; echo EXIT=$?
           # expected (this config): "done, hits=0", exit 0
           # a HIT line + hexdump would be the leak
```
