1

When a C program calls system() to run a Unix command, I know it's possible to pass arguments to the command, and according to a StackOverflow answer (from a very high-rep user), the system() call uses the shell to execute the command.

It surprised me to see system("ls -lh >/dev/null 2>&1"); as an example system() call for a C program, since it looks like this is using the same whitespace delimited "words" as the shell uses interactively.

From my standpoint as a sysadmin, I'd like to understand what the provisos and pitfalls are when a system() call is going to be executed from within a C program on some files or commands on my system. Passing whitespace-containing filenames into a shell script is very issue-prone; are there similar issues when a C program is calling a command?

Or to make the point even blunter (though less exact): Is a C program written by a novice just as likely to break on whitespace-containing filenames as a shell script?

4
  • It just runs sh -c '...'. Jan 27, 2016 at 8:44
  • 3
    You're confusing calls to function "system()" and "system calls", which are functions implemented in the kernel and callable from userland (through a wrapper in the standard library). The function "system()" is implemented in the C standard library and issues the system call "execve".
    – lgeorget
    Jan 27, 2016 at 8:57
  • 1
    (1) If you know that the system() function uses the shell to execute the command, why are you surprised that it uses shell syntax? (2) As a sysadmin, you should try to minimize the use of the system() function in programs on your system — don't use it, and discourage others from using it — especially if the program runs in a privileged context and/or builds its command string dynamically; especially if it builds its command string from user-supplied input. If you must use it, put a lot of thought and effort into validating the user input, to prevent code-injection-type attacks. Jan 27, 2016 at 11:19
  • @lgeorget, that is precisely what I was confusing: system() and execve(). Thanks.
    – Wildcard
    Jul 18, 2017 at 23:03

3 Answers 3

6

There is nothing to do in C source here, the string you pass to system() was run in shell context. The shell will parse that string, your C program doesn't.


If you look at system() function prototype:

#include <stdlib.h>

int system(const char *command);

The argument passed to system() is a string. It has nothing to do with whitespaces characters in string, it gets a string and passes that string to other system call. The same as you did:

sh -c 'ls -l'

Here system() use execve():

$ cat <<\CODE | gcc -xc -
#include <stdlib.h>
int main(void) {
  system("ls -l");
  return 0;
}
CODE

$ strace -fe execve ./a.out
execve("./a.out", ["./a.out"], [/* 64 vars */]) = 0
Process 16281 attached
[pid 16281] execve("/bin/sh", ["sh", "-c", "ls -l"], [/* 64 vars */]) = 0
Process 16282 attached
[pid 16282] execve("/bin/ls", ["ls", "-l"], [/* 64 vars */]) = 0
...
2

Yes and no.

The system() function is a C library call that runs a shell to start an other program, and waits for its result. It is useful for the common need of wanting to run an external command to check if some external condition is satisfied. It does indeed use the shell to start the external command.

It is, not a system call. A system call is a direct request to the kernel; things like 'give me more memory', 'open a file' and 'write this stuff to a file descriptor' are. The system() function is implemented in terms of (at least) three system calls:

  1. fork(), to spawn off a child process.
  2. One of the exec() family, called by the child, to replace that child process by another executable
  3. wait(), called by the parent, to wait for the child process to finish and read its exit state.

The exec() call starts the process without involving any shell, and therefore no whitespace expansion occurs. It is, in theory, possible to bypass the system() function, and then you wouldn't have whitespace issues. That's a lot of work, though, and not usually worth it, because there's a lot more that can go wrong there.

Which brings me to my point: if you're worried about novice people dealing with shell scripts, you shouldn't let them deal with C compilers. It is possible to do a lot more wrong in C than in shell...

1

If a C program uses system() to invoke another program, it must create a command to be parsed by the shell (specifically, /bin/sh).

The implementation of system(const char *command) will normally end up calling something like execl('/bin/sh', 'sh', '-c', command, (char*)NULL) in the child process (and the execl library function will end up calling execve system call).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.