Lock down regression testing temporary clusters on Windows.
authorNoah Misch
Thu, 18 Dec 2014 03:48:40 +0000 (22:48 -0500)
committerNoah Misch
Thu, 18 Dec 2014 03:48:46 +0000 (22:48 -0500)
Use SSPI authentication to allow connections exclusively from the OS
user that launched the test suite.  This closes on Windows the
vulnerability that commit be76a6d39e2832d4b88c0e1cc381aa44a7f86881
closed on other platforms.  Users of "make installcheck" or custom test
harnesses can run "pg_regress --config-auth=DATADIR" to activate the
same authentication configuration that "make check" would use.
Back-patch to 9.0 (all supported versions).

Security: CVE-2014-0067

contrib/dblink/Makefile
contrib/dblink/expected/dblink.out
contrib/dblink/sql/dblink.sql
contrib/pg_upgrade/test.sh
doc/src/sgml/regress.sgml
src/test/regress/pg_regress.c
src/tools/msvc/vcregress.pl

index 09032f89702a4aee07fa819d2fb15f83de72ab34..97cc98d4f06b2ebc8766564cdef75b0697e8fdcc 100644 (file)
@@ -10,7 +10,8 @@ EXTENSION = dblink
 DATA = dblink--1.1.sql dblink--1.0--1.1.sql dblink--unpackaged--1.0.sql
 
 REGRESS = paths dblink
-REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
+REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress \
+   --create-role=dblink_regression_test
 EXTRA_CLEAN = sql/paths.sql expected/paths.out
 
 # the db name is hard-coded in the tests
index f503691bf218a50fa15f71500c1be9c20af6bddc..87eb142bd30b329d48f0ca55cf30678fb9ec5052 100644 (file)
@@ -809,7 +809,6 @@ SELECT dblink_disconnect('dtest1');
 (1 row)
 
 -- test foreign data wrapper functionality
-CREATE USER dblink_regression_test;
 CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
   OPTIONS (dbname 'contrib_regression');
 CREATE USER MAPPING FOR public SERVER fdtest
@@ -851,7 +850,6 @@ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
 \c - :ORIGINAL_USER
 REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
 REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test;
-DROP USER dblink_regression_test;
 DROP USER MAPPING FOR public SERVER fdtest;
 DROP SERVER fdtest;
 -- test asynchronous notifications
index d8d248260c02d9d6ce10ee258606e3e07ba9b914..5305d5a8f5b0b2a7e8375798a058420ef0b1130e 100644 (file)
@@ -387,7 +387,6 @@ SELECT dblink_error_message('dtest1');
 SELECT dblink_disconnect('dtest1');
 
 -- test foreign data wrapper functionality
-CREATE USER dblink_regression_test;
 CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
   OPTIONS (dbname 'contrib_regression');
 CREATE USER MAPPING FOR public SERVER fdtest
@@ -408,7 +407,6 @@ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
 \c - :ORIGINAL_USER
 REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
 REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test;
-DROP USER dblink_regression_test;
 DROP USER MAPPING FOR public SERVER fdtest;
 DROP SERVER fdtest;
 
index 03dce0c0db660e297888dfa5891d6b3138196d23..c6056d20fd50ffdd2eeaecdc8dbf5fc5030b0e5e 100644 (file)
@@ -17,13 +17,20 @@ set -e
 unset MAKEFLAGS
 unset MAKELEVEL
 
+# Run a given "initdb" binary and overlay the regression testing
+# authentication configuration.
+standard_initdb() {
+   "$1" -N
+   ../../src/test/regress/pg_regress --config-auth "$PGDATA"
+}
+
 # Establish how the server will listen for connections
 testhost=`uname -s`
 
 case $testhost in
    MINGW*)
        LISTEN_ADDRESSES="localhost"
-       PGHOST=""; unset PGHOST
+       PGHOST=localhost
        ;;
    *)
        LISTEN_ADDRESSES=""
@@ -49,11 +56,11 @@ case $testhost in
            trap 'rm -rf "$PGHOST"' 0
            trap 'exit 3' 1 2 13 15
        fi
-       export PGHOST
        ;;
 esac
 
 POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES -k \"$PGHOST\""
+export PGHOST
 
 temp_root=$PWD/tmp_check
 
@@ -141,7 +148,7 @@ export EXTRA_REGRESS_OPTS
 # enable echo so the user can see what is being executed
 set -x
 
-$oldbindir/initdb -N
+standard_initdb "$oldbindir"/initdb
 $oldbindir/pg_ctl start -l "$logdir/postmaster1.log" -o "$POSTMASTER_OPTS" -w
 if "$MAKE" -C "$oldsrc" installcheck; then
    pg_dumpall -f "$temp_root"/dump1.sql || pg_dumpall1_status=$?
@@ -181,7 +188,7 @@ fi
 
 PGDATA=$BASE_PGDATA
 
