diff --git a/contrib/pg_upgrade/test.sh b/contrib/pg_upgrade/test.sh index baa7d47..9d31f9a 100644 --- a/contrib/pg_upgrade/test.sh +++ b/contrib/pg_upgrade/test.sh @@ -17,15 +17,43 @@ set -e unset MAKEFLAGS unset MAKELEVEL -# Set listen_addresses desirably +# Establish how the server will listen for connections testhost=`uname -s` case $testhost in - MINGW*) LISTEN_ADDRESSES="localhost" ;; - *) LISTEN_ADDRESSES="" ;; + MINGW*) + LISTEN_ADDRESSES="localhost" + PGHOST=""; unset PGHOST + ;; + *) + LISTEN_ADDRESSES="" + # Select a socket directory. The algorithm is from the "configure" + # script; the outcome mimics pg_regress.c:make_temp_sockdir(). + PGHOST=$PG_REGRESS_SOCK_DIR + if [ "x$PGHOST" = x ]; then + { + dir=`(umask 077 && + mktemp -d /tmp/pg_upgrade_check-XXXXXX) 2>/dev/null` && + [ -d "$dir" ] + } || + { + dir=/tmp/pg_upgrade_check-$$-$RANDOM + (umask 077 && mkdir "$dir") + } || + { + echo "could not create socket temporary directory in \"/tmp\"" + exit 1 + } + + PGHOST=$dir + trap 'rm -rf "$PGHOST"' 0 + trap 'exit 3' 1 2 13 15 + fi + export PGHOST + ;; esac -POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES" +POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES -k \"$PGHOST\"" temp_root=$PWD/tmp_check @@ -86,7 +114,6 @@ PGSERVICE=""; unset PGSERVICE PGSSLMODE=""; unset PGSSLMODE PGREQUIRESSL=""; unset PGREQUIRESSL PGCONNECT_TIMEOUT=""; unset PGCONNECT_TIMEOUT -PGHOST=""; unset PGHOST PGHOSTADDR=""; unset PGHOSTADDR # Select a non-conflicting port number, similarly to pg_regress.c diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index aee049a..13802e8 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -58,21 +58,14 @@ make check - This test method starts a temporary server, which is configured to accept - any connection originating on the local machine. Any local user can gain - database superuser privileges when connecting to this server, and could - in principle exploit all privileges of the operating-system user running - the tests. Therefore, it is not recommended that you use make - check on machines shared with untrusted users. Instead, run the tests - after completing the installation, as described in the next section. - - - - On Unix-like machines, this danger can be avoided if the temporary - server's socket file is made inaccessible to other users, for example - by running the tests in a protected chroot. On Windows, the temporary - server opens a locally-accessible TCP socket, so filesystem protections - cannot help. + On systems lacking Unix-domain sockets, notably Windows, this test method + starts a temporary server configured to accept any connection originating + on the local machine. Any local user can gain database superuser + privileges when connecting to this server, and could in principle exploit + all privileges of the operating-system user running the tests. Therefore, + it is not recommended that you use make check on an affected + system shared with untrusted users. Instead, run the tests after + completing the installation, as described in the next section. diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 803cf90..27c46ab 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -30,6 +30,7 @@ #endif #include "getopt_long.h" +#include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */ #include "pg_config_paths.h" /* for resultmap we need a list of pairs of strings */ @@ -109,6 +110,12 @@ static const char *progname; static char *logfilename; static FILE *logfile; static char *difffilename; +static const char *sockdir; +#ifdef HAVE_UNIX_SOCKETS +static const char *temp_sockdir; +static char sockself[MAXPGPATH]; +static char socklock[MAXPGPATH]; +#endif static _resultmap *resultmap = NULL; @@ -307,6 +314,82 @@ stop_postmaster(void) } } +#ifdef HAVE_UNIX_SOCKETS +/* + * Remove the socket temporary directory. pg_regress never waits for a + * postmaster exit, so it is indeterminate whether the postmaster has yet to + * unlink the socket and lock file. Unlink them here so we can proceed to + * remove the directory. Ignore errors; leaking a temporary directory is + * unimportant. This can run from a signal handler. The code is not + * acceptable in a Windows signal handler (see initdb.c:trapsig()), but + * Windows is not a HAVE_UNIX_SOCKETS platform. + */ +static void +remove_temp(void) +{ + Assert(temp_sockdir); + unlink(sockself); + unlink(socklock); + rmdir(temp_sockdir); +} + +/* + * Signal handler that calls remove_temp() and reraises the signal. + */ +static void +signal_remove_temp(int signum) +{ + remove_temp(); + + pqsignal(signum, SIG_DFL); + raise(signum); +} + +/* + * Create a temporary directory suitable for the server's Unix-domain socket. + * The directory will have mode 0700 or stricter, so no other OS user can open + * our socket to exploit our use of trust authentication. Most systems + * constrain the length of socket paths well below _POSIX_PATH_MAX, so we + * place the directory under /tmp rather than relative to the possibly-deep + * current working directory. + * + * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits + * testing to work in builds that relocate it to a directory not writable to + * the build/test user. + */ +static const char * +make_temp_sockdir(void) +{ + char *template = strdup("/tmp/pg_regress-XXXXXX"); + + temp_sockdir = mkdtemp(template); + if (temp_sockdir == NULL) + { + fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), + progname, template, strerror(errno)); + exit(2); + } + + /* Stage file names for remove_temp(). Unsafe in a signal handler. */ + UNIXSOCK_PATH(sockself, port, temp_sockdir); + snprintf(socklock, sizeof(socklock), "%s.lock", sockself); + + /* Remove the directory during clean exit. */ + atexit(remove_temp); + + /* + * Remove the directory before dying to the usual signals. Omit SIGQUIT, + * preserving it as a quick, untidy exit. + */ + pqsignal(SIGHUP, signal_remove_temp); + pqsignal(SIGINT, signal_remove_temp); + pqsignal(SIGPIPE, signal_remove_temp); + pqsignal(SIGTERM, signal_remove_temp); + + return temp_sockdir; +} +#endif /* HAVE_UNIX_SOCKETS */ + /* * Check whether string matches pattern * @@ -759,8 +842,7 @@ initialize_environment(void) * the wrong postmaster, or otherwise behave in nondefault ways. (Note * we also use psql's -X switch consistently, so that ~/.psqlrc files * won't mess things up.) Also, set PGPORT to the temp port, and set - * or unset PGHOST depending on whether we are using TCP or Unix - * sockets. + * PGHOST depending on whether we are using TCP or Unix sockets. */ unsetenv("PGDATABASE"); unsetenv("PGUSER"); @@ -769,10 +851,20 @@ initialize_environment(void) unsetenv("PGREQUIRESSL"); unsetenv("PGCONNECT_TIMEOUT"); unsetenv("PGDATA"); +#ifdef HAVE_UNIX_SOCKETS if (hostname != NULL) doputenv("PGHOST", hostname); else - unsetenv("PGHOST"); + { + sockdir = getenv("PG_REGRESS_SOCK_DIR"); + if (!sockdir) + sockdir = make_temp_sockdir(); + doputenv("PGHOST", sockdir); + } +#else + Assert(hostname != NULL); + doputenv("PGHOST", hostname); +#endif unsetenv("PGHOSTADDR"); if (port != -1) { @@ -2067,7 +2159,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc /* * To reduce chances of interference with parallel installations, use * a port number starting in the private range (49152-65535) - * calculated from the version number. + * calculated from the version number. This aids !HAVE_UNIX_SOCKETS + * systems; elsewhere, the use of a private socket directory already + * prevents interference. */ port = 0xC000 | (PG_VERSION_NUM & 0x3FFF); @@ -2240,10 +2334,11 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc */ header(_("starting postmaster")); snprintf(buf, sizeof(buf), - "\"%s/postgres\" -D \"%s/data\" -F%s -c \"listen_addresses=%s\" > \"%s/log/postmaster.log\" 2>&1", - bindir, temp_install, - debug ? " -d 5" : "", - hostname ? hostname : "", + "\"%s/postgres\" -D \"%s/data\" -F%s " + "-c \"listen_addresses=%s\" -k \"%s\" " + "> \"%s/log/postmaster.log\" 2>&1", + bindir, temp_install, debug ? " -d 5" : "", + hostname ? hostname : "", sockdir ? sockdir : "", outputdir); postmaster_pid = spawn_process(buf); if (postmaster_pid == INVALID_PID)