diff --git a/include/core/defs.h b/include/core/defs.h index 5095a9e..58aa1bc 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -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; diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index 99fa3c4..a562127 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -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; diff --git a/kernel/sys/sys_sig.c b/kernel/sys/sys_sig.c index 234c643..fb94294 100644 --- a/kernel/sys/sys_sig.c +++ b/kernel/sys/sys_sig.c @@ -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; }