Switch user without creating an intermediate process - linux

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

Related

Why does executing a simple command in a grouping command does not fork a subshell process, and the compound command will do it

I know that grouping commands(command-list) creates a subshell environment, and each listed command is executed in that subshell. But if I execute a simple command in the grouping command, (use the ps command to output the processes), then no subshell process is output. But if I tried to execute a list of commands (compound command) in the grouping command, then a subshell process is output. Why does it produce such a result?
A test of executing a simple command (only a ps command) in a grouping command:
[root#localhost ~]# (ps -f)
with the following output:
UID PID PPID C STIME TTY TIME CMD
root 1625 1623 0 13:49 pts/0 00:00:00 -bash
root 1670 1625 0 15:05 pts/0 00:00:00 ps -f
Another test of executing a compound command(a list of commands) in a grouping command:
[root#localhost ~]# (ps -f;cd)
with the following output:
UID PID PPID C STIME TTY TIME CMD
root 1625 1623 0 13:49 pts/0 00:00:00 -bash
root 1671 1625 0 15:05 pts/0 00:00:00 -bash
root 1672 1671 0 15:05 pts/0 00:00:00 ps -f
I tested a lot of other commands (compound commands and simple commands), but the results are the same. I guess even if I execute a simple command in a grouping command, bash should fork a subshell process, otherwise it can't execute the command. But why can't I see it?
Bash optimizes the execution. It detects that only one command is inside the ( ) group and calls fork + exec instead of fork + fork + exec. That's why you see one bash process less in the list of processes. It is easier to detect when using command that take more time ( sleep 5 ) to eliminate timing. Also, you may want to read this thread on unix.stackexchange.
I think the optimization is done somewhere inside execute_cmd.c in execute_in_subshell() function (arrows > added by me):
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
>>>> This means things like ( sleep 10 ) will only cause one fork
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
and in execute_disk_command() function we can also read:
/* If we can get away without forking and there are no pipes to deal with,
don't bother to fork, just directly exec the command. */
It looks like an optimization and dash appears to be doing it too:
Running
bash -c '( sleep 3)' & sleep 0.2 && ps #or with dash
as does, more robustly:
strace -f -e trace=clone dash -c '(/bin/sleep)' 2>&1 |grep clone # 1 clone
shows that the subshell is skipped, but if there's post work to be done in the subshell after the child, the subshell is created:
strace -f -e trace=clone dash -c '(/bin/sleep; echo done)' 2>&1 |grep clone #2 clones
Zsh and ksh are taking it even one step further and for (when they see it's the last command in the script):
strace -f -e trace=clone ksh -c '(/bin/sleep; echo done)' 2>&1 |grep clone # 0 clones
they don't fork (=clone) at all, execing directly in the shell process.

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

how to daemonize a script

I am trying to use daemon on Ubuntu, but I am not sure how to use it even after reading the man page.
I have the following testing script foo.sh
#!/bin/bash
while true; do
echo 'hi' >> ~/hihihi
sleep 10
done
Then I tried this command but nothing happened:
daemon --name="foo" -b ~/daemon.out -l ~/daemon.err -v -- foo.sh
The file hihihi was not updated, and I found this in the errlog:
20161221 12:12:36 foo: client (pid 176193) exited with 1 status
How could I use the daemon command properly?
AFAIK, most daemon or deamonize programs change the current dir to root as part of the daemonization process. That means that you must give the full path of the command:
daemon --name="foo" -b ~/daemon.out -l ~/daemon.err -v -- /path/to/foo.sh
If it still did not work, you could try to specify a shell:
daemon --name="foo" -b ~/daemon.out -l ~/daemon.err -v -- /bin/bash -c /path/to/foo.sh
It is not necessary to use daemon command in bash. You can daemonize your script manually. For example:
#!/bin/bash
# At first you have to redirect stdout and stderr to /dev/null
exec >/dev/null
exec 2>/dev/null
# Fork and go to background
(
while true; do
echo 'hi' >> ~/hihihi
sleep 10
done
)&
# Parent process finished but child still working

Sudo - Waiting for child, and getting child PID

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.

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.

Resources