-initdb -N
+standard_initdb 'initdb'
 
 pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
 
index cb412978fcf7929fc2ef7b2f1d88824c1feca039..b88038f12c30c7f124b97ce79a5c99531254bafb 100644 (file)
@@ -56,19 +56,6 @@ gmake check
    failure represents a serious problem.
   
 
-  
-   
-    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 gmake check on an affected
-    system shared with untrusted users.  Instead, run the tests after
-    completing the installation, as described in the next section.
-   
-  
-
    
     Because this test method runs a temporary server, it will not work
     if you did the build as the root user, since the server will not start as
index bad5831ec7d0bffd3475b115de59ba3ba49697f8..3e98acc47eecc021e728458aa2f0b5abd55271dc 100644 (file)
@@ -104,6 +104,7 @@ static char *dlpath = PKGLIBDIR;
 static char *user = NULL;
 static _stringlist *extraroles = NULL;
 static _stringlist *extra_install = NULL;
+static char *config_auth_datadir = NULL;
 
 /* internal variables */
 static const char *progname;
@@ -971,6 +972,150 @@ initialize_environment(void)
    load_resultmap();
 }
 
+#ifdef ENABLE_SSPI
+/*
+ * Get account and domain/realm names for the current user.  This is based on
+ * pg_SSPI_recvauth().  The returned strings use static storage.
+ */
+static void
+current_windows_user(const char **acct, const char **dom)
+{
+   static char accountname[MAXPGPATH];
+   static char domainname[MAXPGPATH];
+   HANDLE      token;
+   TOKEN_USER *tokenuser;
+   DWORD       retlen;
+   DWORD       accountnamesize = sizeof(accountname);
+   DWORD       domainnamesize = sizeof(domainname);
+   SID_NAME_USE accountnameuse;
+
+   if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token))
+   {
+       fprintf(stderr,
+               _("%s: could not open process token: error code %lu\n"),
+               progname, GetLastError());
+       exit(2);
+   }
+
+   if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+   {
+       fprintf(stderr,
+               _("%s: could not get token user size: error code %lu\n"),
+               progname, GetLastError());
+       exit(2);
+   }
+   tokenuser = malloc(retlen);
+   if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+   {
+       fprintf(stderr,
+               _("%s: could not get token user: error code %lu\n"),
+               progname, GetLastError());
+       exit(2);
+   }
+
+   if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
+                         domainname, &domainnamesize, &accountnameuse))
+   {
+       fprintf(stderr,
+               _("%s: could not look up account SID: error code %lu\n"),
+               progname, GetLastError());
+       exit(2);
+   }
+
+   free(tokenuser);
+
+   *acct = accountname;
+   *dom = domainname;
+}
+
+/*
+ * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication.  Permit
+ * the current OS user to authenticate as the bootstrap superuser and as any
+ * user named in a --create-role option.
+ */
+static void
+config_sspi_auth(const char *pgdata)
+{
+   const char *accountname,
+              *domainname;
+   char        username[128];
+   DWORD       sz = sizeof(username) - 1;
+   char        fname[MAXPGPATH];
+   int         res;
+   FILE       *hba,
+              *ident;
+   _stringlist *sl;
+
+   /*
+    * "username", the initdb-chosen bootstrap superuser name, may always
+    * match "accountname", the value SSPI authentication discovers.  The
+    * underlying system functions do not clearly guarantee that.
+    */
+   current_windows_user(&accountname, &domainname);
+   if (!GetUserName(username, &sz))
+   {
+       fprintf(stderr, _("%s: could not get current user name: %s\n"),
+               progname, strerror(errno));
+       exit(2);
+   }
+
+   /* Check a Write outcome and report any error. */
+#define CW(cond)   \
+   do { \
+       if (!(cond)) \
+       { \
+           fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \
+                   progname, fname, strerror(errno)); \
+           exit(2); \
+       } \
+   } while (0)
+
+   res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata);
+   if (res < 0 || res >= sizeof(fname) - 1)
+   {
+       /*
+        * Truncating this name is a fatal error, because we must not fail to
+        * overwrite an original trust-authentication pg_hba.conf.
+        */
+       fprintf(stderr, _("%s: directory name too long\n"), progname);
+       exit(2);
+   }
+   hba = fopen(fname, "w");
+   if (hba == NULL)
+   {
+       fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+               progname, fname, strerror(errno));
+       exit(2);
+   }
+   CW(fputs("# Configuration written by config_sspi_auth()\n", hba) >= 0);
+   CW(fputs("host all all 127.0.0.1/32  sspi include_realm=1 map=regress\n",
+            hba) >= 0);
+   CW(fclose(hba) == 0);
+
+   snprintf(fname, sizeof(fname), "%s/pg_ident.conf", pgdata);
+   ident = fopen(fname, "w");
+   if (ident == NULL)
+   {
+       fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+               progname, fname, strerror(errno));
+       exit(2);
+   }
+   CW(fputs("# Configuration written by config_sspi_auth()\n", ident) >= 0);
+
+   /*
+    * Double-quote for the benefit of account names containing whitespace or
+    * '#'.  Windows forbids the double-quote character itself, so don't
+    * bother escaping embedded double-quote characters.
+    */
+   CW(fprintf(ident, "regress  \"%s@%s\"  \"%s\"\n",
+              accountname, domainname, username) >= 0);
+   for (sl = extraroles; sl; sl = sl->next)
+       CW(fprintf(ident, "regress  \"%s@%s\"  \"%s\"\n",
+                  accountname, domainname, sl->str) >= 0);
+   CW(fclose(ident) == 0);
+}
+#endif
+
 /*
  * Issue a command via psql, connecting to the specified database
  *
@@ -1970,6 +2115,7 @@ help(void)
    printf(_("Usage:\n  %s [OPTION]... [EXTRA-TEST]...\n"), progname);
    printf(_("\n"));
    printf(_("Options:\n"));
+   printf(_("  --config-auth=DATADIR     update authentication settings for DATADIR\n"));
    printf(_("  --create-role=ROLE        create the specified role before testing\n"));
    printf(_("  --dbname=DB               use database DB (default \"regression\")\n"));
    printf(_("  --debug                   turn on debug mode in programs that are run\n"));
@@ -2036,6 +2182,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
        {"launcher", required_argument, NULL, 21},
        {"load-extension", required_argument, NULL, 22},
        {"extra-install", required_argument, NULL, 23},
+       {"config-auth", required_argument, NULL, 24},
        {NULL, 0, NULL, 0}
    };
 
@@ -2150,6 +2297,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
            case 23:
                add_stringlist_item(&extra_install, optarg);
                break;
+           case 24:
+               config_auth_datadir = pstrdup(optarg);
+               break;
            default:
                /* getopt_long already emitted a complaint */
                fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -2167,6 +2317,14 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
        optind++;
    }
 
