kinfo_proc (kern.proc.*) exports unredacted kernel pointers (KASLR defeat)
| Field | Value |
|---|---|
| ID | DF-0016 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-200 Exposure of Sensitive Information to an Unauthorized Actor |
| File | sys/kern/kern_proc.c (copyout path); sys/kern/kern_kinfo.c:128-129, 272, 301, 321 (assignment) |
| Lines | 1603-1648 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
The kern.proc.* sysctls are world-readable and copy out the full
struct kinfo_proc. fill_kinfo_proc()/fill_kinfo_lwp() populate raw
kernel virtual addresses into user-visible fields โ kp_paddr = (uintptr_t)p
(struct proc slab address), kp_fd = (uintptr_t)p->p_fd (filedesc slab
address), kl_wchan (wait-channel address), kp_ktaddr (kernel thread
address). These are copied out unredacted, so any unprivileged local user can
recover kernel heap/.text addresses โ defeating KASLR and directly enabling
the slab-grooming needed to turn heap-corruption bugs (e.g. DF-0013) into
reliable privilege escalation.
Root cause
sys/kern/kern_kinfo.c:128-129:
kp->kp_paddr = (uintptr_t)p; /* struct proc slab address */
kp->kp_fd = (uintptr_t)p->p_fd; /* struct filedesc slab address*/
and at :272, :301, :321 the kl_wchan / kp_ktaddr fields. These
fields are written into kinfo_proc/kinfo_lwp which are then copied to
userland unredacted through the sysctl_out_proc() copyout path in
sys/kern/kern_proc.c:1603-1648. The kern.proc.* nodes (:2173-2201) are
CTLFLAG_RD (world-readable); sysctl reads are not privilege-gated.
This pattern is historically retained for libkvm/ps(1) compatibility
(walking the process list over /dev/mem via kp_paddr).
Threat model & preconditions
- Attacker position: any local unprivileged user.
- Privileges gained or impact: information disclosure โ live slab
addresses of every
struct procandstruct filedesc, plus wait-channel / kernel-thread addresses. A reliable KASLR-bypass / slab-layout primitive. Most significantly, it escalates the practical severity of DF-0013 (thekern.proc.argsheap overflow) and any future slab-corruption bug from DoS to reliable code execution by defeating KASLR and revealing the slab layout. - Required config or capabilities: none; default kernel.
- Reachability:
sysctl kern.proc.pid.<pid>(and the otherkern.proc.*variants) as any user.
Proof of concept
PoC source: findings/poc/DF-0016/leak_kinfo.c
Build & run
cc -o leak_kinfo findings/poc/DF-0016/leak_kinfo.c ./leak_kinfo # as a non-root user
Expected output
pid <self> kp_paddr=0xffff... kp_fd=0xffff... result: LEAK CONFIRMED (KASLR-defeat / slab-address primitive)
Impact
Lowers the bar for exploiting any local kernel memory-corruption bug by defeating KASLR and revealing kernel heap layout. Particularly impactful in combination with DF-0013. Information disclosure only (addresses, not arbitrary memory); rated Low standalone.
Recommended fix
Stop exporting kernel addresses to unprivileged readers. Minimal change in
sys/kern/kern_kinfo.c:
--- a/sys/kern/kern_kinfo.c
+++ b/sys/kern/kern_kinfo.c
@@ -128,8 +128,8 @@
- kp->kp_paddr = (uintptr_t)p;
- kp->kp_fd = (uintptr_t)p->p_fd;
+ kp->kp_paddr = 0; /* do not leak kernel addresses to userland */
+ kp->kp_fd = 0;
@@ -272
- kl->kl_wchan = (uintptr_t)lwp->lwp_thread->td_wchan;
+ kl->kl_wchan = 0;
@@ -301
- kp->kp_ktaddr = (uintptr_t)td;
+ kp->kp_ktaddr = 0;
@@ -321
- kp->kp_lwp.kl_wchan = (uintptr_t)td->td_wchan;
+ kp->kp_lwp.kl_wchan = 0;
Caveat: libkvm consumers that rely on kp_paddr to walk the allproc
list over /dev/mem need a migration path โ e.g. gate the real addresses
behind a kinfo_kvm flag or a root-only sysctl variant, so unprivileged
readers get zeroes while /dev/mem-based tools (which require root anyway)
still work. (This fix lives in kern_kinfo.c, which is the next file slated
for audit.)
References
sys/kern/kern_kinfo.c:128-129โkp_paddr/kp_fdraw-pointer assignment (verified).sys/kern/kern_kinfo.c:272,301,321โkl_wchan/kp_ktaddrassignments.sys/kern/kern_proc.c:1603-1648โ copyout path (this file).- CWE-200 Exposure of Sensitive Information to an Unauthorized Actor.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_proc.c. - pending Reported to DragonFlyBSD security contact.
PoC verification
Evidence pack
findings/poc/DF-0016 ยท 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| leak_kinfo.c | trigger-source | reads kern.proc.pid.<pid>, dumps kp_paddr/kp_fd/kl_wchan/kp_ktaddr + stability check | 4.7 KB | view raw |
| build.sh | build-script | cc -o leak_kinfo leak_kinfo.c | 175 B | view raw |
| run.sh | run-script | runs leak_kinfo as unprivileged user | 304 B | view raw |
| build.log | build-log | final successful build, full output | 68 B | view raw |
| run.log | run-log | decisive run: 24 kernel pointers leaked across 8 procs | 2.4 KB | view raw |
| run.2.log | run-log | 2nd run (variance): root-daemon addrs identical, self addr differs | 2.4 KB | view raw |
| leak_sample.txt | leak-sample | leaked slab/.text pointers + nm cross-ref (cron wchan -> nanowait) | 3.7 KB | view raw |
| VERDICT.md | verdict | full mechanism, reachability, proof, impact | 5.1 KB | โ raw |
| README.md | readme | build/run/expected for humans | 1.9 KB | โ raw |
| fix.diff | suggested-fix | git-apply-able: zero kp_paddr/kp_fd/kl_wchan/kp_ktaddr in kern_kinfo.c (libkvm caveat noted) | 1.3 KB | view raw |
| env.txt | environment | guest uname, cc version, sysctls | 385 B | view raw |
DF-0016 โ PoC
leak_kinfo.c โ unprivileged disclosure of kernel heap / struct proc
pointers via the world-readable kern.proc.* sysctl (kinfo_proc).
The bug
fill_kinfo_proc() / fill_kinfo_lwp() (sys/kern/kern_kinfo.c:128-129,
:272, :301, :321) fill user-visible kinfo_proc / kinfo_lwp fields
with raw kernel virtual addresses:
kp->kp_paddr = (uintptr_t)p; /* struct proc slab address */
kp->kp_fd = (uintptr_t)p->p_fd; /* struct filedesc slab address*/
kl->kl_wchan = (uintptr_t)td_wchan; /* wait-channel address */
kp->kp_ktaddr= (uintptr_t)td; /* kernel thread address */
These are copied out unredacted via sysctl_out_proc
(sys/kern/kern_proc.c:1603/1612/1633). sysctl_kern_proc for
KERN_PROC_PID only checks PRISON_CHECK (jail), not p_trespass
(kern_proc.c:1690), so an unprivileged user reads the kinfo_proc of any
pid (including every root daemon) and recovers those addresses โ a
KASLR-bypass / slab-address primitive.
Build
cc -o leak_kinfo leak_kinfo.c # or: ./build.sh
Run
As an unprivileged user (e.g. maxx):
./leak_kinfo # or: ./run.sh
Expected output (bug present)
pid 1 uid=0 comm=init
kp_paddr = 0xfffff80066807880 (struct proc slab)
kp_fd = 0xfffff80066840ec0 (filedesc slab)
kl_wchan = 0xfffff80066807880 (wait channel)
...
pid 1 kp_paddr: 0xfffff80066807880 / 0xfffff80066807880 / 0xfffff80066807880 (STABLE)
result: 24 kernel pointers leaked across 8 processes
result: LEAK CONFIRMED (KASLR-defeat / slab-address primitive)
kp_paddr is the live slab address of the target's struct proc. Cross-ref:
cron's kl_wchan = 0xffffffff8130f670 exactly matches the nm symbol
nanowait (kernel .text) โ see leak_sample.txt. On a fixed kernel the
fields are 0 and the program exits 2.
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):
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.
Confirmed kernel references
Detail
Exploit chain
none (pure info-disclosure / KASLR-defeat + slab-layout primitive; no memory corruption to chain). It is the enabler that escalates the practical severity of a local heap/struct-proc corruption bug (notably DF-0013 kern.proc.args overflow) from DoS to reliable local privilege escalation by defeating KASLR and revealing the slab layout for grooming. No further primitive derivable from this read alone.
Evidence (decisive lines)
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
kl_wchan = 0xffffffff8130f670 (kernel .text/.data -> nm: nanowait EXACT)
=== stability check ===
pid 1 kp_paddr: 0xfffff80066807880 / ...880 / ...880 (STABLE = real struct proc addr)
result: 24 kernel pointers leaked across 8 processes
result: LEAK CONFIRMED (KASLR-defeat / slab-address primitive)
RUN_EXIT=0 (read as uid=1001 maxx)
PoC changes
Rewrote leak_kinfo.c: the original was a FALSE NEGATIVE -- it printed the leaked addresses but its looks_like_kaddr used >=0xffffffff80000000 (kernel TEXT range only), which EXCLUDES DragonFly's 0xfffff8xxxxxxxxxx direct-map/KVA heap region where kp_paddr/kp_fd/kl_wchan live, so it reported 'no kernel pointers observed' (RUN_EXIT=2) despite the leak. Also added missing string.h/unistd.h. New version detects the full canonical kernel upper half (>=0xffff800000000000), reads all four pointer fields, iterates self + root daemons, adds a 3x stability check on pid 1 kp_paddr, and prints a clear LEAK CONFIRMED marker. Added build.sh/run.sh. Full packs in findings/poc/DF-0016/ (VERDICT.md, leak_sample.txt w/ nm cross-ref, run.log+run.2.log, fix.diff).
Verified recommended fix
fix.diff (git apply --check OK) zeroes the leaking fields in sys/kern/kern_kinfo.c: kp_paddr (:128), kp_fd (:129), kl_wchan (:272), kp_ktaddr (:301), kp_lwp.kl_wchan (:321) before they reach userland. Matches finding markdown proposal. Caveat: libkvm consumers that walk allproc over /dev/kmem using kp_paddr need a migration path -- gate the real addresses behind a root-only / /dev/mem-mediated variant so unprivileged sysctl readers get zeroes while /dev/kmem tools (root-only) keep working; a more surgical alternative is to redact in the copyout path (kern_proc.c sysctl_out_proc) rather than in fill_*, preserving in-kernel/kvm use.
Verdict
REPRODUCED. fill_kinfo_proc (sys/kern/kern_kinfo.c:128-129) sets kp->kp_paddr=(uintptr_t)p and kp->kp_fd=(uintptr_t)p->p_fd; fill_kinfo_lwp (:272) sets kl->kl_wchan=(uintptr_t)td_wchan; fill_kinfo_proc_kthread (:301,:321) sets kp_ktaddr and kp_lwp.kl_wchan -- all raw kernel virtual addresses copied out unredacted by sysctl_out_proc (kern_proc.c:1603/1612/1633). Reachable as any user: sysctl_kern_proc for KERN_PROC_PID (kern_proc.c:1686-1694) checks only PRISON_CHECK (:1690), not p_trespass; the kern.proc.* nodes are CTLFLAG_RD and reads are not framework-gated (kern_sysctl.c:1446-1450). As unprivileged maxx I read kern.proc.pid.