1.6. Creating a Child Process Securely

Problem

Your program needs to create a child process either to perform work within the same program or, more frequently, to execute another program.

Solution

On Unix, creating a child process is done by calling fork( ) . When fork( ) completes successfully, a nearly identical copy of the calling process is created as a new process. Most frequently, a new program is immediately executed using one of the exec*( ) family of functions (see Recipe 1.7). However, especially in the days before threading, it was common to use fork( ) to create separate “threads” of execution within a program.[4]

If the newly created process is going to continue running the same program, any pseudo-random number generators (PRNGs) must be reseeded so that the two processes will each yield different random data as they continue to execute. In addition, any inherited file descriptors that are not needed should be closed; they remain open in the other process because the new process only has a copy of them.

Finally, if the original process had extra privileges from being executed as setuid or setgid, those privileges will be inherited by the new process, and they should be dropped immediately if they are not needed. In particular, if the new process is going to be used to execute a new program, privileges should always be dropped so that the new program does not inherit privileges that it should not have.

Discussion

When fork( ) is used to create a new process, the new process is a nearly identical copy of the original process. The only differences in the processes are the process ID, the parent process ID, and the resource utilization counters, which are reset to zero in the new process. Execution in both processes continues immediately after the return from fork( ). Each process can determine whether it is the parent or the child by checking the return value from fork( ). In the parent or original process, fork( ) returns the process ID of the new process, while 0 will be returned in the child process.

It’s important to remember that the new process is a copy of the original. The contents of the original process’s memory (including stack), file descriptor table, and any other process attributes are the same in both processes, but they’re not shared. Any changes to memory contents, file descriptors, and so on are private to the process that is making them. In other words, if the new process changes its file position pointer in an open file, the file position pointer for the same file in the original process remains unchanged.

The fact that the new process is a copy of the original has important security considerations that are often overlooked. For example, if a PRNG is seeded in the original process, it will be seeded identically in the child process. This means that if both the original and new processes were to obtain random data from the PRNG, they would both get the same random data (see Figure 1-2)! The solution to this problem is to reseed the PRNG in one of the processes, or, preferably, both processes. By reseeding the PRNG in both processes, neither process will have any knowledge of the other’s PRNG state. Be sure to do this in a thread-safe manner if your program can fork multiple processes.

Consequences of not reseeding PRNGs after calling fork( )

Figure 1-2. Consequences of not reseeding PRNGs after calling fork( )

At the time of the call to fork( ), any open file descriptors in the original process will also be open in the new process. If any of these descriptors are unnecessary, they should be closed; they will remain open in the other process. Closing unnecessary file descriptors is especially important if one of the processes is going to execute another program (see Recipe 1.5).

Finally, the new process also inherits its access rights from the original process. Normally this is not an issue, but if the parent process had extra privileges because it was executed setuid or setgid, the new process will also have the extra privileges. If the new process does not need these privileges, they should be dropped immediately (see Recipe 1.3). Any extra privileges should be dropped especially if one of the two processes is going to execute a new program.

The following function, spc_fork( ) , is a wrapper around fork( ). As presented here, the code is incomplete when using an application-level random number generator; it will require the appropriate code to reseed whatever PRNG you’re using. It assumes that the new child process is the process that will be used to perform any work that does not require any extra privileges that the process may have. It is rare that when a process is forked, the original process is used to execute another program or the new process is used to continue primary execution of the program. In other words, the new process is most often the worker process.

#include <sys/types.h>
#include <unistd.h>
   
pid_t spc_fork(void) {
  pid_t childpid;
   
  if ((childpid = fork(  )) =  = -1) return -1;
   
  /* Reseed PRNGs in both the parent and the child */
  /* See Chapter 11 for examples */
   
  /* If this is the parent process, there's nothing more to do */
  if (childpid != 0) return childpid;
   
  /* This is the child process */
  spc_sanitize_files(  );   /* Close all open files.  See Recipe 1.1 */
  spc_drop_privileges(1); /* Permanently drop privileges.  See Recipe 1.3 */
   
  return 0;
}


[4] Note that we say “program” here rather than “process.” When fork( ) completes, the same program is running, but there are now two processes. The newly created process has a nearly identical copy of the original process, but it is a copy; any action performed in one process does not affect the other. In a threaded environment, each thread shares the same process, so all memory, file descriptors, signals, and so on are shared.

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.