/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)
PoC verification
Evidence pack
findings/poc/DF-3080 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_PIDguard passes (sys/vfs/procfs/procfs_vnops.c:798-800).pfs_pfind(0)returns&proc0via 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_testablefix.diff authored against read-only sys/ tree; kernel not rebuilt (Info-grade finding).
['fix.diff']
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.
No comments yet.