]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Empty search_path in Autovacuum and non-psql/pgbench clients.
authorNoah Misch <noah@leadboat.com>
Mon, 26 Feb 2018 15:39:44 +0000 (07:39 -0800)
committerNoah Misch <noah@leadboat.com>
Mon, 26 Feb 2018 15:39:48 +0000 (07:39 -0800)
This makes the client programs behave as documented regardless of the
connect-time search_path and regardless of user-created objects.  Today,
a malicious user with CREATE permission on a search_path schema can take
control of certain of these clients' queries and invoke arbitrary SQL
functions under the client identity, often a superuser.  This is
exploitable in the default configuration, where all users have CREATE
privilege on schema "public".

This changes behavior of user-defined code stored in the database, like
pg_index.indexprs and pg_extension_config_dump().  If they reach code
bearing unqualified names, "does not exist" or "no schema has been
selected to create in" errors might appear.  Users may fix such errors
by schema-qualifying affected names.  After upgrading, consider watching
server logs for these errors.

The --table arguments of src/bin/scripts clients have been lax; for
example, "vacuumdb -Zt pg_am\;CHECKPOINT" performed a checkpoint.  That
now fails, but for now, "vacuumdb -Zt 'pg_am(amname);CHECKPOINT'" still
performs a checkpoint.

Back-patch to 9.3 (all supported versions).

Reviewed by Tom Lane, though this fix strategy was not his first choice.
Reported by Arseniy Sharoglazov.

Security: CVE-2018-1058

23 files changed:
contrib/oid2name/oid2name.c
contrib/pg_upgrade/server.c
contrib/vacuumlo/vacuumlo.c
src/backend/postmaster/autovacuum.c
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/pg_backup_db.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/scripts/Makefile
src/bin/scripts/clusterdb.c
src/bin/scripts/common.c
src/bin/scripts/common.h
src/bin/scripts/createdb.c
src/bin/scripts/createlang.c
src/bin/scripts/createuser.c
src/bin/scripts/dropdb.c
src/bin/scripts/droplang.c
src/bin/scripts/dropuser.c
src/bin/scripts/reindexdb.c
src/bin/scripts/vacuumdb.c
src/include/Makefile
src/include/fe_utils/connect.h [new file with mode: 0644]
src/tools/findoidjoins/findoidjoins.c

index f3e393b5039f380c2afe0b7f478cf7d3caf6f48d..1f0c040d12d0f219186bb9eddc289fa2699ed4bd 100644 (file)
@@ -16,6 +16,7 @@
 
 extern char *optarg;
 
+#include "fe_utils/connect.h"
 #include "libpq-fe.h"
 
 /* an extensible array to keep track of elements to show */
@@ -269,6 +270,7 @@ sql_conn(struct options * my_opts)
        PGconn     *conn;
        char       *password = NULL;
        bool            new_pass;
+       PGresult   *res;
 
        /*
         * Start the connection.  Loop until we have a password if requested by
@@ -328,6 +330,17 @@ sql_conn(struct options * my_opts)
                exit(1);
        }
 
+       res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+       if (PQresultStatus(res) != PGRES_TUPLES_OK)
+       {
+               fprintf(stderr, "oid2name: could not clear search_path: %s\n",
+                               PQerrorMessage(conn));
+               PQclear(res);
+               PQfinish(conn);
+               exit(-1);
+       }
+       PQclear(res);
+
        /* return the conn if good */
        return conn;
 }
index 19fbd324c66add60fc5fc2703f6234bcbf673249..b9e11efdf62004e6f39a660005ea58a5b127e00c 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "postgres_fe.h"
 
+#include "fe_utils/connect.h"
 #include "pg_upgrade.h"
 
 
@@ -39,6 +40,8 @@ connectToServer(ClusterInfo *cluster, const char *db_name)
                exit(1);
        }
 
