16

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().

Inian
  • 80,270
  • 14
  • 142
  • 161
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526

4 Answers4

6

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.

Nahuel Fouilleul
  • 18,726
  • 2
  • 31
  • 36
  • It would seem something in the sudo code is preventing it from execing directly. If you know how to ensure this behaviour, that would be appreciated. – Matt Joiner Nov 25 '17 at 03:46
4

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.

Ljm Dullaart
  • 4,273
  • 2
  • 14
  • 31
  • 2
    Your answer is a little short of receiving an upvote from me. The reason that your observations and conclusions differ from those of the author of the question is because you run your `*_sudo.sh` scripts in the background (with the `&` character at the end of the command line). The explanation for it can be found in the source code - see https://www.sudo.ws/repos/sudo/file/tip/src/exec.c and pay attention to the fragment of the code marked with a comment containing *just exec directly*). Improve your answer using the provided info and you will receive an upvote from me. – Leon Nov 20 '17 at 10:06
  • 1
    the running in the background is not a reason, as demonstrated by running in a differnt terminal. The code states that a direct exec is only not done is there is an I/O plugin or the policy has requested a pty. This is in general only the case if you invoke `sudoedit`. Otherwise you have to do something very special. – Ljm Dullaart Nov 20 '17 at 19:31
  • I don't get the behaviour you describe with your exec_sudo.sh. – Matt Joiner Nov 25 '17 at 01:13
  • @LjmDullaart what is your OS? I think it's highly implementation defined. – Matt Joiner Jan 06 '18 at 00:16
  • It's all Linux. Fedora 23, Slackware 14.2 (both 64 bit) and Raspian Jessie. – Ljm Dullaart Jan 06 '18 at 10:42
3

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

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • This might end up being necessary I think. – Matt Joiner Mar 28 '18 at 23:38
  • My suggestion is that if anyone wishes to use `argv` for the new command, try `argv++`, followed by `execvp(*argv, argv)`. The vector `argv` itself is exactly what `execv*()` functions need. Don't forget to check if `argc == 1` at the beginning. – Daniel Shao May 29 '22 at 01:39
1

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 , 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
Felipe Valdes
  • 1,998
  • 15
  • 26
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
  • 1
    This is no good because you're detaching the sleep process and it's being reparented onto init. I need sleep to be a child of the bash process that originally invoked su or sudo. – Matt Joiner Nov 25 '17 at 01:12