system() vs execve()
Solution 1
system will call the shell (sh) to execute the command sent as an argument. The problem with system
because the shell behavior depends on the user who run the command. A small example:
Creating a file test.c
:
#include <stdio.h>
int main(void) {
if (system ("ls") != 0)
printf("Error!");
return 0;
}
Then:
$ gcc test.c -o test
$ sudo chown root:root test
$ sudo chmod +s test
$ ls -l test
-rwsr-sr-x 1 root root 6900 Dec 12 17:53 test
Creating a script called ls
in your current directory:
$ cat > ls
#!/bin/sh
/bin/sh
$ chmod +x ls
Now:
$ PATH=. ./test
# /usr/bin/id
uid=1000(cuonglm) gid=1000(cuonglm) euid=0(root) egid=0(root) groups=0(root),
24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),105(scanner),
110(bluetooth),111(netdev),999(docker),1000(cuonglm)
# /usr/bin/whoami
root
Oops, you got a shell with root privileges.
execve does not call a shell. It executes the program that passed to it as first argument. The program must be a binary executable or a script start with shebang line.
Solution 2
system()
and execve()
work in different ways. system()
will always invoke the shell and this shell will execute the command as a separate process (this is why you can use wildcards and other shell facilities in the command line when using system()
).
execve()
(and the other functions in the exec()
family) replaces the current process with the one being spawned directly (the execve()
function doesn't return, except in case of failure). In fact system()
implementation is supposed to use a sequence of fork()
, execve()
and wait()
calls to perform its function.
Of course both are dangerous depending on what is being executed when the process has root privileges. system()
, though, brings some extra dangers due to the additional shell "layer" it uses that opens room security breaches as it invokes a root shell as in the case of your question (i.e., the process has the suid bit).
Solution 3
Apart from the metioned security issues with system()
, the spawned process inherits the main program's environment. This can be very problematic when using suid
, for example when the calling process sets LD_LIBRARY_PATH
-environment variable.
With the exec()
-family the calling program can set the environment to exactly what's needed (and safe) for the called program before calling exec()
.
And of course the shell called by system()
can have security issues itself.
Related videos on Youtube
Comments
-
Jake almost 2 years
Both
system()
andexecve()
can be used to execute another command inside a program. Why in set-UID programs,system()
is dangerous, whileexecve()
is safe ? -
Jake over 9 yearsSo when using execve() .. you mention it replaces current process .. would the process remain setuid ?
-
Marcelo over 9 yearsYes. The "new" process initiated by execve inherits a number of properties of the one being replaced, like filedescriptors, sockets, etc. and the effective uid is one of them, but there are situations where the uid changes during the execution of execve, like if the executable pointed by the execve parameter has the suid bit set. In this case, the uid is changed to the file owner as it is defined in the filesystem.
-
Bratchley over 9 yearsNot saying
system()
is without problems but wouldn't the above be resolved by using absolute paths in the binary executable? -
Stephane Chazelas over 9 years@JoelDavis, no, you'd need at least to clear the whole environment, give sane default values to a few envvars (PATH, HOME...), if needed preserve some env vars after sanitising (TERM, DISPLAY, LANG...) make sure fds 0, 1, 2 are open... Basically do what sudo does. Even then, I wouldn't go there. Don't invoke a shell in privilege escalation context if that can be avoided. Note that
ls
can do fancy things with its environment, so even withoutsystem()
, you should probably sanitise the environment. When using setuids you want to minimise what is done as root (typically not execute commands). -
cuonglm over 9 years@JoelDavis: No, you still have problem, even if you use full path. If you use
/bin/ls
, the user can add/
to$IFS
, causing the shell split/bin/ls
tobin
andls
. Now, an executable calledbin
in current directory can do the same thing asls
in my answer. -
Bratchley over 9 yearsThank you both. This was actually a pretty interesting answer.
-
alam over 5 yearsEnvironment variable LD_LIBRARY_PATH will be ignored when setuid program is run by non-root user except when the real uid is also 0.
-
Coaden about 2 yearsIf execve will run a shebang, I don't see where it matters.