changeset 40092:943155cd4a86

doc: Document the xstdopen and *-safer modules. * doc/xstdopen.texi: New file. * doc/gnulib.texi (Particular Modules): Include it.
author Bruno Haible <bruno@clisp.org>
date Sun, 06 Jan 2019 15:58:46 +0100
parents fd583bcb1e66
children 7201768c5696
files ChangeLog doc/gnulib.texi doc/xstdopen.texi
diffstat 3 files changed, 235 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Jan 06 09:27:42 2019 +0100
+++ b/ChangeLog	Sun Jan 06 15:58:46 2019 +0100
@@ -1,3 +1,9 @@
+2019-01-06  Bruno Haible  <bruno@clisp.org>
+
+	doc: Document the xstdopen and *-safer modules.
+	* doc/xstdopen.texi: New file.
+	* doc/gnulib.texi (Particular Modules): Include it.
+
 2019-01-06  Bruno Haible  <bruno@clisp.org>
 
 	xstdopen: Add tests.
--- a/doc/gnulib.texi	Sun Jan 06 09:27:42 2019 +0100
+++ b/doc/gnulib.texi	Sun Jan 06 15:58:46 2019 +0100
@@ -6365,6 +6365,7 @@
 * Compile-time Assertions::
 * Integer Properties::
 * extern inline::
+* Closed standard fds::
 * String Functions in C Locale::
 * Quoting::
 * error and progname::
@@ -6394,6 +6395,8 @@
 
 @include extern-inline.texi
 
+@include xstdopen.texi
+
 @include c-locale.texi
 
 @include quote.texi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/xstdopen.texi	Sun Jan 06 15:58:46 2019 +0100
