]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
su: Fix never alarmed SIGKILL when session terminates 423/head
authorRuihan Li <lrh2000@pku.edu.cn>
Sat, 9 Oct 2021 11:54:36 +0000 (19:54 +0800)
committerlrh2000 <lrh2000@pku.edu.cn>
Mon, 25 Oct 2021 05:39:41 +0000 (13:39 +0800)
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

index df4b70ea55592210eb9b72119340ccec4987df70..bbf6d8e2207539819c46eef8497f2bce8d915124 100644 (file)
--- 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);
        }