From 5b4082d00745b68dc89be0a0aadfedfc0f1660f2 Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Sat, 9 Oct 2021 19:54:36 +0800 Subject: [PATCH] su: Fix never alarmed SIGKILL when session terminates The buggy code was introduced nearly 5 years ago at the commit 08fd4b69e84364677a10e519ccb25b71710ee686. The desired behavior is that SIGKILL will be sent to the child if it does not exit within 2 seconds after it receives SIGTERM. However, SIGALRM is masked while waiting for the child so it cannot wake the program up after 2 seconds to send SIGKILL. An example shows the buggy behavior, which exists in Ubuntu 18.04 LTS (with login 1:4.5-1ubuntu2). ```bash user1@localhost:~$ su user2 -c ' _term() { echo SIGTERM received } trap _term TERM while true; do sleep 1 echo still alive done' Password: still alive Session terminated, terminating shell...Terminated SIGTERM received still alive still alive still alive still alive ``` (SIGTERM is sent in another user1's terminal by executing `killall su`.) Here is the desired behavior, which shows what the commit fixes. ```bash user1@localhost:~$ su user2 -c ' _term() { echo SIGTERM received } trap _term TERM while true; do sleep 1 echo still alive done' Password: still alive Session terminated, terminating shell...Terminated SIGTERM received still alive still alive ...killed. user1@localhost:~$ echo $? 255 ``` --- src/su.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/su.c b/src/su.c index df4b70ea..bbf6d8e2 100644 --- a/src/su.c +++ b/src/su.c @@ -397,22 +397,28 @@ static void prepare_pam_close_session (void) snprintf (kill_msg, sizeof kill_msg, _(" ...killed.\n")); snprintf (wait_msg, sizeof wait_msg, _(" ...waiting for child to terminate.\n")); + /* Any signals other than SIGCHLD and SIGALRM will no longer have any effect, + * so it's time to block all of them. */ + sigfillset (&ourset); + if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) { + fprintf (stderr, _("%s: signal masking malfunction\n"), Prog); + kill_child (0); + /* Never reach (_exit called). */ + } + + /* Send SIGKILL to the child if it doesn't + * exit within 2 seconds (after SIGTERM) */ (void) signal (SIGALRM, kill_child); (void) signal (SIGCHLD, catch_signals); (void) alarm (2); - sigemptyset (&ourset); - if ((sigaddset (&ourset, SIGALRM) != 0) - || (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0)) { - fprintf (stderr, _("%s: signal masking malfunction\n"), Prog); - kill_child (0); - } else { - while (0 == waitpid (pid_child, &status, WNOHANG)) { - sigsuspend (&ourset); - } - pid_child = 0; - (void) sigprocmask (SIG_UNBLOCK, &ourset, NULL); + (void) sigdelset (&ourset, SIGALRM); + (void) sigdelset (&ourset, SIGCHLD); + + while (0 == waitpid (pid_child, &status, WNOHANG)) { + sigsuspend (&ourset); } + pid_child = 0; (void) fputs (_(" ...terminated.\n"), stderr); }