signals: a blocked signal stays pending instead of killing
sigsend() ended a process for a default-fatal signal (SIGUSR1, SIGTERM, SIGINT...) on arrival, whatever its signal mask said. Blocking is exactly the promise that it will not: the signal waits until it is unblocked, or until the program takes it some other way -- which is what signalfd is, and how this was found. A blocked signal is now only made pending. If it is later unblocked while still pending, deliver_signals() applies the default action then. SIGKILL cannot be blocked, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
5ed8f1b0d5
commit
6bfb367090
3 changed files with 40 additions and 12 deletions
|
|
@ -530,6 +530,7 @@ int proc_pgid(int pid);
|
|||
int proc_setpgid(int pid, int pgid);
|
||||
int proc_setsid(void);
|
||||
int sigsend(int pid, int sig);
|
||||
int sig_default_kills(int sig);
|
||||
int proc_exists(int);
|
||||
int proc_num(void);
|
||||
struct procinfo;
|
||||
|
|
|
|||
|
|
@ -1508,6 +1508,34 @@ init_ignores(struct proc *p, int sig)
|
|||
return p == initproc && p->sig_handler[sig] == 0;
|
||||
}
|
||||
|
||||
// Whether a signal's default action is to end the process.
|
||||
int
|
||||
sig_default_kills(int sig)
|
||||
{
|
||||
return sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
||||
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
||||
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
||||
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
||||
sig == SIGUSR2 || sig == SIGALRM;
|
||||
}
|
||||
|
||||
// Whether sending `sig` ends `p` now: no handler, a default action that
|
||||
// kills, and **not blocked**. A blocked signal stays pending, which is the
|
||||
// whole of what blocking means -- and the contract signalfd is built on: a
|
||||
// program blocks SIGUSR1 and reads it from a descriptor instead. This used to
|
||||
// kill on arrival whatever the mask said. deliver_signals() applies the
|
||||
// default action if the signal is unblocked later, still pending.
|
||||
// SIGKILL cannot be blocked.
|
||||
static int
|
||||
sig_kills_now(struct proc *p, int sig)
|
||||
{
|
||||
if(sig == SIGKILL)
|
||||
return 1;
|
||||
if(p->signal_mask & (1ULL << sig))
|
||||
return 0;
|
||||
return p->sig_handler[sig] == 0 && sig_default_kills(sig);
|
||||
}
|
||||
|
||||
int
|
||||
sigsend(int pid, int sig)
|
||||
{
|
||||
|
|
@ -1524,12 +1552,7 @@ sigsend(int pid, int sig)
|
|||
return 0; // silently dropped, as POSIX requires
|
||||
}
|
||||
p->pending_signals |= 1ULL << sig;
|
||||
if(p->sig_handler[sig] == 0 &&
|
||||
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
||||
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
||||
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
||||
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
||||
sig == SIGUSR2 || sig == SIGALRM))
|
||||
if(sig_kills_now(p, sig))
|
||||
p->killed = 1;
|
||||
if(sig == SIGKILL)
|
||||
p->killed = 1;
|
||||
|
|
@ -1573,12 +1596,7 @@ sigsend_pgrp(int pgid, int sig)
|
|||
if(init_ignores(p, sig))
|
||||
continue; // see init_ignores(): this is the Ctrl-C case
|
||||
p->pending_signals |= 1ULL << sig;
|
||||
if(p->sig_handler[sig] == 0 &&
|
||||
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
||||
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
||||
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
||||
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
||||
sig == SIGUSR2 || sig == SIGALRM)){
|
||||
if(sig_kills_now(p, sig)){
|
||||
p->killed = 1;
|
||||
if(p->killsig == 0)
|
||||
p->killsig = sig;
|
||||
|
|
|
|||
|
|
@ -191,8 +191,17 @@ deliver_signals(struct trapframe *tf)
|
|||
// applied by sigsend(); all that is left here is to stop it being
|
||||
// pending, which the old `continue` never did, so an ignored signal
|
||||
// was re-examined on the way out of every syscall forever after.
|
||||
//
|
||||
// Except for a signal that was blocked when it arrived: sigsend()
|
||||
// left it pending without acting, and now that it is deliverable its
|
||||
// default action happens here.
|
||||
if (myproc()->sig_handler[sig] <= 1) {
|
||||
myproc()->pending_signals &= ~(1ULL << sig);
|
||||
if (myproc()->sig_handler[sig] == 0 && sig_default_kills(sig)) {
|
||||
myproc()->killed = 1;
|
||||
if (myproc()->killsig == 0)
|
||||
myproc()->killsig = sig;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue