# DF-5119 — Unvalidated user-controlled allocation sizes in /dev/efi ioctls (efidev.c)

## What

`efidev_ioctl()` (sys/dev/misc/efirt/efidev.c) passes the user-supplied
`struct efi_var_ioc` sizes straight into `kmalloc(..., M_TEMP, M_WAITOK)`
with **no upper-bound validation**, in all three variable ioctls:

| ioctl | line | call |
|---|---|---|
| `EFIIOC_VAR_GET`  | efidev.c:105 | `data = kmalloc(ev->datasize, ...)` |
| `EFIIOC_VAR_GET`  | efidev.c:106 | `name = kmalloc(ev->namesize, ...)` |
| `EFIIOC_VAR_NEXT` | efidev.c:140 | `name = kmalloc(ev->namesize, ...)` |
| `EFIIOC_VAR_SET`  | efidev.c:165 | `data = kmalloc(ev->datasize, ...)` |
| `EFIIOC_VAR_SET`  | efidev.c:166 | `name = kmalloc(ev->namesize, ...)` |

Two failure modes, both proven on the guest:

1. **Huge, non-wrapping size** (e.g. `datasize = 0x4000000000000000`):
   `_kmalloc()` takes the large-allocation path (kern_slaballoc.c:942),
   `kmem_slab_alloc()` → `vm_map_findspace()` fails, and because `M_WAITOK`
   was given *without* `M_NULLOK` the kernel **panics outright**:
   `panic("kmem_slab_alloc(): kernel_map ran out of space!")`
   (kern_slaballoc.c:1721-1724).

2. **Size near `SIZE_MAX`** (e.g. `0xFFFFFFFFFFFFFFFF`): `round_page()`
   (kern_slaballoc.c:945) **wraps to 0**, so `kmem_slab_alloc(0, ...)` runs:
   `vm_map_findspace(len=0)` succeeds, `vm_map_insert` inserts a
   **zero-length entry into kernel_map**, and the immediately following
   `vm_map_set_wired_quick()` detects the corrupted map:
   `panic("vm_map_entry_link: dup addr map ...")`. (Even if the insert
   survived, the returned pointer has zero pages mapped and `*kup = 0`,
   so the subsequent `kfree()` at efidev.c:184 would take the SLZone path
   (kern_slaballoc.c:1475-1478) on a zone-less, unmapped pointer.)

The allocations happen **before** any EFI runtime call, so the bug is
reachable even on legacy-BIOS systems: `kldload efirt` reports
`module_register_init: MOD_LOAD (efirt, ...) error 6` but the co-packaged
`efidev` module still registers and creates `/dev/efi`.

## Threat model

`/dev/efi` is created `0700 root:wheel` (efidev.c:205-206), so the trigger
requires root (or a setuid-root helper proxying the ioctl; jails/devfs rules
could also expose the node). Impact: deterministic kernel panic / kernel-map
corruption from one `ioctl(2)` — privileged-user DoS and a violation of the
kernel's own allocator invariants (same privilege calibration as DF-2121).

## Reproduce (guest, as root)

```sh
kldload efirt            # /dev/efi appears even on BIOS boxes
cc -O -o efi_sz efi_sz.c
./efi_sz 0               # control: ENXIO, guest survives
./efi_sz 1               # panic: kmem_slab_alloc(): kernel_map ran out of space!
# (guest reboots/dies; reset, kldload efirt again)
./efi_sz 2               # panic: vm_map_entry_link: dup addr map ...
```

## Fix

`fix.diff` (validated on guest): reject `datasize/namesize > 1 MiB` and
`namesize < sizeof(efi_char)` in all three VAR ioctls before any allocation
(1 MiB matches Linux efivarfs' cap; real EFI variables are far smaller).
Patched module: both modes return `EINVAL`, control mode still `ENXIO`,
guest survives. The `namesize >= sizeof(efi_char)` clause also closes the
DF-2121 underflow as defense in depth.

## Files

- `efi_sz.c` — PoC (modes 0/1/2) · `build.sh` / `run.sh`
- `build.log` — guest build + mode-0 control
- `run.log` — baseline transcript (both panics)
- `panic.txt`, `panic.m1.txt`, `panic.m2.txt` — serial-console panic excerpts
- `fix_build.log`, `fix_run.log` — patched-module build + rerun (EINVAL, alive)
- `fix.diff` — the fix (applied to guest /usr/src for validation only)
- `verdict.json`, `manifest.json`, `VERDICT.md`, `env.txt`