+       PQclear(executeQueryOrDie(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
+
        return conn;
 }
 
index 70f7ea70ba08dc11eb1d037aabc2d18f75e49a32..e92ec89f6ba45baf078910151e46c47cff196238 100644 (file)
@@ -21,6 +21,7 @@
 #include <termios.h>
 #endif
 
+#include "fe_utils/connect.h"
 #include "libpq-fe.h"
 
 #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
@@ -138,11 +139,8 @@ vacuumlo(const char *database, const struct _param * param)
                        fprintf(stdout, "Test run: no large objects will be removed!\n");
        }
 
-       /*
-        * Don't get fooled by any non-system catalogs
-        */
-       res = PQexec(conn, "SET search_path = pg_catalog");
-       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+       if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
                fprintf(stderr, "Failed to set search_path:\n");
                fprintf(stderr, "%s", PQerrorMessage(conn));
index 02041abe47c11dc2b30bbead6696957bc6e62a9e..c118a37ab3351a1272e2cf1d6094ef9dc9e03bc0 100644 (file)
@@ -557,6 +557,12 @@ AutoVacLauncherMain(int argc, char *argv[])
        /* must unblock signals before calling rebuild_database_list */
        PG_SETMASK(&UnBlockSig);
 
+       /*
+        * Set always-secure search path.  Launcher doesn't connect to a database,
+        * so this has no effect.
+        */
+       SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
        /*
         * Force zero_damaged_pages OFF in the autovac process, even if it is set
         * in postgresql.conf.  We don't really want such a dangerous option being
@@ -1599,6 +1605,14 @@ AutoVacWorkerMain(int argc, char *argv[])
 
        PG_SETMASK(&UnBlockSig);
 
+       /*
+        * Set always-secure search path, so malicious users can't redirect user
+        * code (e.g. pg_index.indexprs).  (That code runs in a
+        * SECURITY_RESTRICTED_OPERATION sandbox, so malicious users could not
+        * take control of the entire autovacuum worker in any case.)
+        */
+       SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
        /*
         * Force zero_damaged_pages OFF in the autovac process, even if it is set
         * in postgresql.conf.  We don't really want such a dangerous option being
index 648aae414c19c8130b8ca9681f44136f2c502b9d..1cfb62f5dbd103a8db5d9c9378ad1c960fdd0ef1 100644 (file)
@@ -1376,8 +1376,9 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
        }
 
        /*
-        * Now decide what we need to emit.  Note there will be a leading "^(" in
-        * the patterns in any case.
+        * Now decide what we need to emit.  We may run under a hostile
+        * search_path, so qualify EVERY name.  Note there will be a leading "^("
+        * in the patterns in any case.
         */
        if (namebuf.len > 2)
        {
@@ -1390,15 +1391,18 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
                        WHEREAND();
                        if (altnamevar)
                        {
-                               appendPQExpBuffer(buf, "(%s ~ ", namevar);
+                               appendPQExpBuffer(buf,
+                                                                 "(%s OPERATOR(pg_catalog.~) ", namevar);
                                appendStringLiteralConn(buf, namebuf.data, conn);
-                               appendPQExpBuffer(buf, "\n        OR %s ~ ", altnamevar);
+                               appendPQExpBuffer(buf,
+                                                                 "\n        OR %s OPERATOR(pg_catalog.~) ",
+                                                                 altnamevar);
                                appendStringLiteralConn(buf, namebuf.data, conn);
                                appendPQExpBufferStr(buf, ")\n");
                        }
                        else
                        {
-                               appendPQExpBuffer(buf, "%s ~ ", namevar);
+                               appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
                                appendStringLiteralConn(buf, namebuf.data, conn);
                                appendPQExpBufferChar(buf, '\n');
                        }
@@ -1414,7 +1418,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
                if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
                {
                        WHEREAND();
-                       appendPQExpBuffer(buf, "%s ~ ", schemavar);
+                       appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
                        appendStringLiteralConn(buf, schemabuf.data, conn);
                        appendPQExpBufferChar(buf, '\n');
                }
index fea10bce298d952eb597a5650f70beb7e572759d..fa69e64bdbd1f3e7480b181dd2d64f9034b28b6e 100644 (file)
@@ -10,6 +10,7 @@
  *-------------------------------------------------------------------------
  */
 
+#include "fe_utils/connect.h"
 #include "pg_backup_db.h"
 #include "pg_backup_utils.h"
 #include "dumputils.h"
@@ -96,6 +97,11 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
        PQfinish(AH->connection);
        AH->connection = newConn;
 
+       /* Start strict; later phases may override this. */
+       if (PQserverVersion(AH->connection) >= 70300)
+               PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
+                                                                                       ALWAYS_SECURE_SEARCH_PATH_SQL));
+
        return 1;
 }
 
