Sudo - Waiting for child, and getting child PID - linux

It seems that somewhere between sudo 1.7.2p2 and 1.7.4p5 the behaviour for waiting for executing processes has changed. It looks like in the older versions sudo would start the new process, and then quit. In the newer versions it starts the new process, and then waits for it. There is a bit of a discussion about it here: http://www.sudo.ws/pipermail/sudo-users/2010-August/004461.html which mentions that it is to stop it from breaking PAM session support.
This change is breaking one of my scripts which uses sudo to execute commands in the background, as with the older version sudo the command I want to execute would be backgrounded, and with the new version it is sudo itself that is backgrounded.
For example, the process returned by $! in this case is for sleep
user#localhost$ sudo -V
Sudo version 1.7.2p2
user#localhost$ sudo -u poweruser sleep 60 &
[1] 17491
user#localhost$ ps -fp $!
UID PID PPID C STIME TTY TIME CMD
poweruser 17491 17392 0 16:43 pts/0 00:00:00 sleep 60
Whereas in this case it is for sudo
user#localhost$ sudo -V
Sudo version 1.7.4p5
user#localhost$ sudo -u poweruser sleep 60 &
[1] 792
user#localhost$ ps -fp $!
UID PID PPID C STIME TTY TIME CMD
root 792 29257 0 16:42 pts/3 00:00:00 sudo -u poweruser sleep 60
Is it possible to get the process ID for a child process executed by sudo version 1.7.4p5? The $! variable returns the PID for sudo, and running sudo with the -b option doesn' seem to make the child PID available. Is it possible (without recompiling sudo) to revert the behaviour of sudo to stop it from waiting for child processes?
Thanks

This is certainly a hack, and it doesn't set $!, but you can echo the pid of the command:
$ sudo sh -c 'echo $$; exec sleep 60'
I'm guessing that your explanation of the old behavior is not quite right and that sudo simply exec'd the command rather than forking and exiting. Echoing the pid and then exec'ing the desired command might work for you, but you may need creative redirections. For example:
#!/bin/sh
exec 3>&1
pid=$( sudo sh -c 'echo $$; exec sh -c "{ sleep 1;
echo my pid is $$; }" >&3 &')
echo Child pid is $pid
In the above, you lose the pid of sudo...but it wouldn't be too hard to find it.

Related

Why does SIGHUP not work on busybox sh in an Alpine Docker container?

