system() vs execve()

19,642

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.

Share:
19,642

Related videos on Youtube

Jake
Author by

Jake

# SOreadytohelp

Updated on June 04, 2022

Comments

  • Jake
    Jake almost 2 years

    Both system() and execve() can be used to execute another command inside a program. Why in set-UID programs, system() is dangerous, while execve() is safe ?

  • Jake
    Jake over 9 years
    So when using execve() .. you mention it replaces current process .. would the process remain setuid ?
  • Marcelo
    Marcelo over 9 years
    Yes. 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
    Bratchley over 9 years
    Not saying system() is without problems but wouldn't the above be resolved by using absolute paths in the binary executable?
  • Stephane Chazelas
    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 without system(), you should probably sanitise the environment. When using setuids you want to minimise what is done as root (typically not execute commands).
  • cuonglm
    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 to bin and ls. Now, an executable called bin in current directory can do the same thing as ls in my answer.
  • Bratchley
    Bratchley over 9 years
    Thank you both. This was actually a pretty interesting answer.
  • alam
    alam over 5 years
    Environment 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
    Coaden about 2 years
    If execve will run a shebang, I don't see where it matters.