# DF-0016 — kern.proc.* (kinfo_proc) exports unredacted kernel pointers (KASLR defeat)

## Verdict
**REPRODUCED** — the world-readable `kern.proc.pid.<pid>` sysctl returns the
full `struct kinfo_proc` with unredacted kernel virtual addresses in
`kp_paddr` (struct proc slab), `kp_fd` (filedesc slab), `kl_wchan` (wait
channel) and `kp_ktaddr` (kernel thread). Confirmed on DragonFly master DEV
`v6.5.0.1712.g89e6a-DEVELOPMENT`: as unprivileged `maxx` I read these for
every root daemon; 24 kernel pointers leaked across 8 processes, including a
`.text`-range wait-channel value that exactly matches a `nm` symbol.

## Mechanism (confirmed by source + run)

`fill_kinfo_proc` (`sys/kern/kern_kinfo.c:128-129`):

```c
kp->kp_paddr = (uintptr_t)p;          /* struct proc slab address     */
kp->kp_fd    = (uintptr_t)p->p_fd;    /* struct filedesc slab address */
```

`fill_kinfo_lwp` (`kern_kinfo.c:272`): `kl->kl_wchan = (uintptr_t)lwp->lwp_thread->td_wchan;`
`fill_kinfo_proc_kthread` (`kern_kinfo.c:301`): `kp->kp_ktaddr = (uintptr_t)td;`
and (`kern_kinfo.c:321`): `kp->kp_lwp.kl_wchan = (uintptr_t)td->td_wchan;`

These fields live in user-visible `struct kinfo_proc` (`sys/sys/kinfo.h:174`
`kp_paddr`, `:183` `kp_fd`, `:237` `kp_ktaddr`). They are copied out unredacted
by `sysctl_out_proc` (`sys/kern/kern_proc.c:1603` fill, `:1612`/`:1633`
`SYSCTL_OUT(req, &ki, sizeof(ki))`).

Reachability: `sysctl_kern_proc` for `KERN_PROC_PID` (`kern_proc.c:1686-1694`)
does `pfind(name[0])` and only checks `PRISON_CHECK(cr1, crcache)` (`:1690`) —
**no** `p_trespass`. So an unprivileged user reads the `kinfo_proc` of any pid
(not just own). The `kern.proc.*` nodes (`:2197-2201`) are `CTLFLAG_RD` and
sysctl reads are not framework-gated (`kern_sysctl.c:1446-1450` gates writes
only).

## Proof

`./leak_kinfo` as `maxx` (uid 1001) reads `kern.proc.pid.<pid>` for self + root
daemons and prints the pointer fields (excerpt; full in `leak_sample.txt`):

```
pid 1      uid=0    comm=init
    kp_paddr   = 0xfffff80066807880   (struct proc slab)
    kp_fd      = 0xfffff80066840ec0   (filedesc slab)
    kl_wchan   = 0xfffff80066807880   (wait channel)
pid 730    uid=0    comm=cron
    kp_paddr   = 0xfffff800ab144680 ; kp_fd = 0xfffff800ab16a540
    kl_wchan   = 0xffffffff8130f670   (kernel .text/.data!)
...
pid 1 kp_paddr: 0xfffff80066807880 / 0xfffff80066807880 / 0xfffff80066807880  (STABLE)
result: 24 kernel pointers leaked across 8 processes
result: LEAK CONFIRMED (KASLR-defeat / slab-address primitive)
```

Cross-referencing the leaked `.text`-range value against `nm /boot/kernel/kernel`:
`cron kl_wchan 0xffffffff8130f670` → nearest symbol `nanowait.<clone>+0x0` (EXACT) —
a kernel `.text`/`.data` address, directly revealing the kernel base. The
`0xfffff8xxxxxxxxxx` values are DragonFly kernel KVA/malloc (slab) addresses;
they are confirmed real (not garbage) because they are canonical upper-half
kernel addresses, **stable across repeated reads** (pid 1 kp_paddr identical 3×),
distinct per pid, and clustered in slab-sized groups (init/dhclient in
`0xfffff8006680xxxx`; hammer2/devd/syslogd/sshd/cron in `0xfffff800ab14xxxx`).
Root-daemon addresses are byte-identical between `run.log` and `run.2.log`
(long-running procs don't relocate); the self (leak_kinfo) address differs run
to run because each run is a new process — itself confirming these are live slab
allocations.

## Impact
Information disclosure to any local unprivileged user: the live slab address of
every process's `struct proc` and `struct filedesc`, plus wait-channel / kernel
thread addresses (sometimes a `.text` symbol). A reliable KASLR-bypass and
slab-layout primitive. Standalone it is info-disclosure (rated Low), but it is
the enabler that escalates the practical severity of any local heap / struct-proc
corruption bug (e.g. DF-0013) from DoS to reliable local privilege escalation
by defeating KASLR and revealing the slab layout for grooming.

## PoC changes
The original `leak_kinfo.c` had two bugs: (1) missing `#include <string.h>` /
`<unistd.h>` (compiler warnings), and (2) its `looks_like_kaddr` used
`>= 0xffffffff80000000` — which is the kernel **text** range only and
**excludes** the DragonFly direct-map/KVA heap region `0xfffff8xxxxxxxxxx`
where `kp_paddr`/`kp_fd`/`kl_wchan` live. The original therefore *printed* the
leaked addresses but then reported "no kernel pointers observed" (RUN_EXIT=2)
— a false negative. The rewrite detects the full canonical kernel upper half
(`>= 0xffff800000000000`), reads all four pointer fields, iterates self +
several root daemons, adds a 3× stability check on pid 1's kp_paddr, and prints
a clear LEAK CONFIRMED marker. Added `build.sh`/`run.sh`.

## Recommended fix
Matches the finding markdown's proposal (with the libkvm caveat). Authoritative
`fix.diff` in this folder zeroes `kp_paddr`/`kp_fd`/`kl_wchan`/`kp_ktaddr`/
`kp_lwp.kl_wchan` in `kern_kinfo.c` before they reach userland. For `libkvm`
(`/dev/mem`) consumers that need the real addresses, the addresses must instead
be gated behind a root-only / `/dev/mem`-mediated path (those tools already
require root), so unprivileged sysctl readers get zeroes.
