Next: , Previous: , Up: Particular Modules   [Contents][Index]


14.7 Handling closed standard file descriptors

Usually, when a program gets invoked, its file descriptors 0 (for standard input), 1 (for standard output), and 2 (for standard error) are open. But there are situations when some of these file descriptors are closed. These situations can arise when

When a closed file descriptor is accessed through a system call, such as fcntl(), fstat(), read(), or write(), the system calls fails with error EBADF ("Bad file descriptor").

When a new file descriptor is allocated, the operating system chooses the smallest non-negative integer that does not yet correspond to an open file descriptor. So, when a given fd (0, 1, or 2) is closed, opening a new file descriptor may assign the new file descriptor to this fd. This can have unintended effects, because now standard input/output/error of your process is referring to a file that was not meant to be used in that role.

This situation is a security risk because the behaviour of the program in this situation was surely never tested, therefore anything can happen then – from overwriting precious files of the user to endless loops.

To deal with this situation, you first need to determine whether your program is affected by the problem.

If your program is affected, what is the mitigation?

Some operating systems install open file descriptors in place of the closed ones, either in the exec system call or during program startup. When such a file descriptor is accessed through a system call, it behaves like an open file descriptor opened for the “wrong” direction: the system calls fcntl() and fstat() succeed, whereas read() from fd 0 and write() to fd 1 or 2 fail with error EBADF ("Bad file descriptor"). The important point here is that when your program allocates a new file descriptor, it will have a value greater than 2.

This mitigation is enabled on HP-UX, for all programs, and on glibc, FreeBSD, NetBSD, OpenBSD, but only for setuid or setgid programs. Since it is operating system dependent, it is not a complete mitigation.

For a complete mitigation, Gnulib provides two alternative sets of modules:

The approach with the xstdopen module is simpler, but it adds three system calls to program startup. Whereas the approach with the *-safer modules is more complex, but adds no overhead (no additional system calls) in the normal case.

To use the approach with the xstdopen module:

  1. Import the module xstdopen from Gnulib.
  2. In the compilation unit that contains the main function, include "xstdopen.h".
  3. In the main function, near the beginning, namely right after the i18n related initializations (setlocale, bindtextdomain, textdomain invocations, if any) and the closeout initialization (if any), insert the invocation:
    /* Ensure that stdin, stdout, stderr are open.  */
    xstdopen ();
    

To use the approach with the *-safer modules:

  1. Import the relevant modules from Gnulib.
  2. In the compilation units that contain these function calls, include the replacement header file.

Do so according to this table:

FunctionModuleHeader file
open()fcntl-safer"fcntl--.h"
openat()openat-safer"fcntl--.h"
creat()fcntl-safer"fcntl--.h"
dup()unistd-safer"unistd--.h"
fopen()fopen-safer"stdio--.h"
freopen()freopen-safer"stdio--.h"
pipe()unistd-safer"unistd--.h"
pipe2()pipe2-safer"unistd--.h"
popen()popen-safer"stdio--.h"
opendir()dirent-safer"dirent--.h"
tmpfile()tmpfile-safer"stdio--.h"
mkstemp()stdlib-safer"stdlib--.h"
mkstemps()stdlib-safer"stdlib--.h"
mkostemp()stdlib-safer"stdlib--.h"
mkostemps()stdlib-safer"stdlib--.h"

Next: , Previous: , Up: Particular Modules   [Contents][Index]