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

Write-path SSTOP check diverges from LSSTOP check used by fpregs/dbregs siblings

Field Value
ID DF-0937
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L
CWE CWE-362 Race Condition
File sys/vfs/procfs/procfs_regs.c
Lines 68, 71
Area vfs
Confidence likely
Discovered 2026-07-05
Reported pending
Known CVE none
CVE match dfly_specific

Summary

procfs_doregs gates PT_SETREGS-style writes on p->p_stat == SSTOP, whereas the immediately adjacent procfs_dofpregs (sys/vfs/procfs/procfs_fpregs.c:67) and procfs_dodbregs (sys/vfs/procfs/procfs_dbregs.c:69) gate on lp->lwp_stat == LSSTOP. proc_stop (sys/kern/kern_sig.c:1565-1641) sets p->p_stat = SSTOP at line 1584 BEFORE iterating LWPs, and for LWPs that are LSRUN merely calls lwp_signotify (line 1619) so they stop asynchronously. The chosen first LWP (sys/vfs/procfs/procfs_subr.c:375) can therefore still be LSRUN (or LSSLEEP with LWP_MP_WSTOP, not yet at a tstop()) at the moment p_stat is observed SSTOP, so the write at line 71 can hit a trapframe whose owning LWP is not yet actually stopped.

Root cause

At sys/vfs/procfs/procfs_regs.c:67-72 the gate is:

if (p->p_stat != SSTOP)
    error = EBUSY;
else
    error = procfs_write_regs(lp, &r);

The sibling implementations at sys/vfs/procfs/procfs_fpregs.c:66-71 and sys/vfs/procfs/procfs_dbregs.c:68-73 instead use:

if (lp->lwp_stat != LSSTOP)
    error = EBUSY;

proc_stop() at sys/kern/kern_sig.c:1584 sets p_stat=SSTOP and then, in the loop at lines 1586-1624, only schedules still-running LWPs to stop (LSRUN β†’ lwp_signotify at :1619; LSSLEEP β†’ set LWP_MP_WSTOP at :1609). Thus there is a real window in which p_stat==SSTOP holds but the first LWP is LSRUN.

The machine-dependent contract documented at the top of sys/platform/pc64/x86_64/procfs_machdep.c (lines 45 and 53: "The process is stopped at the time read_regs/write_regs is called") is LWP-local, so the LWP-level check is the correct one.

Threat model & preconditions

  • Attacker position: Local, same-uid (must pass CHECKIO + p_trespass at line 60).
  • Privileges gained or impact: No privilege boundary is crossed and no information is leaked beyond what same-uid ptrace already grants. Demonstrable impact is correctness, not confidentiality/integrity of another uid: the trapframe written via set_regs (sys/platform/pc64/x86_64/machdep.c:3020-3031) is concurrent with the LWP still running on another CPU, so when that LWP eventually returns to userspace its registers reflect the write rather than the state it was actually stopped in.
  • Required config or capabilities: Same-uid + the ability to send SIGSTOP to the target.
  • Reachability: fork() + pthread_create() (second LWP) + kill(child, SIGSTOP) + pwrite(/proc/<child>/regs, …).
  • P_SUGID in CHECKIO and P_TRACED in exec (sys/kern/kern_exec.c:487) rule out using this to corrupt a setuid exec.

Proof of concept

No privilege-gain exploit exists. To demonstrate the divergence vs. the siblings, build and run a small program on DragonFlyBSD that forks a child which creates a second LWP (pthread_create + pthread_spin lock to keep the new LWP running), then has the parent send SIGSTOP to the child and immediately pwrite() into /proc/<child>/regs at offset offsetof(struct reg, r_rip) (struct reg from <machine/reg.h>). On a sufficiently loaded box the write succeeds even though the first LWP's lwp_stat is not yet LSSTOP, whereas writing /proc/<child>/fpregs or /proc/<child>/dbregs at the same instant returns EBUSY.

Reproducibility is timing-dependent and the observable effect is only that RIP/RSP are now whatever the parent chose (which the parent was already authorized to do via set_regs once the LWP settles). This is reported as Info because no uid/capability boundary is crossed.

Impact

No confidentiality or integrity impact beyond same-uid ptrace's existing capabilities. The write can land in a trapframe whose LWP is not yet stopped β€” a correctness issue for the tracing API, not a security primitive.

Make the gate LWP-local to match the documented machine-dependent contract and the fpregs/dbregs siblings.

--- a/sys/vfs/procfs/procfs_regs.c
+++ b/sys/vfs/procfs/procfs_regs.c
@@ -65,7 +65,7 @@ procfs_doregs(struct proc *curp, struct lwp *lp, struct pfsnode *pfs,
    if (error == 0)
        error = uiomove_frombuf(&r, sizeof(r), uio);
    if (error == 0 && uio->uio_rw == UIO_WRITE) {
-       if (p->p_stat != SSTOP)
+       if (lp->lwp_stat != LSSTOP)
            error = EBUSY;
        else
            error = procfs_write_regs(lp, &r);

References

Timeline

  • 2026-07-05 Discovered during automated audit.
  • pending Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0937 Β· 1 files
FileTypeDescriptionSize
fix.diff suggested-fix Write-path SSTOP check diverges from LSSTOP check used by fpregs/dbregs siblings 396 B view raw

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff authored but did not apply cleanly; needs context rework

fix.diff authored but did not apply cleanly; needs context rework
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Info severity)

Evidence (decisive lines)

Source-confirmed at sys/vfs/procfs/procfs_regs.c:68: write-path SSTOP check diverges from LSSTOP check used by fpregs/dbregs

Verified recommended fix

Source-confirmed at sys/vfs/procfs/procfs_regs.c:68: write-path SSTOP check diverges from LSSTOP check used by fpregs/dbregs

Verdict

Source-confirmed at sys/vfs/procfs/procfs_regs.c:68: write-path SSTOP check diverges from LSSTOP check used by fpregs/dbregs