无需创建中间进程即可切换用户

Switch user without creating an intermediate process

我可以使用 sudosu 作为另一个用户执行命令。通过与 exec 结合,我能够用 sudosu 替换当前进程,并且子进程 运行 命令。但是我想以另一个用户的身份用命令 运行 替换当前进程。我该怎么做?

使用 sleep inf 作为命令进行测试,someguy 作为用户进行测试:

exec su someguy -c 'sleep inf'

这给了我 pstree:

bash───su───sleep

exec sudo -u someguy sleep inf

给予

bash───sudo───sleep

在这两种情况下,我只想要 sleep 命令,以 bash 作为父命令。

我希望我可以用 setuid()exec().

的某种序列从 C 中做到这一点

我不同意观察和结论。见下文:

我创建了两个 shell脚本:

$ cat just_sudo.sh
#!/bin/bash
sudo sleep inf
$ cat exec_sudo.sh
#!/bin/bash
exec sudo sleep inf

所以,一个有 exec,一个没有。如果我执行 pstree 查看起始情况,我会得到:

$ pstree $$
bash───pstree
$ echo $$
17250

这给了我基线。接下来我启动了两个脚本:

$ bash just_sudo.sh &
[1] 1218
$ bash exec_sudo.sh &
[2] 1220

然后,pstree 给出:

$ pstree $$
bash─┬─bash───sleep
     ├─pstree
     └─sleep

第一个是just_sudo,第二个是exec_sudo。 运行 作为 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

再次第一个是 just_sudo,第二个是 exec_sudo。您可以看到 exec_sudo 中睡眠的父 PID 是启动脚本的交互式 shell PID 是 1220,这是我们在 中启动脚本时看到的 PID背景。

如果你使用两个终端 windows 并且不把它放在后台,这也可以工作:

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 )

因此,在 my linux 系统上,行为与您不同 suggest.The sudo 可能保留在进程树中的唯一方式是如果它 运行s 在现有的 tty 中(因此没有 exec),或者如果它是用伪终端调用的,例如 exec sudoedit.

为了释放一个命令,你必须给他std io:

为此,您可以关闭所有 stdinstdoutstderr 或者让他们指向别处。

试试这个:

su - someguy -c 'exec nohup sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'

注:

su - someguy -c 'exec nohup sleep 60 &'

够了,还有

su - someguy -c 'exec sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'

也可以。

考虑看看 man nohup

注2:在下,您可以使用:

su - someguy -c 'exec sleep 60 & disown -h'

... 并阅读 help disownman bash

展示如何关闭所有IOs:

的小演示
su - someguy -c 'exec 0<&- ; exec 1>&- ; exec 2>&- ; exec sleep 60 &'

快速测试:

pstree $(ps -C sleep ho pid)
sleep

sudo sleepexec sudo sleep的区别在于第二个命令sudo进程替换bash图像和调用shell进程在sleep退出时退出

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)

然而,sudosu 分叉一个新进程的事实取决于设计及其实现(找到一些来源 here)。

来自 sudo 手册页:

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.

我不确定是否可以使用 sudosu 来完成。但是您可以使用简单的 c 程序轻松实现这一点。我将展示一个带有硬编码命令和用户 ID 的极简版,但您始终可以根据自己的喜好对其进行自定义

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

现在是测试时间

$ pstree $$ -p
bash(23251)───pstree(28627)

$ ./sosu

现在从另一个终端

$ 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

如您所见,进程是 运行 作为 nobody 并且它是 bash

的子进程