0

The info page of command id states that it will output the effective user ID if different from the real user ID. I have been trying to achieve that somehow, running the command with the Bash shell as my normal (unprivileged) user, in Ubuntu. For example, I tried the example in the answer by @Asain Kujovic here but it didn't work, I get not euid in the output whatsoever. This is the example:

osbo@osboxes:~/t$ sudo gcc -o test.bin -xc - <<EOF
     #include <stdlib.h>
     #include <unistd.h>
     int main() { seteuid(0); system("id"); }
EOF
osbo@osboxes:~/t$ sudo chmod +s test.bin && ./test.bin && sudo rm test.bin
uid=1000(osbo) gid=1000(osboxes) groups=1000(osboxes),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),113(bluetooth),119(scanner)

EDIT:
I'm a beginner in Linux, so beginner-friendly answers are welcome, if that is possible for the question I made. I know what is a shell program (like Bash or sh), what are environment variables, what is a C program and what are system calls such as getuid and geteuid. I also know what are real, effective and saved user/group ID, as related to a process a user spawns. Yet, I know very little about parent-child processes scenarios, or the strace command.

1 Answers1

2

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.

  • Thanks for your answer. I read it thoroughly. I edited my question to specify I'm a beginner. It was nice to see the security measure you explained in action, with your first strace command. I focused on the sh shell's -p option, to try and disable it, only for learning purposes. After running the sudo 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 the sudo install ... command. What is that env file? Why if I run just sh -pc 'id' I keep obtaining the same effective and real user IDs? – gambarimas87 Dec 08 '23 at 13:30