Sending SIGHUP with
kill -HUP <pid>
to a busybox sh process on my native system works as expected and the shell hangs up. However, if I use docker kill to send the signal to a container with
docker kill -s HUP <container>
it doesn't do anything. The Alpine container is still running:
$ CONTAINER=$(docker run -dt alpine:latest)
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Up 1 second
$ docker kill -s HUP $CONTAINER
4fea4f2dabe0f8a717b0e1272528af1a97050bcec51babbe0ed801e75fb15f1b
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Up 7 seconds
By the way, with an Ubuntu container (which runs bash) it does work as expected:
$ CONTAINER=$(docker run -dt debian:latest)
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Up 1 second
$ docker kill -s HUP $CONTAINER
9a4aff456716397527cd87492066230e5088fbbb2a1bb6fc80f04f01b3368986
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Exited (129) 1 second ago
Sending SIGKILL does work, but I'd rather find out why SIGHUP does not.
Update: I'll add another example. Here you can see that busybox sh generally does hang up on SIGHUP successfully:
$ busybox sh -c 'while true; do sleep 10; done' &
[1] 28276
$ PID=$!
$ ps -e | grep busybox
28276 pts/5 00:00:00 busybox
$ kill -HUP $PID
$
[1]+ Hangup busybox sh -c 'while true; do sleep 10; done'
$ ps -e | grep busybox
$
However, running the same infinite sleep loop inside the docker container doesn't quit. As you can see, the container is still running after SIGHUP and only exits after SIGKILL:
$ CONTAINER=$(docker run -dt alpine:latest busybox sh -c 'while true; do sleep 10; done')
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Up 14 seconds
$ docker kill -s HUP $CONTAINER
31574ba7c0eb0505b776c459b55ffc8137042e1ce0562a3cf9aac80bfe8f65a0
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Up 28 seconds
$ docker kill -s KILL $CONTAINER
31574ba7c0eb0505b776c459b55ffc8137042e1ce0562a3cf9aac80bfe8f65a0
$ docker ps -a --filter "id=$CONTAINER" --format "{{.Status}}"
Exited (137) 2 seconds ago
$
(I don't have Docker env at hand for a try. Just guessing.)
For your case, docker run must be running busybox/sh or bash as PID 1.
According to Docker doc:
Note: A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. So, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.
For the differece between busybox/sh and bash regarding SIGHUP ---
On my system (Debian 9.6, x86_64), the signal masks for busybox/sh and bash are as follows:
busybox/sh:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 82817 0.0 0.0 6952 1904 pts/2 S+ 10:23 0:00 busybox sh
PENDING (0000000000000000):
BLOCKED (0000000000000000):
IGNORED (0000000000284004):
3 QUIT
15 TERM
20 TSTP
22 TTOU
CAUGHT (0000000008000002):
2 INT
28 WINCH
bash:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 4871 0.0 0.1 21752 6176 pts/16 Ss 2019 0:00 /usr/local/bin/bash
PENDING (0000000000000000):
BLOCKED (0000000000000000):
IGNORED (0000000000380004):
3 QUIT
20 TSTP
21 TTIN
22 TTOU
CAUGHT (000000004b817efb):
1 HUP
2 INT
4 ILL
5 TRAP
6 ABRT
7 BUS
8 FPE
10 USR1
11 SEGV
12 USR2
13 PIPE
14 ALRM
15 TERM
17 CHLD
24 XCPU
25 XFSZ
26 VTALRM
28 WINCH
31 SYS
As we can see busybox/sh does not handle SIGHUP so the signal is ignored. Bash catches SIGHUP so docker kill can deliver the signal to Bash and then Bash will be terminated because, according to its manual, "the shell exits by default upon receipt of a SIGHUP".
UPDATE 2020-03-07 #1:
Did a quick test and my previous analysis is basically correct. You can verify like this:
[STEP 104] # docker run -dt debian busybox sh -c \
'trap exit HUP; while true; do sleep 1; done'
331380090c59018dae4dbc17dd5af9d355260057fdbd2f2ce9fc6548a39df1db
[STEP 105] # docker ps
CONTAINER ID IMAGE COMMAND CREATED
331380090c59 debian "busybox sh -c 'trap…" 11 seconds ago
[STEP 106] # docker kill -s HUP 331380090c59
331380090c59
[STEP 107] # docker ps
CONTAINER ID IMAGE COMMAND CREATED
[STEP 108] #
As I showed earlier, by default busybox/sh does not catch SIGHUP so the signal will be ignored. But after busybox/sh explicitly trap SIGHUP, the signal will be delivered to it.
I also tried SIGKILL and yes it'll always terminate the running container. This is reasonable since SIGKILL cannot be caught by any process so the signal will always be delivered to the container and kill it.
UPDATE 2020-03-07 #2:
You can also verify it this way (much simpler):
[STEP 110] # docker run -ti alpine
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps
/ # kill -HUP 1 <-- this does not kill it because linux ignored the signal
/ #
/ # trap 'echo received SIGHUP' HUP
/ # kill -HUP 1
received SIGHUP <-- this indicates it can receive SIGHUP now
/ #
/ # trap exit HUP
/ # kill -HUP 1 <-- this terminates it because the action changed to `exit`
[STEP 111] #
Like the other answer already points out, the docs for docker run contain the following note:
Note: A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. So, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.
This is the reason why SIGHUP doesn't work on busybox sh inside the container. However, if I run busybox sh on my native system, it won't have PID 1 and therefore SIGHUP works.
There are various solutions:
Use --init to specify an init process which should be used as PID 1.
You can use the --init flag to indicate that an init process should be used as the PID 1 in the container. Specifying an init process ensures the usual responsibilities of an init system, such as reaping zombie processes, are performed inside the created container.
The default init process used is the first docker-init executable found in the system path of the Docker daemon process. This docker-init binary, included in the default installation, is backed by tini.
Trap SIGHUP and call exit yourself.
docker run -dt alpine busybox sh -c 'trap exit HUP ; while true ; do sleep 60 & wait $! ; done'
Use another shell like bash which exits on SIGHUP by default, doesn't matter if PID 1 or not.

sudo ignores SIGTERM sent from same script

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
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.
*/
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.
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

Switch user without creating an intermediate process

I'm able to use sudo or su to execute a command as another user. By combining with exec, I'm able to replace the current process with sudo or su, and a child process running the command. But I want to replace the current process with the command running as another user. How do I do that?
Testing with sleep inf as the command, and someguy as the user:
exec su someguy -c 'sleep inf'
This gives me from pstree:
bash───su───sleep
And
exec sudo -u someguy sleep inf
gives
bash───sudo───sleep
In both cases I just want the sleep command, with bash as the parent.
I expect I could do this from C with something some sequence of setuid() and exec().
The difference between sudo sleep and exec sudo sleep is that in the second command sudo process replaces bash image and calling shell process exits when sleep exits
pstree -p $$
bash(8765)───pstree(8943)
((sleep 1; pstree -p $$ )&); sudo -u user sleep 2
bash(8765)───sudo(8897)───sleep(8899)
((sleep 1; pstree -p $$ )&); exec sudo -u user sleep 2
sudo(8765)───sleep(8993)
however the fact that sudo or su fork a new process depends on design and their implementation (some sources found here).
From sudo man page :
Process model
When sudo runs a command, it calls fork(2), sets up the execution environment as described above, and calls the execve system call in the child process. The main sudo
process waits until the command has completed, then passes the command's exit status to the security policy's close function and exits. If an I/O logging plugin is config-
ured or if the security policy explicitly requests it, a new pseudo-terminal (“pty”) is created and a second sudo process is used to relay job control signals between the
user's existing pty and the new pty the command is being run in. This extra process makes it possible to, for example, suspend and resume the command. Without it, the com-
mand would be in what POSIX terms an “orphaned process group” and it would not receive any job control signals. As a special case, if the policy plugin does not define a
close function and no pty is required, sudo will execute the command directly instead of calling fork(2) first. The sudoers policy plugin will only define a close function
when I/O logging is enabled, a pty is required, or the pam_session or pam_setcred options are enabled. Note that pam_session and pam_setcred are enabled by default on sys-
tems using PAM.
I do not share the observation and the conclusions. See below:
I created two shellscripts:
$ cat just_sudo.sh
#!/bin/bash
sudo sleep inf
$ cat exec_sudo.sh
#!/bin/bash
exec sudo sleep inf
So, one with an exec, one without. If I do a pstree to see the starting situation, I get:
$ pstree $$
bash───pstree
$ echo $$
17250
This gives me the baseline. Next I launched both scripts:
$ bash just_sudo.sh &
[1] 1218
$ bash exec_sudo.sh &
[2] 1220
And then, pstree gives:
$ pstree $$
bash─┬─bash───sleep
├─pstree
└─sleep
the first being the just_sudo, the second is the exec_sudo. Both run as root:
$ ps -ef | grep sleep
root 1219 1218 0 14:01 pts/4 00:00:00 sleep inf
root 1220 17250 0 14:01 pts/4 00:00:00 sleep inf
once again the first is the just_sudo and the second the exec_sudo. You can see that the parent-PID for the sleep in the exec_sudo is the interactive shell from which the scripts are launched and the PID is 1220, which was the PID we saw when the script was launched in the background.
If you use two terminal windows and do not put it in the background, this will work also:
terminal 1 terminal 2
$ echo $$
16053 $ pstree 16053
bash
$ sudo sleep inf
$ pstree 16053
bash───sleep
^C
$ exec sudo sleep inf
$ pstree 16053
sleep
^C
( window is closed )
So, on my linux system, the behavior is not as you suggest.The only way that the sudo may remain in the process-tree is if it runs in the existing tty (so without an exec), or if it is invoked with a pseudo-terminal, for example as exec sudoedit.
I am not sure if this can be done using sudo or su. But you can easily achieve this using a simple c program. I will be showing a very bare minimal one with hard coded command and user id, but you can always customize it to your liking
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
int runAs(int gid, int uid, char *command[]) {
setgid(gid);
setuid(uid);
char *args[]={"sleep","inf",NULL};
execvp(args[0],args);
}
int main(int argc, char *argv[] )
{
runAs(1000, 65534, argv);
return 0;
}
Note: on my machine 1000 is the uid/gid of vagrant user and group. 65534 is uid and gid of nobody user and group
build.sh
#!/bin/bash
sudo gcc test.c -o sosu
sudo chown root:root sosu
sudo chmod u+s sosu
Now time for a test
$ pstree $$ -p
bash(23251)───pstree(28627)
$ ./sosu
Now from another terminal
$ pstree -p 23251
bash(23251)───sleep(28687)
$ ps aux | grep [2]8687
nobody 28687 0.0 0.0 7288 700 pts/0 S+ 11:40 0:00 sleep inf
As you can see the process is run as nobody and its a child of bash
In order to free a command, you have to give him std io:
For this,you could either close all stdin, stdout and stderr or let them point elsewhere.
Try this:
su - someguy -c 'exec nohup sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'
Note:
su - someguy -c 'exec nohup sleep 60 &'
Is enough, and
su - someguy -c 'exec sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'
Will work too.
Consider having a look at man nohup
Note 2: Under bash, you could use:
su - someguy -c 'exec sleep 60 & disown -h'
... And read help disown or man bash.
Little demo showing how to close all IOs:
su - someguy -c 'exec 0<&- ; exec 1>&- ; exec 2>&- ; exec sleep 60 &'
quick test:
pstree $(ps -C sleep ho pid)
sleep

how to terminate a process which is run with sudo? Ctrl+C do it, but not kill

At my company, some commands are allowed to run with sudo, such as tcpdump. Others not.
I expect run tcpdump for a while, and then stop it.
When I run tcpdump, and I could abort that with Ctrl+C
I wrote a shell script like this -
#!/bin/sh
sudo tcpdump -ieth1 -w ~/dump.bin
sleep 5
kill -2 $!
it doesn't really work. The process of tcpdump is run as root, and current user is a normal account.
My question is: is there any way to do the equivalent of ctrl c in bash script?.
EDIT:
ps:As my company's security policy, I cannot run kill as root.
Try the -Z option to tcpdump. It instructs tcpdump to drop root privileges and run as the user specified in the argument.
sudo tcpdump -Z $USER -ieth1 -w ~/dump.bin
Now try killing that process.
Simply run kill through sudo as well:
sudo kill -2 $!
This way the kill process will have the privilege to send signals to a process that runs as root.
For programs that don't have special switches like -Z and in case you can alter sudoers file, this is a solution:
sudo myprogram &
sleep 5
sudo pkill myprogram
All I have to do is to allow to run pkill myprogram passwordless by using visudo and adding this line:
myuser ALL=(ALL) NOPASSWD:/bin/pkill myprogram
This is less dangerous that lo let sudo kill any program.
The timeout command also terminates a program after so long. sudo timeout 5 tcpdump -ieth1 -w ~/dump.bin should accomplish the same thing as the script.
sudo tcpdump -Z root -w ~/dump.bin -n -i eth0 -G 300 -W 1
G - Timeout Seconds (After timeout period the comman gets killed automatically)
Z - drop root and runs as user privilege
W - Number files to be saved (as a splitted file)
sudo tcpdump -ieth1 -w ~/dump.bin
will block your script, you need to put it into the background:
sudo tcpdump -ieth1 -w ~/dump.bin &
.
This and the answer from Blagovest should do it.

Running Process as Unprivileged User

On Linux system, how does one run a process as a different, unprivileged user (like how lighttpd is run by www-data in the default setup)?
I've been using su $user; $command & over ssh, but those processes get killed when I logout.
If it makes any difference, I'm using a default Ubuntu setup on EC2.
su $other_user -c 'nohup sleep 600 &'
nohup $command </dev/null >/dev/null 2>/dev/null &
or
command </dev/null >/dev/null 2>/dev/null &; disown

Resources