7

I'm trying to figure out why this doesn't work:

#!/bin/bash

sudo sleep 60 &
sudo_pid=$!
sudo kill $sudo_pid

I'd expect that after the kill, the sudo command and its child sleep process would be terminated, but they aren't, as shown by this script:

#!/bin/bash

sudo sleep 60 &
sudo_pid=$!
sudo kill $sudo_pid

if ps -p $sudo_pid > /dev/null; then
    sudo kill $sudo_pid
else
    echo "No sudo process running"
    exit 1
fi

if ps -p $sudo_pid > /dev/null; then
    echo "sudo (pid $sudo_pid) is still running"
    ps -F $sudo_pid
else
    echo "sudo successfully killed"
fi

Which yields this output when I run it (with sudo creds cached):

jon@ubuntu:~$ ./so.sh 
sudo (pid 46199) is still running
UID         PID   PPID  C    SZ   RSS PSR STIME TTY      STAT   TIME CMD
root      46199  46198  0 14764  3984   3 13:37 pts/0    S+     0:00 sudo sleep 60

After the script completes (and sudo sleep 60 is still running), it is possible to kill it with the identical command:

jon@ubuntu:~$ ps -F 46199
UID         PID   PPID  C    SZ   RSS PSR STIME TTY      STAT   TIME CMD
root      46199      1  0 14764  3984   3 13:37 pts/0    S      0:00 sudo sleep 60

jon@ubuntu:~$ sudo kill 46199

jon@ubuntu:~$ ps -F 46199
UID         PID   PPID  C    SZ   RSS PSR STIME TTY      STAT   TIME CMD

jon@ubuntu:~$

I notice that after the so.sh script exits, the parent process ID for sudo sleep 60 has changed from the script to the init process, which I think is significant. It's also possible to successfully kill the sudo sleep 60 process from a different shell while the so.sh script is still running.

I also noticed that using sudo kill -ABRT in the script (as opposed to kill's default SIGTERM) does successfully kill the sudo process, so I assume that is has something to do with the way sudo handles SIGTERM. However, based on the man page, I don't think it should be doing anything special:

Signal handling
  When the command is run as a child of the sudo process, sudo will relay
  signals it receives to the command.  The SIGINT and SIGQUIT signals are
  only relayed when the command is being run in a new pty or when the sig‐
  nal was sent by a user process, not the kernel.  This prevents the com‐
  mand from receiving SIGINT twice each time the user enters control-C.

The only special handling of SIGTERM mentioned in the man page is for signals that were sent by the command it is running; not the case here. Furthermore, I changed the ps -F in above script to ps -o blocked,caught,ignored,pending and it output

sudo (pid 46429) is still running
         BLOCKED           CAUGHT          IGNORED          PENDING
0000000000000000 00000001800b7a07 0000000000000000 0000000000000000

Which seems to indicate that SIGTERM isn't being blocked or ignored, so why isn't the sudo sleep 60 process getting terminated?

I've come up with several workarounds (setsid, sudo -b, killing the child sleep process rather than the parent sudo process), so I'm not looking for alternate approach answers. I just want to understand what's going on here.

In case it matters:

jon@ubuntu:~$ uname -a
Linux ubuntu 4.13.0-16-generic #19-Ubuntu SMP Wed Oct 11 18:35:14 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
jon@ubuntu:~$ sudo --version
Sudo version 1.8.20p2
Sudoers policy plugin version 1.8.20p2
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.20p2
Jon
  • 71
  • 3

3 Answers3

4

I found following comments in source code of sudo

  /*  
 * Do not forward signals sent by a process in the command's process
 * group, as we don't want the command to indirectly kill itself.
 * For example, this can happen with some versions of reboot that
 * call kill(-1, SIGTERM) to kill all other processes.
 */
4

If you will change your sudo kill $sudo_pid to setsid sudo kill $sudo_pid it will work.

Found this here: starting a new process group from bash script

omribahumi
  • 2,011
  • 1
  • 17
  • 19
1

The signal mask bit for any given signal is:

(1ULL << ((signal_number) - 1))

(for standard signals in the 1-32 range anyway; additional signals are in the "real time" signal sets and are handled somewhat differently, though the same concept applies). So the interesting part of the CAUGHT mask is:

...7a07

which is (+ for caught, - for not caught, in the expanded part):

xxx7: signals 1, 2, 3, but not 4: +SIGHUP  +SIGINT  +SIGQUIT -SIGILL
xx0x: not 5-8:                    -SIGTRAP -SIGABRT -SIGBUS  -SIGFPE
xaxx: not 9, 10, not 11, 12:      -SIGKILL +SIGUSR1 -SIGSEGV +SIGUSR2
7xxx: 13, 14, 15, not 16:         +SIGPIPE +SIGALRM +SIGTERM -SIGSTKFLT

(you can continue decoding the rest if you like; see /usr/include/asm-generic/signal.h for the Linux-specific signal numbers; note that the numeric definitions differ on OSX and BSD but the technique is the same: caught or blocked or whatever signals are represented as 1-bits in the mask).

So, this means sudo is catching SIGTERM but not catching SIGABRT. The fact that SIGTERM is not passed on must have something to do with the sudo code itself. The source (apt-get source sudo) has some rather complicated code for doing the signal handling, including some interesting debug tricks you can turn on in the sudo config file to help you track down what's going on.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for the additional info on the signals! It makes sense that `sudo` is catching `SIGTERM` since it doesn't propagate that signal when it originates with `sudo`'s child process. I guess I'll have to dig into the source (thanks for that apt pointer) if I really want to know. – Jon Dec 29 '17 at 23:21