system("code")
is actually running sh
with arguments sh
, -c
, --
, code
¹ in a child process.
Several shells, as a security measure, do the equivalent of:
if (geteuid() != (uid = getuid())
setuid(uid);
That is, they revert the effective uid to the real uid if the effective uid is not the same as the real uid.
When writing setuid executable (which should be avoided in the first place if possible), you want to make sure the section of the code that runs with elevated privileges is as short and as tightly controlled as possible to avoid introducing privilege escalation vulnerabilities.
Running a shell from a setuid executable is the last thing you want to do as it's almost impossible to write safe shell scripts. So those shells help there by just dropping those privilege upon start as early as possible, which helps for poorly written setuid commands that fail to do so by themselves.
Here from zsh, and on an Ubuntu system where sh is based dash:
$ sudo install -m 4750 -o root -g "$GID" =env .
$ ls -ld env
-rwsr-x--- 1 root stephane 43352 Dec 7 16:12 env*
$ sudo strace -u "$USERNAME" -ze '/uid|execve' ./env sh -c ''
execve("./env", ["./env", "sh", "-c", ""], 0x7ffde6ddf108 /* 17 vars */) = 0
execve("/usr/bin/sh", ["sh", "-c", ""], 0x7fff075efcd0 /* 17 vars */) = 0
getuid() = 10031
geteuid() = 0
geteuid() = 0
setuid(10031) = 0
geteuid() = 10031
+++ exited with 0 +++
Some of those shells that do that often have a -p
option to disable it, not that it would help when using system()
. Then they don't drop their privileges, but still are more careful not to trust their environment as much.
$ sudo strace -u "$USERNAME" -ze '/uid|execve' ./env sh -pc ''
execve("./env", ["./env", "sh", "-pc", ""], 0x7ffd4616dfb8 /* 17 vars */) = 0
execve("/usr/bin/sh", ["sh", "-pc", ""], 0x7ffef3883230 /* 17 vars */) = 0
getuid() = 10031
geteuid() = 0
+++ exited with 0 +++
What you could do here is do setuid(0)
before calling system()
so both real and effective uid will be 0, but then that would mean your shell would not drop its privilege and trust its environment. So a very bad idea. You'd want at least to perform all the sanitisation that commands such as sudo
do before a setuid executable calls sh
as root
.
For instance, here using zsh
which doesn't do that dropping and has support for changing uids builtin to do the setuid()
:
$ sudo strace -u "$USERNAME" -ze '/uid|execve' ./env zsh -c 'UID=0; exec "$@"' zsh sh -c 'id -u'
execve("./env", ["./env", "zsh", "-c", "UID=0; exec \"$@\"", "zsh", "sh", "-c", "id -u"], 0x7ffc6de6e5e8 /* 17 vars */) = 0
execve("/usr/bin/zsh", ["zsh", "-c", "UID=0; exec \"$@\"", "zsh", "sh", "-c", "id -u"], 0x7ffda351af70 /* 17 vars */) = 0
getuid() = 10031
geteuid() = 0
getuid() = 10031
setuid(0) = 0
execve("/usr/bin/sh", ["sh", "-c", "id -u"], 0x562a89149d20 /* 21 vars */) = 0
getuid() = 0
geteuid() = 0
geteuid() = 0
0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=47576, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
If you do need to run a separate executable from within a setuid executable in a child process, you can do what system()
does (fork, exec in the child, block some signals and wait in the parent) but at least skip the execution of sh
. You'd want to give the full path of the executable rather than rely on a $PATH
lookup, and you'll likely want to do some amount of environment sanitisation.
¹ Actually, more often than not,
it's running it with sh
, -c
, code
, which means that with modern implementations of sh
, it doesn't work properly if the code
starts with -
or +
. More implementations are likely to switch now that POSIX mandates it since issue 8 of the standard.
strace
command. I focused on the sh shell's-p
option, to try and disable it, only for learning purposes. After running thesudo install ...
command, I ran./env sh -pc 'id'
and I did get different real and effective UID as I was looking for! However, I do not understand thesudo install ...
command. What is thatenv
file? Why if I run justsh -pc 'id'
I keep obtaining the same effective and real user IDs? – gambarimas87 Dec 08 '23 at 13:30