@@ -304,6 +310,11 @@ ConnectDatabase(Archive *AHX,
                                          PQdb(AH->connection) ? PQdb(AH->connection) : "",
                                          PQerrorMessage(AH->connection));
 
+       /* Start strict; later phases may override this. */
+       if (PQserverVersion(AH->connection) >= 70300)
+               PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
+                                                                                       ALWAYS_SECURE_SEARCH_PATH_SQL));
+
        /*
         * We want to remember connection's actual password, whether or not we got
         * it by prompting.  So we don't just store the password variable.
index c0a9d8b7c70fef7801da392415c1088328754b0b..50c62ce70593ee0dd7b37e516a7ace463f8c404b 100644 (file)
@@ -61,6 +61,7 @@
 #include "pg_backup_db.h"
 #include "pg_backup_utils.h"
 #include "dumputils.h"
+#include "fe_utils/connect.h"
 #include "parallel.h"
 
 extern char *optarg;
@@ -969,6 +970,9 @@ setup_connection(Archive *AH, const char *dumpencoding, char *use_role)
        PGconn     *conn = GetConnection(AH);
        const char *std_strings;
 
+       if (AH->remoteVersion >= 70300)
+               PQclear(ExecuteSqlQueryForSingleRow(AH, ALWAYS_SECURE_SEARCH_PATH_SQL));
+
        /*
         * Set the client encoding if requested.
         */
