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

Ungated /proc/<pid>/file and /exe readlink discloses every process's full executable path (root daemons included) to unprivileged users

Summary

The Pfile target is registered with pt_valid == NULL (unlike regs/map/etype) and procfs_readlink's Pfile case performs only pfs_pfind + a p_ucred NULL check - no uid check, no CHECKIO/p_trespass, no prison check, no ps_showallprocs gate; readlink(2) applies no mode check on VLNK. Any user reads the authoritative executable path of every process. procfs_getattr's Pfile case leaks the same via size. The kernel's own comment (procfs_mem.c:212-219) admits this 'exposes an information leak that shouldn't happen'. cmdline is gated by exactly the CHECKIO/p_trespass policy this path lacks (procfs_status.c:203-205), and the KERN_PROC argv sysctl is uid-gated - so this exceeds what ps reveals by default, with no sysctl knob. Sibling of DF-0921 (map). VERIFIED guest uid 1001: readlink /proc/1/file -> /sbin/init; sweep of uid-0 pids -> dhclient, devd, syslogd, hammer2, sshd. Fix: apply the cmdline policy to both Pfile paths (CHECKIO/p_trespass else 'unknown').

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3081 Β· 7 files
FileTypeDescriptionSize
procfs_file_leak.sh β€” 1.5 KB view raw
run.sh β€” 90 B view raw
run.log β€” 491 B view raw
env.txt β€” 272 B view raw
fix.diff β€” 943 B view raw
VERDICT.md β€” 2.0 KB ↓ raw
verdict.json β€” 2.2 KB view raw
VERDICT.md
↓ download raw

DF-3081 β€” VERDICT

Status: reproduced (deterministic; guest stayed up). Impact: leak β€” unprivileged disclosure of every process's executable path (root daemons included) via /proc/<pid>/file and /proc/<pid>/exe.

How it reproduces

  1. Resolving /proc/710/file walks procfs_lookup Pproc for pid 710; with security.ps_showallprocs=1 (default) the directory resolves for any user (sys/vfs/procfs/procfs_vnops.c:830-835).
  2. The Pfile target has pt_valid == NULL (sys/vfs/procfs/procfs_vnops.c:147-148) so the per-target loop at :837-842 always matches file/exe β€” no validity filter.
  3. readlink(2) performs no mode check on VLNK; procfs_readlink's Pfile case (sys/vfs/procfs/procfs_vnops.c:1128-1162) runs pfs_pfind, checks only p_ucred != NULL, then cache_fullpath() on p_textnch and uiomoves the full path out. No CHECKIO / p_trespass / prison / showallprocs gate anywhere on this path (contrast procfs_docmdline, procfs_status.c:203-205, which gates argv reads exactly this way).
  4. Observed as uid 1001: /sbin/init, /sbin/dhclient, /sbin/devd, /usr/sbin/syslogd, /sbin/hammer2, /usr/sbin/sshd (run.log).

The in-tree comment at sys/vfs/procfs/procfs_mem.c:212-219 acknowledges "the /proc/pid/file object exposes an information leak that shouldn't happen" β€” the pt_valid == NULL registration keeps it live.

Why Low (not higher)

Information-only: path strings, no memory contents. ps already exposes comm names and (with kern.ps_argsopen=1, as on this guest) full argv; the delta is the authoritative binary path for every process regardless of that sysctl. Valuable for recon (admin tooling paths, home-dir binaries), consistent with Low/C:L.

Fix validation

Not rebuilt (Low info-leak finding). fix.diff mirrors the docmdline policy: CHECKIO(curproc, procp) || p_trespass(...) fails β†’ emit "unknown" (readlink) / size of "unknown" (getattr). Root and same-uid debuggers keep full resolution. 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 (Low info-leak finding).

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

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log: readlink /proc/1/file -> /sbin/init; uid-0 sweep yields /sbin/dhclient, /sbin/devd, /usr/sbin/syslogd, /sbin/hammer2, /usr/sbin/sshd']

PoC changes

Written fresh; no seed existed.

Verified recommended fix

Gate Pfile readlink/getattr with CHECKIO+p_trespass (same policy as procfs_docmdline), falling back to 'unknown' for processes the caller may not examine.

Verdict

procfs_readlink's Pfile case (vnops.c:1128-1162) and procfs_getattr's Pfile case (:642-665) return the full executable path of any process with no authorization gate β€” pt_valid is NULL for Pfile (:147-148), and the readlink path performs only pfs_pfind + p_ucred NULL check (no uid/CHECKIO/p_trespass/prison/showallprocs). Reproduced as uid 1001: readlink of /proc/1/file, /proc/390/file, /proc/466/file, /proc/68/file, /proc/710/file yields /sbin/init, /sbin/devd, /usr/sbin/syslogd, /sbin/hammer2, /usr/sbin/sshd. The in-tree comment at procfs_mem.c:212-219 admits this leak 'shouldn't happen'. Info-only impact: path disclosure of every binary on the system (more than ps reveals by default). Sibling of DF-0921 (map).