On Unix, one of the exec*( )
family of functions is used to
replace the current program within a process with another program.
Typically, when you’re executing another program,
the original program continues to run while the new program is
executed, thus requiring two processes to achieve the desired effect.
The exec*( )
functions do not create a new
process. Instead, you must first use fork( )
to
create a new process, and then use one of the exec*(
)
functions in the new process to run the new program. See
Recipe 1.6 for a discussion of using fork( )
securely.
execve( )
is the system call used to load and begin
execution of a new program. The other functions in the
exec*( )
family are wrappers around the
execve( )
system call, and they are implemented in
user space in the standard C runtime library. When a new program is
loaded and executed with execve( )
, the new
program replaces the old program within the same process. As part of
the process of loading the new program, the old
program’s address space is replaced with a new
address space. File descriptors that are marked to close on execute
are closed; the new program inherits all others. All other
system-level properties are tied to the process, so the new program
inherits them from the old program. Such properties include the
process ID, user IDs, group IDs, working and root directories, and
signal mask.
Table 1-2 lists the various exec*(
)
wrappers around the execve( )
system
call. Note that many of these wrappers should not be used in secure
code. In particular, never use the wrappers that are named with a
“p” suffix because they will search
the environment to locate the file to be executed. When executing
external programs, you should always specify the full path to the
file that you want to execute. If the PATH
environment variable is used to locate the file, the file that is
found to execute may not be the expected one.
Table 1-2. The exec*( ) family of functions
The two easiest and safest functions to use are execv(
)
and
execve( )
; the only difference between the two is that
execv( )
calls execve( )
,
passing environ
for the environment pointer. If
you have already sanitized the environment (see Recipe 1.1),
it’s reasonable to call execv( )
without explicitly specifying an environment to use. Otherwise, a new
environment can be built and passed to execve( )
.
The argument lists for the functions are built just as they will be
received by main( )
. The first element of the
array is the name of the program that is running, and the last
element of the array must be a NULL
. The
environment is built in the same manner as described in Recipe 1.1.
The first argument to the two functions is the full path and filename
of the executable file to load and execute.
As a courtesy to the new program, before executing it you should
close any file descriptors that are open unless there are descriptors
that you intentionally want to pass along to it. Be sure to leave
stdin
, stdout
, and
stderr
open. (See Recipe 1.5 for a discussion of
file descriptors.)
Finally, if your program was executed setuid or setgid and the extra
privileges have not yet been dropped, or they have been dropped only
temporarily, you should drop them permanently before executing the
new program. Otherwise, the new program will inherit the extra
privileges when it should not. If you use the spc_fork(
)
function from Recipe 1.6, the file descriptors and
privileges will be handled for you.
Another function provided by the standard C runtime library for
executing programs is system(
)
. This function hides the details of calling
fork( )
and the appropriate exec*(
)
function to execute the program. There are two reasons
why you should never use the system( )
function:
It uses the shell to launch the program.
It passes the command to execute to the shell, leaving the task of breaking up the command’s arguments to the shell.
The system( )
function works differently from the
exec*( )
functions; instead of replacing the
currently executing program, it creates a new process with
fork( )
. The new process executes the shell with
execve( )
while the original process waits for the
new process to terminate. The system( )
function
therefore does not return control to the caller until the specified
program has completed.
Yet another function, popen(
)
, works somewhat similarly to
system( )
. It also uses the shell to launch the
program, passing the command to execute to the shell and leaving the
task of breaking up the command’s arguments to the
shell. What it does differently is create an anonymous pipe that is
attached to either the new program’s
stdin
or its stdout
file
descriptor. The new program’s
stderr
file descriptor is always inherited from
the parent. In addition, it returns control to the caller immediately
with a FILE
object connected to the created pipe
so that the caller can communicate with the new program. When
communication with the new program is finished, you should call
pclose( )
to clean up the file descriptors and reap the
child process created by the call to fork( )
.
You should also avoid using popen( )
and its
accompanying pclose( )
function, but
popen( )
does have utility that is worth
duplicating in a secure fashion. The following implementation with a
similar API does not make use of the shell.
If you do wish to use either system( )
or
popen( )
, be extremely careful. First, make sure
that the environment is properly set, so that there are no Trojan
environment variables. Second, remember that the command
you’re running will be run in a Unix shell. This
means that you must ensure that there is no way an attacker can pass
malicious data to the shell command. If possible, pass in a fixed
string that the attacker cannot manipulate. If the user must be
allowed to manipulate the input, only very careful filtering will
accomplish this securely. We recommend that you avoid this scenario
at all costs.
The following code implements secure versions of popen(
)
and pclose( )
using the spc_fork( )
code from Recipe 1.6. Our
versions differ slightly in both interface and function, but not by
too much.
The function spc_popen(
)
requires the same arguments execve( )
does. In
fact, the arguments are passed directly to execve(
)
without any modification. If the operation is successful,
an SPC_PIPE
object is returned; otherwise,
NULL
is returned. When communication with the new
program is complete, call spc_pclose( )
, passing
the SPC_PIPE
object returned by
spc_popen( )
as its only argument. If the new
program has not yet terminated when spc_pclose( )
is called in the original program, the call will block until the new
program does terminate.
If spc_popen( )
is successful, the
SPC_PIPE
object it returns contains two
FILE
objects:
read_fd
can be used to read data written by the new program to itsstdout
file descriptor.write_fd
can be used to write data to the new program for reading from its stdin file descriptor.
Unlike popen( )
, which in its most portable form
is unidirectional, spc_popen( )
is bidirectional.
#include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> typedef struct { FILE *read_fd; FILE *write_fd; pid_t child_pid; } SPC_PIPE; SPC_PIPE *spc_popen(const char *path, char *const argv[], char *const envp[]) { int stdin_pipe[2], stdout_pipe[2]; SPC_PIPE *p; if (!(p = (SPC_PIPE *)malloc(sizeof(SPC_PIPE)))) return 0; p->read_fd = p->write_fd = 0; p->child_pid = -1; if (pipe(stdin_pipe) = = -1) { free(p); return 0; } if (pipe(stdout_pipe) = = -1) { close(stdin_pipe[1]); close(stdin_pipe[0]); free(p); return 0; } if (!(p->read_fd = fdopen(stdout_pipe[0], "r"))) { close(stdout_pipe[1]); close(stdout_pipe[0]); close(stdin_pipe[1]); close(stdin_pipe[0]); free(p); return 0; } if (!(p->write_fd = fdopen(stdin_pipe[1], "w"))) { fclose(p->read_fd); close(stdout_pipe[1]); close(stdin_pipe[1]); close(stdin_pipe[0]); free(p); return 0; } if ((p->child_pid = spc_fork( )) = = -1) { fclose(p->write_fd); fclose(p->read_fd); close(stdout_pipe[1]); close(stdin_pipe[0]); free(p); return 0; } if (!p->child_pid) { /* this is the child process */ close(stdout_pipe[0]); close(stdin_pipe[1]); if (stdin_pipe[0] != 0) { dup2(stdin_pipe[0], 0); close(stdin_pipe[0]); } if (stdout_pipe[1] != 1) { dup2(stdout_pipe[1], 1); close(stdout_pipe[1]); } execve(path, argv, envp); exit(127); } close(stdout_pipe[1]); close(stdin_pipe[0]); return p; } int spc_pclose(SPC_PIPE *p) { int status; pid_t pid; if (p->child_pid != -1) { do { pid = waitpid(p->child_pid, &status, 0); } while (pid = = -1 && errno = = EINTR); } if (p->read_fd) fclose(p->read_fd); if (p->write_fd) fclose(p->write_fd); free(p); if (pid != -1 && WIFEXITED(status)) return WEXITSTATUS(status); else return (pid = = -1 ? -1 : 0); }
Get Secure Programming Cookbook for C and C++ now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.