When your program starts up, you
want to make sure that only the standard
stdin
,
stdout
, and stderr
file
descriptors are open, thus avoiding denial of service
attacks and avoiding having an attacker place untrusted files on
special hardcoded file descriptors.
On Unix, use the function getdtablesize(
)
to obtain the size of the process’s file descriptor
table. For each file descriptor in the process’s
table, close the descriptors that are not stdin
,
stdout
, or stderr
, which are
always 0, 1, and 2, respectively. Test stdin
,
stdout
, and stderr
to ensure
that they’re open using fstat(
)
for
each descriptor. If any one is not open, open
/dev/null and associate with the descriptor. If
the program is running setuid, stdin
,
stdout
, and stderr
should also
be closed if they’re not associated with a tty, and
reopened using /dev/null.
On Windows, there is no way to determine what file handles are open, but the same issue with open descriptors does not exist on Windows as it does on Unix.
Normally, when a process is started, it inherits all open file descriptors from its parent. This can be a problem because the size of the file descriptor table on Unix is typically a fixed size. The parent process could therefore fill the file descriptor table with bogus files to deny your program any file handles for opening its own files. The result is essentially a denial of service for your program.
When a new file is opened, a descriptor is assigned using the first
available entry in the process’s file descriptor
table. If stdin
is not open, for example, the
first file opened is assigned a file descriptor of 0, which is
normally reserved for stdin
. Similarly, if
stdout
is not open, file descriptor 1 is assigned
next, followed by stderr
’s file
descriptor of 2 if it is not open.
The only file descriptors that should remain open when your program
starts are the stdin
, stdout
,
and stderr
descriptors. If the standard
descriptors are not open, your program should open them using
/dev/null
and leave them open.
Otherwise, calls to functions like printf( )
can
have unexpected and potentially disastrous effects. Worse, the
standard C library considers the standard descriptors to be special,
and some functions expect stderr
to be properly
opened for writing error messages to. If your program opens a data
file for writing and gets
stderr
’s file descriptor, an
error message written to stderr
will destroy your
data file.
Warning
Particularly in a chroot( )
environment (see
Recipe 2.12), the
/dev/null
device may not be available (it can be made available if the
environment is set up properly). If it is not available, the proper
thing for your program to do is to refuse to run.
The potential for security vulnerabilities arising from file
descriptors being managed improperly is high in non-setuid programs.
For setuid (especially setuid root) programs, the potential for
problems increases dramatically. The problem is so serious that some
variants of Unix (OpenBSD, in particular) will explicitly open
stdin
, stdout
, and
stderr
from the execve(
)
system
call for a setuid process if they’re not already
open.
The following function, spc_sanitize_files(
)
, first closes all open
file descriptors that
are not one of the standard descriptors. Because there is no easy way
to tell whether a descriptor is open, close( )
is
called for each one, and any error returned is ignored. Once all of
the nonstandard descriptors are closed, stdin
,
stdout
, and stderr
are checked
to ensure that they are open. If any one of them is not open, an
attempt is made to open /dev/null. If
/dev/null cannot be opened, the program is
terminated immediately.
#include <sys/types.h> #include <limits.h> #include <sys/stat.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <paths.h> #ifndef OPEN_MAX #define OPEN_MAX 256 #endif static int open_devnull(int fd) { FILE *f = 0; if (!fd) f = freopen(_PATH_DEVNULL, "rb", stdin); else if (fd = = 1) f = freopen(_PATH_DEVNULL, "wb", stdout); else if (fd = = 2) f = freopen(_PATH_DEVNULL, "wb", stderr); return (f && fileno(f) = = fd); } void spc_sanitize_files(void) { int fd, fds; struct stat st; /* Make sure all open descriptors other than the standard ones are closed */ if ((fds = getdtablesize( )) = = -1) fds = OPEN_MAX; for (fd = 3; fd < fds; fd++) close(fd); /* Verify that the standard descriptors are open. If they're not, attempt to * open them using /dev/null. If any are unsuccessful, abort. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) = = -1 && (errno != EBADF || !open_devnull(fd))) abort( ); }
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.