@@ -1235,13 +1239,20 @@ expand_table_name_patterns(Archive *fout,
 
        for (cell = patterns->head; cell; cell = cell->next)
        {
+               /*
+                * Query must remain ABSOLUTELY devoid of unqualified names.  This
+                * would be unnecessary given a pg_table_is_visible() variant taking a
+                * search_path argument.
+                */
                if (cell != patterns->head)
                        appendPQExpBuffer(query, "UNION ALL\n");
                appendPQExpBuffer(query,
                                                  "SELECT c.oid"
                                                  "\nFROM pg_catalog.pg_class c"
-               "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-                                        "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+                                                 "\n     LEFT JOIN pg_catalog.pg_namespace n"
+                                                 "\n     ON n.oid OPERATOR(pg_catalog.=) c.relnamespace"
+                                                 "\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY"
+                                                 "\n    (array['%c', '%c', '%c', '%c', '%c'])\n",
                                                  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
                                                  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
                processSQLNamePattern(GetConnection(fout), query, cell->val, true,
@@ -1249,7 +1260,9 @@ expand_table_name_patterns(Archive *fout,
                                                          "pg_catalog.pg_table_is_visible(c.oid)");
        }
 
+       ExecuteSqlStatement(fout, "RESET search_path");
        res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+       PQclear(ExecuteSqlQueryForSingleRow(fout, ALWAYS_SECURE_SEARCH_PATH_SQL));
 
        for (i = 0; i < PQntuples(res); i++)
        {
index a93a7edf5223f98b8bb3e6da5ee4753e10d97051..c2de3a8db905748b9c91de6d64ee3cac953a9ce6 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "dumputils.h"
 #include "pg_backup.h"
+#include "fe_utils/connect.h"
 
 /* version string we expect back from pg_dump */
 #define PGDUMP_VERSIONSTR "pg_dump (PostgreSQL) " PG_VERSION "\n"
@@ -1958,12 +1959,8 @@ connectDatabase(const char *dbname, const char *connection_string,
                exit_nicely(1);
        }
 
-       /*
-        * On 7.3 and later, make sure we are not fooled by non-system schemas in
-        * the search path.
-        */
        if (server_version >= 70300)
-               executeCommand(conn, "SET search_path = pg_catalog");
+               PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
 
        return conn;
 }
index b9169aa3ab7477ccab21f72fcb81a42346c04753..996db500e949083f73f5fde67b0c8be6b38b8ee1 100644 (file)
@@ -25,16 +25,17 @@ all: $(PROGRAMS)
 %: %.o $(WIN32RES)
        $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
-createdb: createdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-createlang: createlang.o common.o print.o mbprint.o | submake-libpq submake-libpgport
-createuser: createuser.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-dropdb: dropdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-droplang: droplang.o common.o print.o mbprint.o | submake-libpq submake-libpgport
-dropuser: dropuser.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-clusterdb: clusterdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-vacuumdb: vacuumdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-reindexdb: reindexdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-pg_isready: pg_isready.o common.o | submake-libpq submake-libpgport
+SCRIPTS_COMMON = common.o dumputils.o kwlookup.o keywords.o
+createdb: createdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+createlang: createlang.o $(SCRIPTS_COMMON) print.o mbprint.o | submake-libpq submake-libpgport
+createuser: createuser.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+dropdb: dropdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+droplang: droplang.o $(SCRIPTS_COMMON) print.o mbprint.o | submake-libpq submake-libpgport
+dropuser: dropuser.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+clusterdb: clusterdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+vacuumdb: vacuumdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+reindexdb: reindexdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+pg_isready: pg_isready.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
 
 dumputils.c keywords.c: % : $(top_srcdir)/src/bin/pg_dump/%
        rm -f $@ && $(LN_S) $< .
@@ -66,5 +67,5 @@ uninstall:
 
 clean distclean maintainer-clean:
        rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
-       rm -f common.o dumputils.o kwlookup.o keywords.o print.o mbprint.o $(WIN32RES)
+       rm -f $(SCRIPTS_COMMON) print.o mbprint.o $(WIN32RES)
        rm -f dumputils.c print.c mbprint.c kwlookup.c keywords.c
index 1bdfe2967c2d7744ea4428824e4a636fe8ec7d01..4f0374baa2e5d0572e0bd86c2892292331bf0c90 100644 (file)
@@ -194,17 +194,21 @@ cluster_one_database(const char *dbname, bool verbose, const char *table,
 
        PGconn     *conn;
 
+       conn = connectDatabase(dbname, host, port, username, prompt_password,
+                                                  progname, echo, false);
+
        initPQExpBuffer(&sql);
 
        appendPQExpBuffer(&sql, "CLUSTER");
        if (verbose)
                appendPQExpBuffer(&sql, " VERBOSE");
        if (table)
-               appendPQExpBuffer(&sql, " %s", table);
+       {
+               appendPQExpBufferChar(&sql, ' ');
+               appendQualifiedRelation(&sql, table, conn, progname, echo);
+       }
        appendPQExpBuffer(&sql, ";\n");
 
-       conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
        if (!executeMaintenanceCommand(conn, sql.data, echo))
        {
                if (table)
@@ -233,7 +237,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db,
        int                     i;
 
        conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
-                                                                         prompt_password, progname);
+                                                                         prompt_password, progname, echo);
        result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
        PQfinish(conn);
 
index 3954ca586483ef592c1a775635fc6b122abdc172..1888c48a7e43a3139f06f4a060c2c4f1702d498e 100644 (file)
@@ -19,6 +19,8 @@
 #include <unistd.h>
 
 #include "common.h"
+#include "dumputils.h"
+#include "fe_utils/connect.h"
 
 static void SetCancelConn(PGconn *conn);
 static void ResetCancelConn(void);
@@ -90,9 +92,10 @@ handle_help_version_opts(int argc, char *argv[],
  * interactive password prompt is automatically issued if required.
  */
 PGconn *
-connectDatabase(const char *dbname, const char *pghost, const char *pgport,
-                               const char *pguser, enum trivalue prompt_password,
-                               const char *progname, bool fail_ok)
+connectDatabase(const char *dbname, const char *pghost,
+                               const char *pgport, const char *pguser,
+                               enum trivalue prompt_password, const char *progname,
+                               bool echo, bool fail_ok)
 {
        PGconn     *conn;
        char       *password = NULL;
@@ -166,6 +169,10 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
                exit(1);
        }
 
+       if (PQserverVersion(conn) >= 70300)
+               PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL,
+                                                        progname, echo));
+
        return conn;
 }
 
@@ -173,24 +180,24 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
  * Try to connect to the appropriate maintenance database.
  */
 PGconn *
-connectMaintenanceDatabase(const char *maintenance_db, const char *pghost,
-                                                  const char *pgport, const char *pguser,
-                                                  enum trivalue prompt_password,
-                                                  const char *progname)
+connectMaintenanceDatabase(const char *maintenance_db,
+                                                  const char *pghost, const char *pgport,
+                                                  const char *pguser, enum trivalue prompt_password,
+                                                  const char *progname, bool echo)
 {
        PGconn     *conn;
 
        /* If a maintenance database name was specified, just connect to it. */
        if (maintenance_db)
                return connectDatabase(maintenance_db, pghost, pgport, pguser,
-                                                          prompt_password, progname, false);
+                                                          prompt_password, progname, echo, false);
 
        /* Otherwise, try postgres first and then template1. */
        conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password,
-                                                  progname, true);
+                                                  progname, echo, true);
        if (!conn)
                conn = connectDatabase("template1", pghost, pgport, pguser,
-                                                          prompt_password, progname, false);
+                                                          prompt_password, progname, echo, false);
 
        return conn;
 }
