ctl DETACH is exempt from all authorization: any same-euid bystander can rip a victim out of an active tracer's session (clear P_TRACED, delete pending SIGTRAP, reparent, unstop); wakeup(curp) also never wakes a blocked CTL_WAIT tracer
Summary
procfs_control skips the entire credential gate for DETACH ('if (op != PROCFS_CTL_DETACH)', :122). The exemption's stated purpose (a tracer that lost permission may disengage) only justifies it for the current tracer, but it applies to anyone who can open the ctl file (any same-euid process, or a pre-opened fd across cred changes). A bystander's 'detach' clears P_TRACED, deletes the pending SIGTRAP, reparents to p_oppid, clears P_WAITED and unstops the victim - silently terminating a live (possibly root-run) debugging/monitoring session; the tracer's subsequent ctl ops fail EBUSY. Additionally wakeup((caddr_t)curp) at :223 wakes the detacher's pointer while a CTL_WAIT tracer sleeps on (caddr_t)p (:258), so third-party detaches never wake it. VERIFIED uid 1001: tracer T attaches victim V; bystander D writes 'detach' -> session destroyed, T's step fails EBUSY. Fix validated (DETACH restricted to current tracer per PT_DETACH + wakeup(p)): D gets EPERM, T's step succeeds. Same-uid kill(2) already permits coarser damage, hence Low.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3226 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3226.c | β | 2.9 KB | view raw | |
| build.sh | β | 79 B | view raw | |
| run.sh | β | 89 B | view raw | |
| run.log | β | 267 B | view raw | |
| run.2.log | β | 267 B | view raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| README.md | β | 2.7 KB | β raw | |
| fix.diff | β | 794 B | view raw | |
| verdict.json | β | 3.4 KB | view raw |
DF-3226 β /proc/pid/ctl "detach" requires no authorization: bystanders can rip a tracee out of a live tracing session
File: sys/vfs/procfs/procfs_ctl.c:122-128 (authz exemption), :197-225 (DETACH body), :223 (wrong wakeup address)
Severity: Low β same-euid interference with active debuggers/tracers (CWE-862)
Status: REPRODUCED on DragonFly 6.5-DEVELOPMENT #0, two runs.
What the bug is
procfs_control() deliberately skips the credential gate for DETACH:
if (op != PROCFS_CTL_DETACH) { /* procfs_ctl.c:122 */
... CHECKIO / p_trespass / securelevel checks ...
}
The stated intent ("allow processes to disengage debugging on a process onto
which they have previously attached, but no longer have permission to debug")
only justifies the exemption for the tracer itself β but the code grants
it to anyone who can open the ctl file (any same-euid process; mode 0200,
owner = target euid). A bystander that never attached anything can write
"detach" to /proc/<victim>/ctl and the kernel will, with no
authorization:
- clear
P_TRACED(procfs_ctl.c:203), - delete the victim's pending SIGTRAP (:206-208),
- reparent the victim back to
p_oppid(:211-219), - clear
P_WAITEDand unstop the victim (:222, :280-281),
i.e. silently terminate an active debugging/tracing session run by another (same-euid, or root) tracer β the tracer's subsequent ctl ops fail EBUSY and a root debugger (or any ptrace-based monitoring/sandbox tool holding the session via procfs semantics) loses the tracee mid-flight.
Secondary defect on the same path: wakeup((caddr_t) curp) at :223 wakes a
address derived from the detacher, while a tracer blocked in CTL_WAIT
tsleeps on (caddr_t) p (:258) β a detach issued by anyone other than the
WAITer's own process never wakes the blocked tracer (it hangs until a signal
breaks the PCATCH sleep).
Reproduced evidence (run.log)
[T 1101] ctl attach V(1100) [D 1102] bystander writes detach to /proc/1100/ctl [D] DETACH ACCEPTED with no authorization [T] ctl step V #1 after interference: failed: Device busy [T] ctl run V: failed: Device busy
Bystander D (uid 1001, never attached anything, not the tracer, not the parent) successfully detached the victim from tracer T's live session; T's next ctl ops fail EBUSY. Reproduced twice (run.2.log after clean-snapshot reset).
Files
df3226.cβ PoC (all processes unprivileged uid 1001)run.log,run.2.logβ full output, two runsfix.diffβ verified fix (exempt DETACH only for the current tracerp->p_pptr == curp; wakepnotcurp)verdict.json,manifest.json
Build & run
cc -O -o df3226 df3226.c ./df3226
DF-3226 VERDICT β REPRODUCED (unauthorized detach of a live tracee by a same-uid bystander)
Bottom line
procfs_control() skips ALL authorization for PROCFS_CTL_DETACH
(sys/vfs/procfs/procfs_ctl.c:122). Any process that can open
/proc/<victim>/ctl (mode 0200 β any same-euid process, or anyone holding a
pre-opened fd across the victim's cred changes) β not just the tracer β can
detach a victim from its active tracer: P_TRACED cleared, pending SIGTRAP
deleted, victim reparented to p_oppid and unstopped. The tracer (possibly
root β a debugger or a ptrace-style monitoring/sandbox session on a uid-U
victim) silently loses the tracee; its subsequent ctl operations fail EBUSY.
Demonstrated twice as uid 1001 (run.log, run.2.log).
Additionally, wakeup((caddr_t) curp) at procfs_ctl.c:223 uses the
detacher's proc pointer as the wake address while a tracer blocked in
CTL_WAIT sleeps on (caddr_t) p (procfs_ctl.c:258) β a third-party detach
never wakes the blocked tracer.
Observed (run.log)
[T 1101] ctl attach V(1100) [D 1102] bystander writes detach to /proc/1100/ctl [D] DETACH ACCEPTED with no authorization [T] ctl step V #1 after interference: failed: Device busy [T] ctl run V: failed: Device busy
D was never the tracer, never the parent, never attached anything: the write was accepted and destroyed T's session. Same output on the second run after a clean-snapshot reset.
Impact
Integrity/availability of debugging and ptrace/procfs-based supervision sessions: a malicious same-uid process can unstick a victim from analysis or containment at will (break root-run debuggers, evade procfs-style tracing tools). No memory corruption; no uid0 route. Low severity (same-euid scope via the open gate), certain confidence.
Fix
fix.diff (v2, the validated version): DETACH of a P_TRACED process is
restricted to the current tracer (p->p_pptr == curp β the tracer may still
disengage after privilege loss, the original intent; untraced targets keep
the harmless no-op path), mirroring PT_DETACH (sys_process.c:250-262), plus
the wake-address fix (wakeup((caddr_t) p)).
Validation note: a first fix attempt (v1, simply applying
CHECKIO/p_trespass to non-tracer DETACH β kept as run.fixed.log) was
insufficient: CHECKIO legitimately passes for same-uid bystanders, so
the bystander detach was still accepted. v2 (tracer-only) is what was
re-validated on a second patched kernel: bystander β EPERM, tracer's
session intact (run.fixed2.log).
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
fixedBaseline: bystander detach accepted, tracer session destroyed (EBUSY afterwards). Patched v2: bystander detach refused EPERM, tracer's step succeeds (run.fixed2.log).
findings/poc/DF-3226/run.fixed2.log (v2 pass); run.fixed.log kept as the documented v1-insufficient iteration
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log/run.2.log: '[D 1102] bystander writes detach to /proc/1100/ctl', '[D] DETACH ACCEPTED with no authorization', '[T] ctl step V #1 after interference: failed: Device busy', '[T] ctl run V: failed: Device busy'. run.fixed2.log (patched): '[D] detach failed: Operation not permitted', '[T] ctl step V #1 ... ok'.
PoC changes
PoC written fresh (tracer T, victim V, bystander D, all uid 1001).
Verified recommended fix
Restrict DETACH of a P_TRACED process to the current tracer (p->p_pptr == curp), mirroring PT_DETACH; wakeup((caddr_t) p) instead of curp for CTL_WAIT.
Verdict
REPRODUCED on DragonFly 6.5-DEVELOPMENT #0, two boots. procfs_control() exempts DETACH from ALL authorization (procfs_ctl.c:122), so any process that can open /proc/
No comments yet.