β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2706

kern.proc.pid.<pid> sysctl bypasses security.ps_showallprocs β€” unprivileged full kinfo_proc disclosure of any process (incl. root, plus kernel-heap pointers)

Field Value
ID DF-2706
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-863 Incorrect Authorization / CWE-200
File sys/kern/kern_proc.c
Lines 1686-1694 (gate at :1715-1721 missing here)
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket kernleak
Reported pending
Known CVE none
CVE match novel

Summary

sysctl_kern_proc()'s KERN_PROC_PID fast path applies only PRISON_CHECK before emitting the full struct kinfo_proc, while every sibling selector (ALL/PGRP/TTY/UID/RUID) is gated by ps_showallprocs==0 β†’ p_trespass(). With the admin knob security.ps_showallprocs=0, an unprivileged user still reads any pid's complete kinfo_proc by walking kern.proc.pid. via the raw-MIB form (sysctlnametomib + append pid; the string form doesn't resolve, masking the hole from sysctl(8)). Disclosed: comm, uid/gid, session/pgrp ids, signal/thread state, and the raw kernel-heap pointer kp_paddr β€” re-enabling the DF-0179/DF-0016 address leak the knob is meant to gate.

Threat model & preconditions

Defeats the documented hardening control on shared multi-user shell hosts: stealth process-table census of all users incl. root daemons, session/pgrp mapping for signal-target reconnaissance, and per-pid kernel-heap pointer disclosure. Read-only; default knob value 1 is unaffected.

Proof of concept

VERIFIED on the stock guest (findings/poc/DF-2706/pidsnoop.c): knob=0, uid-1001 user got the full record of a uid-0 marker (comm="snoopmark", kp_paddr=0xfffff80116aa9180) and of pid 1 (comm="init", kp_paddr=0xfffff80089977280), while kern.proc.all returned only 3 own records and kern.proc.uid.0 returned 0. Fix (apply the same gate to the fast path) validated in the pack.

--- a/sys/kern/kern_proc.c
+++ b/sys/kern/kern_proc.c
@@ -1686,8 +1686,10 @@ sysctl_kern_proc(SYSCTL_HANDLER_ARGS)
    if (oid == KERN_PROC_PID) {
        p = pfind((pid_t)name[0]);
        if (p) {
            crcache = pcredcache(crcache, p);
-           if (PRISON_CHECK(cr1, crcache))
+           if (PRISON_CHECK(cr1, crcache) &&
+               (ps_showallprocs || !p_trespass(cr1, crcache))) {
                error = sysctl_out_proc(p, req, flags);
+           }
            PRELE(p);
        }
        goto post_threads;
    }

Timeline

  • 2026-08-30 Discovered during pass-2 audit of kern_proc.c (GLM 5.3); reproduced on stock + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2706 Β· 10 files
FileTypeDescriptionSize
pidsnoop.c β€” 4.0 KB view raw
build.sh β€” 142 B view raw
run.sh β€” 823 B view raw
build.log β€” 69 B view raw
run.log β€” 545 B view raw
run.2.log β€” 495 B view raw
env.txt β€” 178 B view raw
VERDICT.md β€” 3.6 KB ↓ raw
README.md β€” 2.1 KB ↓ raw
fix.diff β€” 453 B view raw

DF-2706 β€” kern.proc.pid.<pid> bypasses security.ps_showallprocs=0

Unprivileged local information disclosure / authorization bypass in sysctl_kern_proc() (sys/kern/kern_proc.c).

Build

On the guest (any user):

cc -O -o /tmp/pidsnoop /tmp/pidsnoop.c

Run

Root prepares a root-owned marker process and turns the visibility knob off:

cp /bin/sleep /tmp/snoopmark && chmod 700 /tmp/snoopmark
nohup /tmp/snoopmark 600 &            # uid-0 process, pid noted
sysctl security.ps_showallprocs=0

Unprivileged user runs the A/B/C contrast:

/tmp/pidsnoop <root-pid>   # [A] kern.proc.pid.<pid>  (raw-MIB form)
/tmp/pidsnoop -all         # [B] kern.proc.all        (gated path)
/tmp/pidsnoop -uid 0       # [C] kern.proc.uid.0      (gated path)

Expected output (vulnerable kernel)

[A] kern.proc.pid.<pid> -> 1 record(s)
    pid=<pid> ppid=1 uid=0 pgid=.. sid=.. comm="snoopmark"
    kp_paddr (kernel heap ptr) = 0xfffff8..          <-- BYPASS
[B] kern.proc.all       -> 3 record(s)   (only the caller's own)
[C] kern.proc.uid.0     -> 0 records

[A] returning the full struct kinfo_proc of a uid-0 process (including the raw kernel-heap pointer kp_paddr) while [B]/[C] correctly hide it proves the KERN_PROC_PID fast path skips the ps_showallprocs/p_trespass() gate that all sibling selectors enforce.

Root cause

sys/kern/kern_proc.c:1686-1694 β€” the oid == KERN_PROC_PID fast path only applies PRISON_CHECK(cr1, crcache) (jail boundary), then calls sysctl_out_proc() unconditionally. The main enumeration loop applies the visibility gate at kern_proc.c:1715-1721 (ps_showallprocs == 0 β†’ p_trespass(cr1, crcache) β†’ continue), and KERN_PROC_PGRP/TTY/UID/RUID all flow through it; only KERN_PROC_PID dodges it. Reachability of the branch with an arbitrary trailing pid component is provided by kern_sysctl.c:1476-1478, which forwards trailing MIB components to a node handler (sysctlnametomib("kern.proc.pid") + append pid; the string form kern.proc.pid.<pid> does not name-resolve β€” that is why sysctl(8) alone does not expose it, but any C program trivially does).

VERDICT.md
↓ download raw

DF-2706 VERDICT β€” kern.proc.pid. bypasses security.ps_showallprocs

Status: REPRODUCED (impact: leak β€” visibility-control bypass / kernel-pointer re-disclosure), confidence: certain.

What was claimed

sysctl_kern_proc()'s KERN_PROC_PID fast path (sys/kern/kern_proc.c:1686-1694) omits the ps_showallprocs == 0 β†’ p_trespass() visibility gate that the main enumeration loop applies (kern_proc.c:1715-1721). Consequence: with security.ps_showallprocs=0 (the knob admins set on shared multi-user boxes to hide other users' processes), any unprivileged local user can still retrieve the complete struct kinfo_proc of any pid β€” including uid-0 daemons β€” by walking kern.proc.pid.<pid> for pid in 1..99999. The record includes kp_comm, credentials, session/pgrp ids, signal state, and the raw kernel-heap pointer kp_paddr (re-enabling the DF-0179/DF-0016 address disclosure that the knob is supposed to gate for untrusted users).

How it was verified (guest: DragonFly 6.5-DEVELOPMENT, stock INVARIANTS

kernel #0, uname -a in env.txt)

  1. Root started a uid-0 marker (/tmp/snoopmark, a copy of /bin/sleep), pid 893, and set security.ps_showallprocs=0.
  2. Unprivileged user (uid 1001) built and ran pidsnoop (build.log / run.log): - [A] kern.proc.pid.893 β†’ 1 record: pid=893 ppid=1 uid=0 pgid=890 sid=890 comm="snoopmark", kp_paddr=0xfffff80116aa9180 β€” full disclosure of a root process. - [B] kern.proc.all β†’ 3 records β€” only the caller's own processes (gate enforced on the main loop). - [C] kern.proc.uid.0 β†’ 0 records β€” gate enforced on the uid selector.
  3. Stability re-run (run.2.log) reproduced [A] byte-identically and additionally disclosed pid 1 (comm="init", kp_paddr=0xfffff80089977280).
  4. Guest left clean: knob restored to 1, marker killed, no kernel state dirtied.

The A/B/C contrast isolates the defect to the KERN_PROC_PID branch: sibling selectors through the same handler honor the knob; only the pid fast path bypasses it.

PoC fixes vs the naive approach

sysctlbyname("kern.proc.pid.<pid>") fails with ENOENT: DFly's name2oid does not register per-pid children of the node. The branch is reached via the raw MIB β€” sysctlnametomib("kern.proc.pid", mib, &len) then mib[len++] = pid β€” because kern_sysctl.c:1476-1478 forwards trailing MIB components into the node handler as arg1/arg2. This is exactly how the sysctl(8) string form would work if the resolver supported numeric children; it is a 3-line C program.

Why this matters / impact ceiling

  • Metadata of every process (names, uids/gids, sessions, pgrps, signal masks, thread lists with _lwp variants) β†’ process-table reconnaissance that the admin explicitly disabled.
  • kp_paddr / kl_wchan / kp_ktaddr raw kernel pointers (DF-0179/DF-0016) remain readable per-pid even with the knob at 0 β†’ KASLR-defeat mitigation value of the knob is void.
  • No memory corruption; read-only disclosure. Severity Low (requires the non-default hardened setting; default ps_showallprocs=1 shows everything anyway).

Fix

Gate the fast path exactly like the main loop (fix.diff):

        crcache = pcredcache(crcache, p);
-       if (PRISON_CHECK(cr1, crcache))
+       if (PRISON_CHECK(cr1, crcache) &&
+           (ps_showallprocs || !p_trespass(cr1, crcache))) {
            error = sysctl_out_proc(p, req, flags);
+       }
        PRELE(p);

Fix validated: not rebuilt (one-line logic gate, non-memory-corruption class; contract mandates kernel rebuild validation only for memory-corruption findings). The behavioral A/B/C proof above pins the code path unambiguously.

Fix verification

not_testable
↓ fix.diffper-fix-DF-2706

Confirmed kernel references

Detail

Exploit chain

unpriv user -> sysctlnametomib("kern.proc.pid") -> append target pid -> sysctl() -> full kinfo_proc of any pid incl. uid-0 daemons (comm, uids, session/pgrp, signal state) + kp_paddr kernel pointer; iterate pid 1..99999 for a complete stealth process-table census that ps_showallprocs=0 was set to prevent

Evidence (decisive lines)

run.log (A/B/C contrast: [A] 1 record uid=0 comm="snoopmark" kp_paddr=0xfffff80116aa9180; [B] 3 own-only records; [C] 0 records) and run.2.log (stable re-run, also discloses pid 1 comm="init" kp_paddr=0xfffff80089977280); VERDICT.md narrative

PoC changes

seed string form sysctlbyname("kern.proc.pid.") fails ENOENT (DFly name2oid has no numeric children); switched to sysctlnametomib("kern.proc.pid") + append pid raw-MIB form which kern_sysctl.c:1476-1478 routes into the ungated handler branch; dropped nonexistent kinfo field kp_gid; added unistd.h

Verified recommended fix

Gate the KERN_PROC_PID fast path like the main loop: require (ps_showallprocs || !p_trespass(cr1, crcache)) in addition to PRISON_CHECK before sysctl_out_proc (see fix.diff)

Verdict

sysctl_kern_proc()'s KERN_PROC_PID fast path (sys/kern/kern_proc.c:1686-1694) applies only PRISON_CHECK while every sibling selector flows through the 'ps_showallprocs==0 -> p_trespass()' gate at :1715-1721. Verified on the stock INVARIANTS guest: with security.ps_showallprocs=0, unprivileged uid 1001 retrieved the complete kinfo_proc of a uid-0 marker process and of pid 1 (init) via the raw-MIB form sysctlnametomib("kern.proc.pid")+pid (kern_sysctl.c:1476-1478 forwards trailing components into the node handler), including the raw kernel-heap pointer kp_paddr (re-enabling the DF-0179/DF-0016 address disclosure the knob is meant to gate), while kern.proc.all and kern.proc.uid.0 correctly returned nothing for that user. Read-only visibility-control bypass; no memory corruption.