@@ -276,6 +283,116 @@ executeMaintenanceCommand(PGconn *conn, const char *query, bool echo)
        return r;
 }
 
+
+/*
+ * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions.  When you
+ * finish using them, pg_free(*table).  *columns is a pointer into "spec",
+ * possibly to its NUL terminator.
+ */
+static void
+split_table_columns_spec(const char *spec, int encoding,
+                                                char **table, const char **columns)
+{
+       bool            inquotes = false;
+       const char *cp = spec;
+
+       /*
+        * Find the first '(' not identifier-quoted.  Based on
+        * dequote_downcase_identifier().
+        */
+       while (*cp && (*cp != '(' || inquotes))
+       {
+               if (*cp == '"')
+               {
+                       if (inquotes && cp[1] == '"')
+                               cp++;                   /* pair does not affect quoting */
+                       else
+                               inquotes = !inquotes;
+                       cp++;
+               }
+               else
+                       cp += PQmblen(cp, encoding);
+       }
+       *table = pg_strdup(spec);
+       (*table)[cp - spec] = '\0'; /* no strndup */
+       *columns = cp;
+}
+
+/*
+ * Break apart TABLE[(COLUMNS)] of "spec".  With the reset_val of search_path
+ * in effect, have regclassin() interpret the TABLE portion.  Append to "buf"
+ * the qualified name of TABLE, followed by any (COLUMNS).  Exit on failure.
+ * We use this to interpret --table=foo under the search path psql would get,
+ * in advance of "ANALYZE public.foo" under the always-secure search path.
+ */
+void
+appendQualifiedRelation(PQExpBuffer buf, const char *spec,
+                                               PGconn *conn, const char *progname, bool echo)
+{
+       char       *table;
+       const char *columns;
+       PQExpBufferData sql;
+       PGresult   *res;
+       int                     ntups;
+
+       /* Before 7.3, the concept of qualifying a name did not exist. */
+       if (PQserverVersion(conn) < 70300)
+       {
+               appendPQExpBufferStr(&sql, spec);
+               return;
+       }
+
+       split_table_columns_spec(spec, PQclientEncoding(conn), &table, &columns);
+
+       /*
+        * Query must remain ABSOLUTELY devoid of unqualified names.  This would
+        * be unnecessary given a regclassin() variant taking a search_path
+        * argument.
+        */
+       initPQExpBuffer(&sql);
+       appendPQExpBufferStr(&sql,
+                                                "SELECT c.relname, ns.nspname\n"
+                                                " FROM pg_catalog.pg_class c,"
+                                                " pg_catalog.pg_namespace ns\n"
+                                                " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
+                                                "  AND c.oid OPERATOR(pg_catalog.=) ");
+       appendStringLiteralConn(&sql, table, conn);
+       appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
+
+       executeCommand(conn, "RESET search_path", progname, echo);
+
+       /*
+        * One row is a typical result, as is a nonexistent relation ERROR.
+        * regclassin() unconditionally accepts all-digits input as an OID; if no
+        * relation has that OID; this query returns no rows.  Catalog corruption
+        * might elicit other row counts.
+        */
+       res = executeQuery(conn, sql.data, progname, echo);
+       ntups = PQntuples(res);
+       if (ntups != 1)
+       {
+               fprintf(stderr,
+                               ngettext("%s: query returned %d row instead of one: %s\n",
+                                                "%s: query returned %d rows instead of one: %s\n",
+                                                ntups),
+                               progname, ntups, sql.data);
+               PQfinish(conn);
+               exit(1);
+       }
+       appendPQExpBufferStr(buf,
+                                                fmtQualifiedId(PQserverVersion(conn),
+                                                                               PQgetvalue(res, 0, 1),
+                                                                               PQgetvalue(res, 0, 0)));
+       appendPQExpBufferStr(buf, columns);
+       PQclear(res);
+       termPQExpBuffer(&sql);
+       pg_free(table);
+
+       PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL,
+                                                progname, echo));
+}
+
+
 /*
  * Check yes/no answer in a localized way.  1=yes, 0=no, -1=neither.
  */
