# DF-0922 — VERDICT

## Verdict: REPRODUCED → FIXED

**DF-0922** is a real kernel heap-pointer info leak via `%p` of the
`vm_object` pointer in `/proc/<pid>/map` output. The leak is reachable by any
local user reading their own map (no privilege needed). The supplied `fix.diff`
removes the `%p` conversion and was validated on a built-and-booted single-fix
kernel: **0 kernel pointers leaked after the fix, vs 12–16 on baseline.**

## Mechanism (confirmed line-by-line)

At `sys/vfs/procfs/procfs_map.c:208-223` the per-map-entry format string
passed to `sbuf_printf()` contains a `%p` conversion (line 210 for 64-bit,
line 212 for 32-bit):

```c
error = sbuf_printf(sb,
#if LONG_BIT == 64
      "0x%016lx 0x%016lx %d %d %p %s%s%s %d %d "        /* line 210 */
#else
      "0x%08lx 0x%08lx %d %d %p %s%s%s %d %d "          /* line 212 */
#endif
      "0x%04x %s%s %s %s\n",
      (u_long)e_start, (u_long)e_end,
      resident, -1, (ba ? ba->object : NULL),            /* line 216: %p arg */
      ...);
```

The 5th conversion `%p` is satisfied by `(ba ? ba->object : NULL)` at
**`procfs_map.c:216`** — a `struct vm_object *`, i.e. a kernel heap address.
That pointer is emitted verbatim into the sbuf and copied back to userland by
`uiomove_frombuf()` at **`procfs_map.c:245`**. There is no `%pK`-style
privilege gate; the entire `procfs_domap` path has no privilege check on the
*content* of the output (the cross-user *access* check is a separate bug,
DF-0921).

## Reproduction (unpatched `#0` baseline)

```
$ uname -a
DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 ... x86_64
$ id
uid=1001(maxx) gid=1001(maxx) groups=1001(maxx)
$ head -2 /proc/self/map
0x0000000000400000 0x0000000000403000 -1 -1 0xfffff80116837240 r-x 2 0 0x0000 COW NC vnode /bin/cat
0x0000000000602000 0x0000000000603000 -1 -1 0xfffff80116837240 rw- 2 0 0 0x0000 COW NNC vnode /bin/cat
                                          ^^^^^^^^^^^^^^^^^^^^
                                          leaked vm_object kernel pointer
```

Three independent runs each emit 12 distinct `0xffff...` addresses that **vary
between runs** (live heap, not cosmetic) — see `leak_sample.txt`:

```
run1: 0xfffff80116821940, 0xfffff801170b1d40, 0xfffff801170b3f00, ...
run2: 0xfffff80116821940, 0xfffff801170b0e40, 0xfffff801170b2380, ...
run3: 0xfffff80116821940, 0xfffff801168252c0, 0xfffff80116825900, ...
```

(The 4 stable addresses are shared kernel objects like libc/ld-elf.so vnodes;
the rest drift run-to-run as fresh vm_objects are allocated.)

## Impact

Info leak — KASLR / kernel-heap-layout defeat. No direct privilege gain, no
memory corruption. The leaked set of `vm_object` addresses lets an attacker:
- defeat KASLR (kernel heap is at fixed offsets on this guest, but on a
  KASLR-hardened kernel this would defeat randomization);
- infer slab-zone layout for heap-grooming in service of a *separate*
  memory-corruption primitive.

Not exploitable on its own — no write primitive, no control flow. This is the
"classic `%p` leak" pattern (Linux fixed it with `%pK`).

## Fix

`fix.diff` replaces the `%p` conversion with the literal token `0x0` in both
the 64-bit and 32-bit format strings, and drops the now-unused
`(ba ? ba->object : NULL)` argument from the variadic list. This:
- preserves the column count (existing `procstat`/`gdb`/`lsof` consumers keep
  parsing the same field positions);
- emits a constant `0x0` instead of the kernel pointer;
- removes one variadic argument so the format/argument count still matches.

A debug consumer that genuinely needs the pointer should be gated behind
`priv_check_cred()` and print `0x0` otherwise — out of scope for this minimal
fix. This fix matches the finding markdown's `## Recommended fix` proposal.

## Fix validation (Phase 8)

Built a single-fix kernel with `make -j6 nativekernel KERNCONF=X86_64_GENERIC`
(warm obj, applied only this `fix.diff`), installed `/boot/kernel/kernel` +
`/boot/kernel/kernel.debug`, rebooted into `#1`, re-ran the SAME PoC:

| kernel | `kern.version` | column-5 sample | `0xffff` count |
|--------|----------------|-----------------|----------------|
| baseline (unpatched) | `#0` Thu Jul 2 | `0xfffff80116837240` | **12–16** |
| patched (this fix)    | `#1` Sun Jul 12 | `0x0`               | **0** |

Deterministic across 3 runs on the patched kernel. The leak is gone and no
new panic or regression was introduced. `fix_status: fixed`.

## Files

| file | desc |
|------|------|
| `leak_kptr.sh` | trigger: extracts column 5 of `/proc/<pid>/map` |
| `run.sh` | runnable wrapper that builds + runs + classifies |
| `build.sh` | no-op (pure shell PoC) |
| `fix.diff` | git-apply-able fix (replaces `%p` with `0x0`) |
| `run.log` | baseline `#0` reproduction, full output |
| `fix_run.log` | patched `#1` re-run, full output |
| `fix_build.log` | single-fix kernel build log |
| `leak_sample.txt` | 3-run variance sample |
| `env.txt` | guest environment |
| `manifest.json` | artifact catalog |
