# DF-2830 — Off-by-one NUL clobber in `sysctl_vm_zone` name padding → kernel-stack OOB read in unprivileged `sysctl vm.zone` output

## What

`sysctl_vm_zone()` pads zone names into a 14-byte stack buffer
(sys/vm/vm_zone.c:815-841):

```c
char tmpname[14];
len = strlen(curzone->zname);
if (len >= (sizeof(tmpname) - 1))       /* len >= 13 */
        len = (sizeof(tmpname) - 1);    /* len = 13 */
for (i = 0; i < sizeof(tmpname) - 1; i++)
        tmpname[i] = ' ';
tmpname[i] = 0;                          /* tmpname[13] = NUL */
memcpy(tmpname, curzone->zname, len);    /* fills [0..12] */
tmpname[len] = ':';                      /* len==13 → overwrites the NUL */
```

For any zone name of length ≥ 13 the only NUL terminator of `tmpname` is
replaced by ':' , and the subsequent `ksnprintf("%s ...", tmpname, ...)`
(vm_zone.c:854) walks past the end of the 14-byte buffer until it finds a
zero byte — copying **kernel stack residue** into the sysctl string that any
unprivileged local user can read (`vm.zone` is CTLFLAG_RD, world-readable).

In-tree trigger: netbt's `rfcomm_credit` zone — exactly 13 characters
(sys/netbt/rfcomm_session.c:162) on kernels built with `device bluetooth`
(not in stock GENERIC; present in LINT/custom BT kernels). Any KLD creating
a vm_zone with a ≥13-char name triggers it too.

## Reproduce

Guest (stock X86_64_GENERIC kernel #0):

1. `build.sh` — build `df2830.ko` (creates zones named `AAAAAAAAAAAAA` (13)
   and `BBBBBBBBBBBBBBBB` (16)).
2. `run.sh` as root: `kldload /root/df2830/df2830.ko`, then as nobody:
   `sysctl vm.zone | hexdump -C`.

## Expected (vulnerable kernel)

The affected rows contain >100 bytes of leaked stack after the name — in
practice the over-read lands on the *previous* read's `tmpbuf` content from
the reused kernel stack, so repeated reads self-echo and amplify:

```
TS\n\nAAAAAAAAAAAAA:AAAAAAAAAAAAA: ... (9+ echoes) ... :ASWAPMETA:  ...
```

Rows run into each other (the row's trailing '\n' is truncated away by the
127-byte ksnprintf cap) and the stats columns themselves get corrupted.
On the *patched* kernel the same zones render as clean 12-char truncated
labels: `AAAAAAAAAAAA: 000064, ...` with no echo.

## Files

- `df2830_harness.c` — KLD creating 13/16-char zones
- `build.sh` / `run.sh`
- `build.log` / `run.log` / `run.hexdump.log` — decisive unpriv reads
- `leak_sample.txt` — 3 successive samples (stable fixed-point echo)
- `fix_validation.log`, `fix_hex.log` — patched-kernel rerun (clean)
- `fix.diff` — git-apply-able fix (clamp to 12 chars so ':' + NUL fit)
- `manifest.json`, `verdict.json`