index 6cf490f7484588269aea2fca6be614c6de1376ac..913eec2f08d8b1d229fe12942a15450a29561553 100644 (file)
@@ -31,11 +31,12 @@ extern void handle_help_version_opts(int argc, char *argv[],
 extern PGconn *connectDatabase(const char *dbname, const char *pghost,
                                const char *pgport, const char *pguser,
                                enum trivalue prompt_password, const char *progname,
-                               bool fail_ok);
+                               bool echo, bool fail_ok);
 
 extern PGconn *connectMaintenanceDatabase(const char *maintenance_db,
-                                 const char *pghost, const char *pgport, const char *pguser,
-                                               enum trivalue prompt_password, const char *progname);
+                                                  const char *pghost, const char *pgport,
+                                                  const char *pguser, enum trivalue prompt_password,
+                                                  const char *progname, bool echo);
 
 extern PGresult *executeQuery(PGconn *conn, const char *query,
                         const char *progname, bool echo);
@@ -46,6 +47,9 @@ extern void executeCommand(PGconn *conn, const char *query,
 extern bool executeMaintenanceCommand(PGconn *conn, const char *query,
                                                  bool echo);
 
+extern void appendQualifiedRelation(PQExpBuffer buf, const char *name,
+                                               PGconn *conn, const char *progname, bool echo);
+
 extern bool yesno_prompt(const char *question);
 
 extern void setup_cancel_handler(void);
index 5b28f18a81a5acd67840a99596118d73e1fdcce6..4b075d06d20910b1b1f93ddc1528c387ec1372cc 100644 (file)
@@ -202,7 +202,7 @@ main(int argc, char *argv[])
                maintenance_db = "template1";
 
        conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
-                                                                         prompt_password, progname);
+                                                                         prompt_password, progname, echo);
 
        if (echo)
                printf("%s", sql.data);
