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

/proc/<pid>/rlimit read has no per-op authorization: any local user reads any process's full rlimit table

Summary

procfs_dorlimit() serves /proc/<pid>/rlimit with zero credential checks, unlike every sibling op (regs/fpregs/dbregs/mem/ctl all CHECKIO+p_trespass per operation; status gates argv sections). The only barrier is lookup-time visibility, and security.ps_showallprocs defaults to 1 (verified) - uid 1001 read 209 bytes of init's (uid 0) soft/hard limit table. VERIFIED twice on stock + patched control. Same data Linux gates behind PTRACE_MODE_READ for /proc/pid/limits - unpriv user fingerprints root/security-daemon limit configuration (memlock/nofile/stack of privilege-separated services). Also empirically pinned DF-0935's boundary: DF-0935 is UNFIXED in tree but unreachable with default loader caps (kernel clamps the limits; maximized formatter output is exactly 431 bytes < psbuf[512], 81 headroom; overflow needs root-relaxed maxdsiz-class tunables - confirms pass-1's Low). Fix: mirror the sibling gate (CHECKIO+p_trespass).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3259 Β· 8 files
FileTypeDescriptionSize
rlimit_authz.c β€” 3.0 KB view raw
build.sh β€” 91 B view raw
run.sh β€” 97 B view raw
run.log β€” 1.1 KB view raw
run.2.log β€” 99 B view raw
fix.diff β€” 354 B view raw
VERDICT.md β€” 2.8 KB ↓ raw
env.txt β€” 122 B view raw
VERDICT.md
↓ download raw

DF-3259 VERDICT β€” /proc/pid/rlimit missing per-op authorization

Status: REPRODUCED (authorization bypass / cross-uid info disclosure), Low severity

Root cause

sys/vfs/procfs/procfs_rlimit.c:55-110 β€” procfs_dorlimit() contains no credential check of any kind. The only filter is lookup-time process visibility: sys/vfs/procfs/procfs_vnops.c:809-811,833 hide other-uid pid directories only when security.ps_showallprocs == 0; guest default is security.ps_showallprocs: 1 (verified). Every comparable procfs op re-checks CHECKIO(curp, p) || p_trespass(curp->p_ucred, p->p_ucred) per operation (procfs_regs.c:60, procfs_fpregs.c:59, procfs_dbregs.c:62, procfs_mem.c:189, procfs_ctl.c:126); rlimit is the outlier. This is the DF-3224 per-op lesson applied in reverse: the gate is not merely open-time β€” it is absent entirely.

Observation

uid=1001 (maxx) opened and read /proc/1/rlimit (init, uid 0): 209 bytes, full soft/hard limit table of the root process (run.log, run.2.log β€” reproduced twice, stable). Discloses limit-based fingerprinting of security daemons (e.g. memlock/nofile of privilege-separated services).

Impact ceiling

Low: numeric configuration disclosure of arbitrary local processes. No memory-safety impact; no privilege escalation. (Linux gates the same file behind PTRACE_MODE_READ; the info is considered worth gating there.)

Fix validation

Not kernel-rebuilt for this finding (Low severity, authorization gate only β€” the orchestrator's Phase V budget went to the DB-3258 fix build). fix.diff adds the exact sibling gate at procfs_rlimit.c:63; pattern is used verbatim at five neighboring call sites. fix_status: not_testable (no rebuild), with the diff provided for the upstream PR.

Secondary result (DF-0935 boundary, empirical)

The same PoC maximizes all 12 rlimits to the highest values an unprivileged process can reach (hard-limit lowering to 9223372036854775806 is unprivileged β€” kern_plimit.c:294-303 β€” and the kernel then clamps data/stack/nofile/nproc/posixlocks to maxdsiz=34359738368, maxssiz=536870912, maxfilesperproc=32528, maxprocperuid=2033, maxposixlocksperuid=32528 β€” kern_plimit.c:316-379). Resulting formatter output: 431 bytes (predicted == actual), under psbuf[512] by 81 bytes. DF-0935 (unfixed in tree) therefore remains unreachable with default caps β€” confirming pass-1; overflow requires relaxing maxdsiz/maxssiz-type loader tunables (root), after which the reader must merely read a suitably-limited root process's rlimit.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Low-severity authorization gate; kernel rebuild budget spent on the DF-3258 fix validation. fix.diff provided (sibling-pattern gate); not compiled into the test kernel.

[]
↓ fix.diffper-fix-DF-3259

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'uid=1001 read /proc/1/rlimit: 209 bytes' + full root limit table
run.2.log: second reproduction, identical 209 bytes
run.log tail: 'predicted max formatter output: 431 bytes ... actual /proc/self/rlimit length: 431 bytes'

PoC changes

none β€” written fresh for this finding

Verified recommended fix

Add CHECKIO(curp,p) || p_trespass(curp->p_ucred, p->p_ucred) EPERM gate at the top of procfs_dorlimit

Verdict

procfs_dorlimit has no CHECKIO/p_trespass per-op gate (unique among procfs ops), and security.ps_showallprocs defaults to 1, so unprivileged uid=1001 read 209 bytes of init's (uid 0) full rlimit table from /proc/1/rlimit β€” reproduced twice on stock kernel and again on the patched (DF-3258-fix) kernel. Low-impact cross-uid configuration disclosure; sibling gate added in fix.diff (not kernel-rebuilt for this Low finding). Secondary empirical result: unpriv-maximized formatter output is exactly 431 bytes < psbuf[512], confirming DF-0935 is unreachable with default kernel caps (clamps: maxdsiz=32G, maxssiz=512M, nofile=32528, nproc=2033, posixlock=32528).