@@ -0,0 +1,226 @@
+@c GNU xstdopen and *-safer modules documentation
+
+@c Copyright (C) 2019 Free Software Foundation, Inc.
+
+@c Permission is granted to copy, distribute and/or modify this document
+@c under the terms of the GNU Free Documentation License, Version 1.3
+@c or any later version published by the Free Software Foundation;
+@c with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
+@c Texts.  A copy of the license is included in the ``GNU Free
+@c Documentation License'' file as part of this distribution.
+
+@c Written by Bruno Haible, based on ideas from Paul Eggert.
+
+@node Closed standard fds
+@section Handling closed standard file descriptors
+
+@cindex xstdopen
+@cindex stdopen
+@cindex dirent-safer
+@cindex fcntl-safer
+@cindex fopen-safer
+@cindex freopen-safer
+@cindex openat-safer
+@cindex pipe2-safer
+@cindex popen-safer
+@cindex stdlib-safer
+@cindex tmpfile-safer
+@cindex unistd-safer
+
+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
+@itemize @bullet
+@item
+The invoking process invokes @code{close()} on the file descriptor before
+@code{exec}, or
+@item
+The invoking process invokes @code{posix_spawn_file_actions_addclose()} for
+the file descriptor before @code{posix_spawn} or @code{posix_spawnp}, or
+@item
+The invoking process is a Bourne shell, and the shell script uses the
+POSIX syntax for closing the file descriptor:
+@code{<&-} for closing standard input,
+@code{>&-} for closing standard output, or
+@code{2>&-} for closing standard error.
+@end itemize
+
+When a closed file descriptor is accessed through a system call, such as
+@code{fcntl()}, @code{fstat()}, @code{read()}, or @code{write()}, the
+system calls fails with error @code{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.
+@itemize @bullet
+@item
+Does your program invoke functions that allocate new file descriptors?
+These are the system calls
+@itemize @bullet
+@item
+@code{open()}, @code{openat()}, @code{creat()}
+@item
+@code{dup()}
+@item
+@code{fopen()}, @code{freopen()}
+@item
+@code{pipe()}, @code{pipe2()}, @code{popen()}
+@item
+@code{opendir()}
+@item
+@code{tmpfile()}, @code{mkstemp()}, @code{mkstemps()}, @code{mkostemp()},
+@code{mkostemps()}
+@end itemize
+@noindent
+Note that you also have to consider the libraries that your program uses.
+@item
+If your program may open two or more file descriptors or FILE streams for
+reading at the same time, and some of them may reference standard input,
+your program @emph{is affected}.
+@item
+If your program may open two or more file descriptors or FILE streams for
+writing at the same time, and some of them may reference standard output
+or standard error, your program @emph{is affected}.
+@item
+If your program does not open new file descriptors or FILE streams, it is
+@emph{not affected}.
+@item
+If your program opens only one new file descriptor or FILE stream at a time,
+it is @emph{not affected}.  This is often the case for programs that are
+structured in simple phases: first a phase where input is read from a file
+into memory, then a phase of processing in memory, finally a phase where
+the result is written to a file.
+@item
+If your program opens only two new file descriptors or FILE streams at a
+time, out of which one is for reading and the one is for writing, it is
+@emph{not affected}.  This is because if the first file descriptor is
+allocated and the second file descriptor is picked as 0, 1, or 2, and
+both happen to be the same, writing to the one opened in @code{O_RDONLY}
+mode will produce an error @code{EBADF}, as desired.
+@end itemize
+
+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 @code{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 @code{fcntl()} and @code{fstat()} succeed, whereas
+@code{read()} from fd 0 and @code{write()} to fd 1 or 2 fail with error
+@code{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:
+@itemize @bullet
+@item
+The @code{xstdopen} module.
+@item
+The @code{*-safer} modules:
+@code{fcntl-safer},
+@code{openat-safer},
+@code{unistd-safer},
+@code{fopen-safer},
+@code{freopen-safer},
+@code{pipe2-safer},
+@code{popen-safer},
+@code{dirent-safer},
+@code{tmpfile-safer},
+@code{stdlib-safer}.
+@end itemize
+
+The approach with the @code{xstdopen} module is simpler, but it adds three
+system calls to program startup.  Whereas the approach with the @code{*-safer}
+modules is more complex, but adds no overhead (no additional system calls)
+in the normal case.
+
+To use the approach with the @code{xstdopen} module:
+@enumerate
+@item
+Import the module @code{xstdopen} from Gnulib.
+@item
+In the compilation unit that contains the @code{main} function, include
+@code{"xstdopen.h"}.
+@item
+In the @code{main} function, near the beginning, namely right after
+the i18n related initializations (@code{setlocale}, @code{bindtextdomain},
+@code{textdomain} invocations, if any) and
+the @code{closeout} initialization (if any), insert the invocation:
+@smallexample
+/* Ensure that stdin, stdout, stderr are open.  */
+xstdopen ();
+@end smallexample
+@end enumerate
+
+To use the approach with the @code{*-safer} modules:
+@enumerate
+@item
+Import the relevant modules from Gnulib.
+@item
+In the compilation units that contain these function calls, include the
+replacement header file.
+@end enumerate
+Do so according to this table:
+@multitable @columnfractions .28 .32 .4
+@headitem Function @tab Module @tab Header file
+@item @code{open()}
+@tab @code{fcntl-safer}
+@tab @code{"fcntl--.h"}
+@item @code{openat()}
+@tab @code{openat-safer}
+@tab @code{"fcntl--.h"}
+@item @code{creat()}
+@tab @code{fcntl-safer}
+@tab @code{"fcntl--.h"}
+@item @code{dup()}
+@tab @code{unistd-safer}
+@tab @code{"unistd--.h"}
+@item @code{fopen()}
+@tab @code{fopen-safer}
+@tab @code{"stdio--.h"}
+@item @code{freopen()}
+@tab @code{freopen-safer}
+@tab @code{"stdio--.h"}
+@item @code{pipe()}
+@tab @code{unistd-safer}
+@tab @code{"unistd--.h"}
+@item @code{pipe2()}
+@tab @code{pipe2-safer}
+@tab @code{"unistd--.h"}
+@item @code{popen()}
+@tab @code{popen-safer}
+@tab @code{"stdio--.h"}
+@item @code{opendir()}
+@tab @code{dirent-safer}
+@tab @code{"dirent--.h"}
+@item @code{tmpfile()}
+@tab @code{tmpfile-safer}
+@tab @code{"stdio--.h"}
+@item @code{mkstemp()}
+@tab @code{stdlib-safer}
+@tab @code{"stdlib--.h"}
+@item @code{mkstemps()}
+@tab @code{stdlib-safer}
+@tab @code{"stdlib--.h"}
+@item @code{mkostemp()}
+@tab @code{stdlib-safer}
+@tab @code{"stdlib--.h"}
+@item @code{mkostemps()}
+@tab @code{stdlib-safer}
+@tab @code{"stdlib--.h"}
+@end multitable