index 587ef0573fd61120b30869c3175e7ab65b23a3a9..7c146c3007aa058824c7fa3f281e1574e9f6e26c 100644 (file)
@@ -141,7 +141,7 @@ main(int argc, char *argv[])
                static const bool translate_columns[] = {false, true};
 
                conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                          progname, false);
+                                                          progname, echo, false);
 
                printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
                                "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@@ -179,7 +179,7 @@ main(int argc, char *argv[])
                        *p += ('a' - 'A');
 
        conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        /*
         * Make sure the language isn't already installed
index d1542d945ac0c84f4feb78e6d0e7e90788e3f556..753f8cfaae56b7925b05cabfceeb9dc83523fc20 100644 (file)
@@ -246,7 +246,7 @@ main(int argc, char *argv[])
                login = TRI_YES;
 
        conn = connectDatabase("postgres", host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        initPQExpBuffer(&sql);
 
index 2493943491e03254b6db2775545138476489e432..9e2eae119f63c80e6c37daf4e0a65b66abb289fb 100644 (file)
@@ -129,7 +129,8 @@ main(int argc, char *argv[])
                maintenance_db = "template1";
 
        conn = connectMaintenanceDatabase(maintenance_db,
-                                                       host, port, username, prompt_password, progname);
+                                                                         host, port, username, prompt_password,
+                                                                         progname, echo);
 
        if (echo)
                printf("%s", sql.data);
index 46d4ae1d5dda71cfc59f38e08e49e1762d7bce23..eb152ebe26ddfbbd01cd74e4de49d8dd60bc28f9 100644 (file)
@@ -140,7 +140,7 @@ main(int argc, char *argv[])
                static const bool translate_columns[] = {false, true};
 
                conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                          progname, false);
+                                                          progname, echo, false);
 
                printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
                                "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@@ -180,13 +180,7 @@ main(int argc, char *argv[])
                        *p += ('a' - 'A');
 
        conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
-
-       /*
-        * Force schema search path to be just pg_catalog, so that we don't have
-        * to be paranoid about search paths below.
-        */
-       executeCommand(conn, "SET search_path = pg_catalog;", progname, echo);
+                                                  progname, echo, false);
 
        /*
         * Make sure the language is installed
index db062e29ad2e289a73e9e377d23d5e68f0687887..32b4659b04a566a7fc68505d608627e04f04cb2e 100644 (file)
@@ -129,7 +129,7 @@ main(int argc, char *argv[])
                                          (if_exists ? "IF EXISTS " : ""), fmtId(dropuser));
 
        conn = connectDatabase("postgres", host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        if (echo)
                printf("%s", sql.data);
index 3c2a8ff0b8d2f7deac35ff66a9f9953426b82d0c..bb89614d52645efaacdfedcf75948d25678d40c3 100644 (file)
@@ -245,17 +245,18 @@ reindex_one_database(const char *name, const char *dbname, const char *type,
        PGconn     *conn;
 
        conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        initPQExpBuffer(&sql);
 
-       appendPQExpBuffer(&sql, "REINDEX");
-       if (strcmp(type, "TABLE") == 0)
-               appendPQExpBuffer(&sql, " TABLE %s", name);
-       else if (strcmp(type, "INDEX") == 0)
-               appendPQExpBuffer(&sql, " INDEX %s", name);
+       appendPQExpBufferStr(&sql, "REINDEX ");
+       appendPQExpBufferStr(&sql, type);
+       appendPQExpBufferChar(&sql, ' ');
+       if (strcmp(type, "TABLE") == 0 ||
+               strcmp(type, "INDEX") == 0)
+               appendQualifiedRelation(&sql, name, conn, progname, echo);
        else if (strcmp(type, "DATABASE") == 0)
-               appendPQExpBuffer(&sql, " DATABASE %s", fmtId(PQdb(conn)));
+               appendPQExpBufferStr(&sql, fmtId(PQdb(conn)));
        appendPQExpBuffer(&sql, ";\n");
 
        if (!executeMaintenanceCommand(conn, sql.data, echo))
@@ -289,7 +290,7 @@ reindex_all_databases(const char *maintenance_db,
        int                     i;
 
        conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
-                                                                         prompt_password, progname);
+                                                                         prompt_password, progname, echo);
        result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
        PQfinish(conn);
 
@@ -326,7 +327,7 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port,
        PQExpBufferData sql;
 
        conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        initPQExpBuffer(&sql);
 
index 3614e71b6a6e76f7d7a16184d3f70c446bca4eb2..b10d4ac9d0265c68275c07279973096f826b5f43 100644 (file)
@@ -244,7 +244,7 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
        initPQExpBuffer(&sql);
 
        conn = connectDatabase(dbname, host, port, username, prompt_password,
-                                                  progname, false);
+                                                  progname, echo, false);
 
        if (analyze_only)
        {
@@ -297,7 +297,10 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
                }
        }
        if (table)
-               appendPQExpBuffer(&sql, " %s", table);
+       {
+               appendPQExpBufferChar(&sql, ' ');
+               appendQualifiedRelation(&sql, table, conn, progname, echo);
+       }
        appendPQExpBuffer(&sql, ";\n");
 
        if (!executeMaintenanceCommand(conn, sql.data, echo))
@@ -329,7 +332,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl
        int                     i;
 
        conn = connectMaintenanceDatabase(maintenance_db, host, port,
-                                                                         username, prompt_password, progname);
+                                                                         username, prompt_password, progname, echo);
        result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
        PQfinish(conn);
 
index c553e74890975ca25b5332fc07e44f5f9a758d78..e6ffe9a442d39e278485b49ae19cebe4bb418452 100644 (file)
@@ -17,7 +17,8 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
 
 
 # Subdirectories containing headers for server-side dev
-SUBDIRS = access bootstrap catalog commands common datatype executor foreign \
+SUBDIRS = access bootstrap catalog commands common datatype \
+       executor fe_utils foreign \
        lib libpq mb nodes optimizer parser postmaster regex replication \
        rewrite storage tcop snowball snowball/libstemmer tsearch \
        tsearch/dicts utils port port/win32 port/win32_msvc \
diff --git a/src/include/fe_utils/connect.h b/src/include/fe_utils/connect.h
new file mode 100644 (file)
index 0000000..fa293d2
--- /dev/null
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * Interfaces in support of FE/BE connections.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/fe_utils/connect.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CONNECT_H
+#define CONNECT_H
+
+/*
+ * This SQL statement installs an always-secure search path, so malicious
+ * users can't take control.  CREATE of an unqualified name will fail, because
+ * this selects no creation schema.  This does not demote pg_temp, so it is
+ * suitable where we control the entire FE/BE connection but not suitable in
+ * SECURITY DEFINER functions.  This is portable to PostgreSQL 7.3, which
+ * introduced schemas.  When connected to an older version from code that
+ * might work with the old server, skip this.
+ */
+#define ALWAYS_SECURE_SEARCH_PATH_SQL \
+       "SELECT pg_catalog.set_config('search_path', '', false)"
+
+#endif                                                 /* CONNECT_H */
index b9818d9f316015fa17e0b000a52997abc0571446..79691bdd82f058dd145f3404d4c5a0cb97bd6efd 100644 (file)
@@ -7,6 +7,7 @@
  */
 #include "postgres_fe.h"
 
+#include "fe_utils/connect.h"
 #include "libpq-fe.h"
 #include "pqexpbuffer.h"
 
@@ -44,6 +45,14 @@ main(int argc, char **argv)
                exit(EXIT_FAILURE);
        }
 
+       res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+       if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+       {
+               fprintf(stderr, "sql error:  %s\n", PQerrorMessage(conn));
+               exit(EXIT_FAILURE);
+       }
+       PQclear(res);
+
        /* Get a list of relations that have OIDs */
 
        printfPQExpBuffer(&sql, "%s",