* modules/freopen-safer: New module.
* m4/stdio-safer.m4 (gl_FREOPEN_SAFER): New macro.
* lib/freopen-safer.c (freopen_safer): New file.
* lib/stdio-safer.h (freopen_safer): New declaration.
* lib/stdio--.h (freopen): New override.
* MODULES.html.sh (File stream based Input/Output): Mention it.
* doc/posix-functions/freopen.texi (freopen): Mention pitfalls and
freopen-safer module.
* doc/posix-functions/stderr.texi (stderr): Likewise.
* doc/posix-functions/stdin.texi (stdin): Likewise.
* doc/posix-functions/stdout.texi (stdout): Likewise.
* modules/freopen-safer-tests: New test.
* tests/test-reopen-safer.c: New file.
Signed-off-by: Eric Blake <ebb9@byu.net>
+2009-11-05 Eric Blake <ebb9@byu.net>
+
+ freopen-safer: new module
+ * modules/freopen-safer: New module.
+ * m4/stdio-safer.m4 (gl_FREOPEN_SAFER): New macro.
+ * lib/freopen-safer.c (freopen_safer): New file.
+ * lib/stdio-safer.h (freopen_safer): New declaration.
+ * lib/stdio--.h (freopen): New override.
+ * MODULES.html.sh (File stream based Input/Output): Mention it.
+ * doc/posix-functions/freopen.texi (freopen): Mention pitfalls and
+ freopen-safer module.
+ * doc/posix-functions/stderr.texi (stderr): Likewise.
+ * doc/posix-functions/stdin.texi (stdin): Likewise.
+ * doc/posix-functions/stdout.texi (stdout): Likewise.
+ * modules/freopen-safer-tests: New test.
+ * tests/test-reopen-safer.c: New file.
+
2009-11-05 Jim Meyering <meyering@redhat.com>
maint.mk: Prohibit inclusion of "close-stream.h" without use.
func_module freading
func_module freadptr
func_module freadseek
+ func_module freopen-safer
func_module fwritable
func_module fwriting
func_module getpass
and (without the slash) names a nonexistent file or a file that is not a
directory, on some platforms:
HP-UX 11.00, Solaris 9, Irix 5.3.
+@item
+Applications should not assume that @code{fileno(f)} will be the same
+before and after a call to @code{freopen(name,mode,f)}. However, the
+module freopen-safer can at least protect @code{stdin}, @code{stdout},
+and @code{stderr}.
@end itemize
One workaround is to use freopen(NULL, ``r+'', stderr) on Cygwin 1.5.21
or newer. Another is to use the gnulib ftello module and do
ftello(stderr).
+@item
+POSIX states that a setuid application can guarantee that fd 2 is
+open, but some systems guarantee this even for non-setuid programs.
+If an application is executed with fd 2 closed, use of @code{stderr}
+can affect an unrelated file that happened to be assigned to fd 2.
+The gnulib *-safer modules may be used to guarantee that fd 2 stays
+reserved for @code{stderr}.
@end itemize
One workaround is to use freopen(NULL, ``r'', stdin) on Cygwin 1.5.21
or newer. Another is to use the gnulib ftello module and do
ftello(stdin).
+@item
+POSIX states that a setuid application can guarantee that fd 0 is
+open, but some systems guarantee this even for non-setuid programs.
+If an application is executed with fd 0 closed, use of @code{stdin}
+can affect an unrelated file that happened to be assigned to fd 0.
+The gnulib *-safer modules may be used to guarantee that fd 0 stays
+reserved for @code{stdin}.
@end itemize
One workaround is to use freopen(NULL, ``w'', stdout) on Cygwin 1.5.21
or newer. Another is to use the gnulib ftello module and do
ftello(stdout).
+@item
+POSIX states that a setuid application can guarantee that fd 1 is
+open, but some systems guarantee this even for non-setuid programs.
+If an application is executed with fd 1 closed, use of @code{stdout}
+can affect an unrelated file that happened to be assigned to fd 1.
+The gnulib *-safer modules may be used to guarantee that fd 1 stays
+reserved for @code{stdout}.
@end itemize
--- /dev/null
+/* Invoke freopen, but avoid some glitches.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake. */
+
+#include <config.h>
+
+#include "stdio-safer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+/* Guarantee that FD is open; all smaller FDs must already be open.
+ Return true if successful. */
+static bool
+protect_fd (int fd)
+{
+ int value = open ("/dev/null", O_RDONLY);
+ if (value != fd)
+ {
+ if (0 <= value)
+ {
+ close (value);
+ errno = EBADF; /* Unexpected; this is as good as anything else. */
+ }
+ return false;
+ }
+ return true;
+}
+
+/* Like freopen, but guarantee that reopening stdin, stdout, or stderr
+ preserves the invariant that STDxxx_FILENO==fileno(stdxxx), and
+ that no other stream will interfere with the standard streams.
+ This is necessary because most freopen implementations will change
+ the associated fd of a stream to the lowest available slot. */
+
+FILE *
+freopen_safer (char const *name, char const *mode, FILE *f)
+{
+ /* Unfortunately, we cannot use the fopen_safer approach of using
+ fdopen (dup_safer (fileno (freopen (cmd, mode, f)))), because we
+ need to return f itself. The implementation of freopen(NULL,m,f)
+ is system-dependent, so the best we can do is guarantee that all
+ lower-valued standard fds are open prior to the freopen call,
+ even though this puts more pressure on open fds. */
+ bool protect_in = false;
+ bool protect_out = false;
+ bool protect_err = false;
+ int saved_errno;
+
+ switch (fileno (f))
+ {
+ default: /* -1 or not a standard stream. */
+ if (dup2 (STDERR_FILENO, STDERR_FILENO) != STDERR_FILENO)
+ protect_err = true;
+ /* fall through */
+ case STDERR_FILENO:
+ if (dup2 (STDOUT_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
+ protect_out = true;
+ /* fall through */
+ case STDOUT_FILENO:
+ if (dup2 (STDIN_FILENO, STDIN_FILENO) != STDIN_FILENO)
+ protect_in = true;
+ /* fall through */
+ case STDIN_FILENO:
+ /* Nothing left to protect. */
+ break;
+ }
+ if (protect_in && !protect_fd (STDIN_FILENO))
+ f = NULL;
+ else if (protect_out && !protect_fd (STDOUT_FILENO))
+ f = NULL;
+ else if (protect_err && !protect_fd (STDERR_FILENO))
+ f = NULL;
+ else
+ f = freopen (name, mode, f);
+ saved_errno = errno;
+ if (protect_err)
+ close (STDERR_FILENO);
+ if (protect_out)
+ close (STDOUT_FILENO);
+ if (protect_in)
+ close (STDIN_FILENO);
+ if (!f)
+ errno = saved_errno;
+ return f;
+}
# define fopen fopen_safer
#endif
+#if GNULIB_FREOPEN_SAFER
+# undef freopen
+# define freopen freopen_safer
+#endif
+
#if GNULIB_TMPFILE_SAFER
# undef tmpfile
# define tmpfile tmpfile_safer
#include <stdio.h>
+#if GNULIB_FOPEN_SAFER
FILE *fopen_safer (char const *, char const *);
+#endif
+
+#if GNULIB_FREOPEN_SAFER
+FILE *freopen_safer (char const *, char const *, FILE *);
+#endif
+
+#if GNULIB_POPEN_SAFER
FILE *popen_safer (char const *, char const *);
+#endif
+
+#if GNULIB_TMPFILE_SAFER
FILE *tmpfile_safer (void);
+#endif
-#serial 11
+#serial 12
dnl Copyright (C) 2002, 2005-2007, 2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
AC_LIBOBJ([fopen-safer])
])
+AC_DEFUN([gl_FREOPEN_SAFER],
+[
+ AC_LIBOBJ([freopen-safer])
+])
+
AC_DEFUN([gl_POPEN_SAFER],
[
AC_LIBOBJ([popen-safer])
--- /dev/null
+Description:
+freopen function that avoids clobbering std{in,out,err}.
+
+Files:
+lib/stdio--.h
+lib/stdio-safer.h
+lib/freopen-safer.c
+m4/stdio-safer.m4
+
+Depends-on:
+dup2
+freopen
+open
+stdbool
+
+configure.ac:
+gl_FREOPEN_SAFER
+gl_MODULE_INDICATOR([freopen-safer])
+
+Makefile.am:
+
+Include:
+"stdio-safer.h"
+
+License:
+GPL
+
+Maintainer:
+Eric Blake
--- /dev/null
+Files:
+tests/test-freopen-safer.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-freopen-safer
+check_PROGRAMS += test-freopen-safer
--- /dev/null
+/* Test of reopening a stream.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009. */
+
+#include <config.h>
+
+/* Specification. */
+#include "stdio--.h"
+
+/* Helpers. */
+#include <stdlib.h>
+#include <unistd.h>
+
+/* This test intentionally closes stderr. So, we arrange to have fd 10
+ (outside the range of interesting fd's during the test) set up to
+ duplicate the original stderr. */
+
+#define BACKUP_STDERR_FILENO 10
+static FILE *myerr;
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (myerr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (myerr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+int
+main (int argc, char **argv)
+{
+ FILE *fp;
+
+ /* We close fd 2 later, so save it in fd 10. */
+ if (dup2 (STDERR_FILENO, BACKUP_STDERR_FILENO) != BACKUP_STDERR_FILENO
+ || (myerr = fdopen (BACKUP_STDERR_FILENO, "w")) == NULL)
+ return 2;
+
+ {
+ FILE *tmp;
+ ASSERT (tmp = fopen ("/dev/null", "r"));
+ ASSERT (STDERR_FILENO < fileno (tmp));
+ ASSERT (fp = fopen ("/dev/null", "w"));
+ ASSERT (fileno (tmp) < fileno (fp));
+ ASSERT (fclose (tmp) == 0);
+ }
+
+ /* Gap in fds. */
+ ASSERT (freopen ("/dev/null", "r+", fp) == fp);
+ ASSERT (STDERR_FILENO < fileno (fp));
+
+ ASSERT (freopen ("/dev/null", "r", stdin) == stdin);
+ ASSERT (STDIN_FILENO == fileno (stdin));
+
+ ASSERT (freopen ("/dev/null", "w", stdout) == stdout);
+ ASSERT (STDOUT_FILENO == fileno (stdout));
+
+ ASSERT (freopen ("/dev/null", "w", stderr) == stderr);
+ ASSERT (STDERR_FILENO == fileno (stderr));
+
+ /* fd 0 closed. */
+ ASSERT (close (STDIN_FILENO) == 0);
+
+ ASSERT (freopen ("/dev/null", "w", stdout) == stdout);
+ ASSERT (STDOUT_FILENO == fileno (stdout));
+
+ ASSERT (freopen ("/dev/null", "w", stderr) == stderr);
+ ASSERT (STDERR_FILENO == fileno (stderr));
+
+ ASSERT (freopen ("/dev/null", "a", fp) == fp);
+ ASSERT (STDERR_FILENO < fileno (fp));
+
+ /* fd 1 closed. */
+ ASSERT (close (STDOUT_FILENO) == 0);
+
+ ASSERT (freopen ("/dev/null", "w", stderr) == stderr);
+ ASSERT (STDERR_FILENO == fileno (stderr));
+
+ ASSERT (freopen ("/dev/null", "a+", fp) == fp);
+ ASSERT (STDERR_FILENO < fileno (fp));
+
+ /* fd 2 closed. */
+ ASSERT (close (STDERR_FILENO) == 0);
+
+ ASSERT (freopen ("/dev/null", "w+", fp) == fp);
+ ASSERT (STDERR_FILENO < fileno (fp));
+
+ return 0;
+}