From 845302b9b67f8ac10b050145c31e4463e60decb8 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Thu, 27 Aug 2020 10:14:53 -0400 Subject: AS_INIT: ensure fds 0, 1, 2 are open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A patch was recently proposed for GNU libc to make *all* processes start up with file descriptors 0, 1, and 2 guaranteed to be open. Part of the rationale for this patch was that configure scripts fail catastrophically if these fds are closed, even if you just want to run --help or --version, e.g. $ ./configure --version <&-; echo $? ./configure: line 555: 0: Bad file descriptor 1 configure scripts cannot rely on behavior specific to GNU libc, so whether or not that patch gets committed, it makes sense for us to make configure scripts robust against being started up with closed stdin/stdout/stderr. This patch adds code to ensure fds 0, 1, and 2 are open, early in _AS_SHELL_SANITIZE. It uses a construct, ‘(exec 3>&n)’, that’s known not to work in very old shells, but that’s OK because those shells will be rejected by _AS_DETECT_BETTER_SHELL anyway. The worst-case scenario is that the “This script requires a shell more modern than all the shells I found on your system” error message won’t get printed. When these fds are found not to be open, we open them on /dev/null, in the normal I/O direction (0 for reading, 1 and 2 for writing). There is a case for opening them in the *opposite* direction so that, for instance, writes to fd 1 will fail when fd 1 started out closed. However, that would expose latent bugs that I think should be dealt with *after* 2.70. (See Savannah bug #110300 for more detail.) I also took the opportunity to rationalize the order of operations in _AS_SHELL_SANITIZE a little. All the special shell and environment variables that we care about are dealt with immediately after AS_BOURNE_COMPATIBLE, and _AS_PATH_SEPARATOR_PREPARE happens immediately before the first use of _AS_PATH_WALK. * lib/m4sugar/m4sh.m4 (_AS_ENSURE_STANDARD_FDS): New macro. (_AS_SHELL_SANITIZE): Move the “Unset variables that we do not need” and “NLS nuisances” blocks immediately after setting IFS; merge the unsetting of CDPATH into the main unsetting loop; move invocation of _AS_PATH_SEPARATOR_PREPARE to immediately above the “Find who we are” block; invoke _AS_ENSURE_STANDARD_FDS immediately before _AS_PATH_SEPARATOR_PREPARE. * tests/base.at (configure with closed standard fds): New test. * tests/torture.at (--help and --version in unwritable directory): New test. --- tests/base.at | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'tests/base.at') diff --git a/tests/base.at b/tests/base.at index 4042a8aa..6894990a 100644 --- a/tests/base.at +++ b/tests/base.at @@ -846,3 +846,76 @@ FOO ]]) AT_CLEANUP + +## ----------------------------------- ## +## configure with closed standard fds ## +## ----------------------------------- ## + +AT_SETUP([configure with closed standard fds]) +AT_KEYWORDS([AS@&t@_INIT]) + +# Create a configure script that runs a relatively complicated but +# self-contained test. Run it. +AT_CONFIGURE_AC([[AC_PROG_CC]]) +AT_CHECK_AUTOCONF +AT_CHECK_AUTOHEADER +AT_CHECK_CONFIGURE([], [], [stdout], [stderr]) +AT_CHECK_ENV + +mv stdout stdout-expected +mv stderr stderr-expected +mv state-env.after state-env-expected +mv config.status config-status-expected +mv config.h config-h-expected + +# Run it again with stdin (fd 0) closed. +# There should be no change to the stdout or stderr output and thoe +# result of configuration should be the same. + +AT_CHECK_CONFIGURE([ 0<&- ], [], [stdout], [stderr]) +AT_CHECK_ENV +AT_CMP([stdout-expected], [stdout]) +AT_CMP([stderr-expected], [stderr]) +AT_CONFIG_CMP([state-env-expected], [state-env.after]) + +mv stdout stdout-closed-0 +mv stderr stderr-closed-0 +mv state-env.after state-env-closed-0 +mv config.status config-status-closed-0 +mv config.h config-h-closed-0 + +# Run it again with stdout (fd 1) closed. +# There should be no change to the stderr output and the +# result of configuration should be the same. (Any output +# that would have gone to stdout, of course, is lost.) + +AT_CHECK_CONFIGURE([ 1>&- ], [], [stdout], [stderr]) +AT_CHECK_ENV +AT_CHECK([test -f stdout && test ! -s stdout]) +AT_CMP([stderr-expected], [stderr]) +AT_CONFIG_CMP([state-env-expected], [state-env.after]) + +mv stdout stdout-closed-1 +mv stderr stderr-closed-1 +mv state-env.after state-env-closed-1 +mv config.status config-status-closed-1 +mv config.h config-h-closed-1 + +# Run it again with stderr (fd 2) closed. +# There should be no change to the stdout output and the +# result of configuration should be the same. (Any output +# that would have gone to stderr, of course, is lost.) + +AT_CHECK_CONFIGURE([ 2>&- ], [], [stdout], [stderr]) +AT_CHECK_ENV +AT_CMP([stdout-expected], [stdout]) +AT_CHECK([test -f stderr && test ! -s stderr]) +AT_CONFIG_CMP([state-env-expected], [state-env.after]) + +mv stdout stdout-closed-2 +mv stderr stderr-closed-2 +mv state-env.after state-env-closed-2 +mv config.status config-status-closed-2 +mv config.h config-h-closed-2 + +AT_CLEANUP -- cgit v1.2.1