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

postsig() KASSERT panic: unlocked ps_sigact[] read races concurrent sigaction(SIG_IGN) from another LWP

Field Value
ID DF-2693
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-367 TOCTOU
File sys/kern/kern_sig.c
Lines 2281 (unlocked read), 2309-2310 (KASSERT)
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

userret() decides deliverability via CURSIG (from p_sigcatch), then postsig() re-reads ps_sigact[sig] (:2281) without p_token when the signal was lwp-pending (haveptok==0 β€” the common routing for kill() to multithreaded procs). A concurrent sigaction(SIG_IGN) (under p_token) makes action==SIG_IGN and the KASSERT at :2309-2310 fires. On non-INVARIANTS kernels the thread merely jumps to SIG_IGN (address 1) β€” self-inflicted SIGSEGV.

Threat model & preconditions

Unprivileged local user panics INVARIANTS/debug kernels (the shipped X86_64_GENERIC build target of this audit).

Proof of concept

VERIFIED (findings/poc/DF-2693/postsig_race.c): 4 threads loop kill(getpid(), SIGUSR1), 4 threads loop sigaction(SIGUSR1, SIG_IGN↔handler) β†’ panic: postsig action within 150s; gdb pins postsig+0x3ae to exactly kern_sig.c:2309.

Replace the KASSERT with graceful handling of the lost race (if (action == SIG_IGN || SIGISMEMBER(lp->lwp_sigmask, sig)) return;) β€” fix.diff applies cleanly (not kernel-rebuilt for this Low/DoS finding).

Timeline

  • 2026-08-30 Discovered during pass-2 audit of kern_sig.c (GLM 5.3); reproduced on stock same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2693 Β· 9 files
FileTypeDescriptionSize
postsig_race.c β€” 2.5 KB view raw
build.sh β€” 115 B view raw
run.sh β€” 83 B view raw
run.log β€” 1.9 KB view raw
panic.txt β€” 522 B view raw
README.md β€” 2.8 KB ↓ raw
fix.diff β€” 835 B view raw
verdict.json β€” 2.5 KB view raw
manifest.json β€” 708 B view raw

DF-2693 β€” postsig() KASSERT race with concurrent sigaction(SIG_IGN)

File: sys/kern/kern_sig.c β€” postsig() (lines 2253-2356) Class: CWE-367 TOCTOU on signal disposition β†’ INVARIANTS kernel panic Reach: unprivileged local user (own process, two or more threads) Verdict: REPRODUCED on the stock INVARIANTS kernel (panic: postsig action, symbolically pinned to kern_sig.c:2309).

Root cause

userret() (trap.c:278-281) decides deliverability with CURSIG_LCK_TRACE() β€” based on p_sigcatch β€” and then calls postsig(sig, ptok). For a signal that was pending on the lwp list (the common case: lwpsignal() routes kill()-style process signals to a specific LWP, kern_sig.c:1425-1428), haveptok == 0 and postsig() reads

action = ps->ps_sigact[_SIG_IDX(sig)];           /* kern_sig.c:2281 */

without holding p->p_token. A concurrent sigaction(sig, SIG_IGN) from another thread of the same process takes p_token (kern_sig.c:258-377), clears p_sigcatch, deletes the pending signal from all lists, and stores SIG_IGN into ps_sigact[sig]. Interleaving the two yields action == SIG_IGN at the

KASSERT(action != SIG_IGN && !SIGISMEMBER(lp->lwp_sigmask, sig),
    ("postsig action"));                          /* kern_sig.c:2309-2310 */

β†’ panic: postsig action on INVARIANTS kernels. The same race is visible from the other side as issignal()'s "should not hit signal %d!" warning (kern_sig.c:2210-2220) β€” both messages appear interleaved on the panicked console.

Reproduction

  • build.sh: cc -O2 -Wall -pthread -o postsig_race postsig_race.c
  • run.sh: ./postsig_race 150 as an unprivileged user
  • Expected (stock INVARIANTS kernel): panic: postsig action, stack postsig+0x3ae ← userret ← syscall2. Hit within 150 s in both attempts.

Impact

  • INVARIANTS/debug kernels (like the shipped X86_64_GENERIC target of this audit): reliable-enough unprivileged kernel panic = local DoS.
  • Stock/production kernels: sv_sendsig installs handler == SIG_IGN ((void *)1); the thread returns to userland at address 1 and takes a self-inflicted SIGSEGV. No cross-privilege impact β€” the process only kills itself β€” but signal-state corruption (mask/handler mismatch) is possible.

Fix

fix.diff: tolerate the lost race instead of asserting β€” if the handler became SIG_IGN (or the signal became masked) between the CURSIG decision and now, simply return (the racing sigaction() already cleared the pending state; the token bookkeeping is already finished at that point). Not kernel-rebuilt (Low/DoS class): fix_status=not_tested.

References

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff authored (KASSERT -> graceful return) and verified to apply cleanly, but no fixed kernel was built for this Low/DoS finding within this run.

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

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log - stock-kernel run, panic: postsig action, gdb line confirmation', "panic.txt - console capture incl. interleaved 'should not hit signal 30!' (issignal's warning, same race)", 'postsig_race.c - 4 sender threads + 4 flipper threads']

PoC changes

none - the sketch-level race program worked as designed on the first stock-kernel attempt.

Verified recommended fix

Replace the KASSERT with graceful handling: if action == SIG_IGN or the signal became masked between the CURSIG decision and now, simply return (the racing sigaction already cleared pending state)

Verdict

postsig() reads ps_sigact[] without p_token when the delivered signal was lwp-pending (haveptok==0, the common routing for kill() to multithreaded processes), racing a concurrent sigaction(SIG_IGN) from another thread; the KASSERT at kern_sig.c:2309 fires. Reproduced on the stock INVARIANTS kernel as an unprivileged 8-thread program: panic 'postsig action' within 150 s, stack postsig+0x3ae <- userret <- syscall2, symbolically confirmed via gdb to be exactly kern_sig.c:2309 (postsig+928..944). On non-INVARIANTS kernels the race degrades to the process jumping to SIG_IGN (address 1) - self-inflicted SIGSEGV, no cross-privilege impact.