+   if (config_auth_datadir)
+   {
+#ifdef ENABLE_SSPI
+       config_sspi_auth(config_auth_datadir);
+#endif
+       exit(0);
+   }
+
    if (temp_install && !port_specified_by_user)
 
        /*
@@ -2307,6 +2465,18 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 
        fclose(pg_conf);
 
+#ifdef ENABLE_SSPI
+
+       /*
+        * Since we successfully used the same buffer for the much-longer
+        * "initdb" command, this can't truncate.
+        */
+       snprintf(buf, sizeof(buf), "%s/data", temp_install);
+       config_sspi_auth(buf);
+#elif !defined(HAVE_UNIX_SOCKETS)
+#error Platform has no means to secure the test installation.
+#endif
+
        /*
         * Check if there is a postmaster running already.
         */
index 5f5c9bc228b7554c58f44f1e8cabd7c664a1b6ce..30da0ba0e653621394b9f70b93cb71576d95b4dc 100644 (file)
@@ -242,6 +242,15 @@ sub contribcheck
    exit $mstat if $mstat;
 }
 
+# Run "initdb", then reconfigure authentication.
+sub standard_initdb
+{
+   return (
+       system('initdb', '-N') == 0 and system(
+           "$topdir/$Config/pg_regress/pg_regress", '--config-auth',
+           $ENV{PGDATA}) == 0);
+}
+
 sub upgradecheck
 {
    my $status;
@@ -253,6 +262,7 @@ sub upgradecheck
    # i.e. only this version to this version check. That's
    # what pg_upgrade's "make check" does.
 
+   $ENV{PGHOST} = 'localhost';
    $ENV{PGPORT} ||= 50432;
    my $tmp_root = "$topdir/contrib/pg_upgrade/tmp_check";
    (mkdir $tmp_root || die $!) unless -d $tmp_root;
@@ -270,7 +280,7 @@ sub upgradecheck
    my $logdir = "$topdir/contrib/pg_upgrade/log";
    (mkdir $logdir || die $!) unless -d $logdir;
    print "\nRunning initdb on old cluster\n\n";
-   system("initdb") == 0 or exit 1;
+   standard_initdb() or exit 1;
    print "\nStarting old cluster\n\n";
    system("pg_ctl start -l $logdir/postmaster1.log -w") == 0 or exit 1;
    print "\nSetting up data for upgrading\n\n";
@@ -284,7 +294,7 @@ sub upgradecheck
    system("pg_ctl -m fast stop") == 0 or exit 1;
    $ENV{PGDATA} = "$data";
    print "\nSetting up new cluster\n\n";
-   system("initdb") == 0 or exit 1;
+   standard_initdb() or exit 1;
    print "\nRunning pg_upgrade\n\n";
    system("pg_upgrade -d $data.old -D $data -b $bindir -B $bindir") == 0
      or exit 1;