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

/proc/0 makes kernel-internal proc0 (swapper) addressable and readable by unprivileged users (atopid("0") slips past the NO_PID guard into pfs_pfind(0)β†’&proc0)

Summary

procfs_lookup's Proot case accepts pid component '0': atopid returns 0 which is not NO_PID, and pfs_pfind(0) special-cases pid 0 to &proc0 (procfs_subr.c:285-287) - a kernel-internal, never-reapable object whose pid-0 path bypasses pfind's allproc/SZOMB filtering. With the default security.ps_showallprocs=1 the uid gate passes and a Pproc node for pid 0 is created; unpriv users can then read /proc/0/status (swapper row incl. root's credential group list and boot epoch), cmdline (p_comm), rlimit, and stat the directory; readdir lists '0' since proc0 sits on allproc. Privileged targets stay correctly denied (mem/ctl via CHECKIO+mode, regs/map/etype via P_SYSTEM valid* checks) - verified on guest. Info-grade: leaked fields are individually public; security value is namespace hygiene - any future per-proc target/field lands on proc0 for free, and the pid-0 path skips normal pfind protections. Linux/FreeBSD never expose pid 0. VERIFIED guest uid 1001. Fix: reject pid 0 at lookup + skip pid-0 rows in readdir.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3080 Β· 7 files
FileTypeDescriptionSize
procfs_zero.sh β€” 1.2 KB view raw
run.sh β€” 85 B view raw
run.log β€” 612 B view raw
env.txt β€” 272 B view raw
fix.diff β€” 576 B view raw
VERDICT.md β€” 2.0 KB ↓ raw
verdict.json β€” 2.1 KB view raw
VERDICT.md
↓ download raw

DF-3080 β€” VERDICT

Status: reproduced (1 run, deterministic behavior; guest stayed up). Impact: leak (trivial) β€” unprivileged users can open and read kernel proc0's (swapper) procfs pseudo-files.

How it reproduces

  • atopid("0") β†’ 0; pid == NO_PID guard passes (sys/vfs/procfs/procfs_vnops.c:798-800).
  • pfs_pfind(0) returns &proc0 via the pid-0 special case (sys/vfs/procfs/procfs_subr.c:285-287) β€” no allproc/zombie filtering applies.
  • security.ps_showallprocs=1 (default, verified in env.txt) makes the uid gate at sys/vfs/procfs/procfs_vnops.c:809-811 a no-op for this case.
  • procfs_allocvp(..., 0, Pproc) creates the directory node; per-target gates then apply exactly as for any process: status/cmdline/rlimit are world-readable (mode 0444), mem/ctl are denied for non-root (verified: dd /proc/0/mem β†’ Permission denied).

Observed (uid 1001 maxx, full log in run.log):

/proc/0: dr-xr-xr-x  16 root wheel ...
status: swapper 0 0 0 0 -1,-1 sldr <epoch>,145 0,0 3,294605 idle 0 0 0,0 -
rlimit: cpu -1 -1 / fsize -1 -1 / data 134217728 34359738368
readdir: '0' is listed (proc0 is on allproc)

Why it is only Info

Every field readable through /proc/0/status is independently public: boot time (kern.boottime), idle cpu (kern.cp_time), root's groups (/etc/group). rlimits of proc0 are kernel defaults. mem/regs/ctl/map/ etype stay denied (CHECKIO + mode gates + P_SYSTEM valid* checks β€” procfs_vnops.c:136-144, procfs_regs.c:79-81, procfs_map.c:254-256, procfs_type.c:76-78). The finding documents a namespace-hygiene defect and a latent hazard (kernel-internal object addressable; pid-0 path skips pfind's list protections), not a confidentiality breach with delta.

Fix validation

Not rebuilt (Info finding). fix.diff rejects pid 0 at the Proot lookup case and skips pid 0 rows in the readdir callback β€” no pfs_pfind change needed (pid 0 remains meaningful for Proot/Pcurproc nodes). fix_status: not_testable (authored only).

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff authored against read-only sys/ tree; kernel not rebuilt (Info-grade finding).

['fix.diff']
↓ fix.diffper-fix-DF-3080

Confirmed kernel references

Detail

Evidence (decisive lines)

["run.log: /proc/0 directory listing, swapper status row 'swapper 0 0 0 0 -1,-1 sldr ... 0 0 0,0 -', rlimit lines, '0' present in readdir listing, /proc/0/mem Permission denied (gates hold)"]

PoC changes

Written fresh; no seed existed.

Verified recommended fix

Reject pid 0 in procfs_lookup's Proot case (pid == NO_PID || pid == 0) and skip p_pid==0 rows in procfs_readdir_root_callback.

Verdict

Unprivileged users can address kernel-internal proc0 (swapper) through /proc/0: atopid('0') returns 0 which slips past the NO_PID guard (vnops.c:798-800) and pfs_pfind(0) special-cases pid 0 to &proc0 (subr.c:285-287). Reproduced as uid 1001: /proc/0/{status,cmdline,rlimit} readable (status shows the swapper row incl. root group list and boot epoch), /proc/0 listed by readdir since proc0 sits on allproc. mem/ctl/regs/map/etype correctly denied. Leaked fields are individually public elsewhere (kern.boottime, kern.cp_time), so the practical delta is negligible β€” Info-grade namespace/hygiene defect and latent hazard; Linux/FreeBSD never expose pid 0.