1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-17 17:02:08 +03:00

Empty search_path in Autovacuum and non-psql/pgbench clients.

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
This commit is contained in:
Noah Misch
2018-02-26 07:39:44 -08:00
parent 815172ba80
commit e170b8c8c6
26 changed files with 347 additions and 77 deletions

View File

@ -9,6 +9,7 @@
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
#include "fe_utils/connect.h"
#include "libpq-fe.h" #include "libpq-fe.h"
#include "pg_getopt.h" #include "pg_getopt.h"
@ -263,6 +264,7 @@ sql_conn(struct options * my_opts)
PGconn *conn; PGconn *conn;
char *password = NULL; char *password = NULL;
bool new_pass; bool new_pass;
PGresult *res;
/* /*
* Start the connection. Loop until we have a password if requested by * Start the connection. Loop until we have a password if requested by
@ -322,6 +324,17 @@ sql_conn(struct options * my_opts)
exit(1); 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 the conn if good */
return conn; return conn;
} }

View File

@ -21,6 +21,7 @@
#include <termios.h> #include <termios.h>
#endif #endif
#include "fe_utils/connect.h"
#include "libpq-fe.h" #include "libpq-fe.h"
#include "pg_getopt.h" #include "pg_getopt.h"
@ -135,11 +136,8 @@ vacuumlo(const char *database, const struct _param * param)
fprintf(stdout, "Test run: no large objects will be removed!\n"); fprintf(stdout, "Test run: no large objects will be removed!\n");
} }
/* res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
* Don't get fooled by any non-system catalogs if (PQresultStatus(res) != PGRES_TUPLES_OK)
*/
res = PQexec(conn, "SET search_path = pg_catalog");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{ {
fprintf(stderr, "Failed to set search_path:\n"); fprintf(stderr, "Failed to set search_path:\n");
fprintf(stderr, "%s", PQerrorMessage(conn)); fprintf(stderr, "%s", PQerrorMessage(conn));

View File

@ -528,6 +528,12 @@ AutoVacLauncherMain(int argc, char *argv[])
/* must unblock signals before calling rebuild_database_list */ /* must unblock signals before calling rebuild_database_list */
PG_SETMASK(&UnBlockSig); 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 * 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 * in postgresql.conf. We don't really want such a dangerous option being
@ -1537,6 +1543,14 @@ AutoVacWorkerMain(int argc, char *argv[])
PG_SETMASK(&UnBlockSig); 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 * 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 * in postgresql.conf. We don't really want such a dangerous option being

View File

@ -30,6 +30,7 @@
#include "pqexpbuffer.h" #include "pqexpbuffer.h"
#include "common/fe_memutils.h" #include "common/fe_memutils.h"
#include "datatype/timestamp.h" #include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#define ERRCODE_DUPLICATE_OBJECT "42710" #define ERRCODE_DUPLICATE_OBJECT "42710"
@ -208,6 +209,23 @@ GetConnection(void)
if (conn_opts) if (conn_opts)
PQconninfoFree(conn_opts); PQconninfoFree(conn_opts);
/* Set always-secure search path, so malicious users can't get control. */
if (dbname != NULL)
{
PGresult *res;
res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, _("%s: could not clear search_path: %s\n"),
progname, PQerrorMessage(tmpconn));
PQclear(res);
PQfinish(tmpconn);
exit(1);
}
PQclear(res);
}
/* /*
* Ensure we have the same value of integer timestamps as the server we * Ensure we have the same value of integer timestamps as the server we
* are connecting to. * are connecting to.

View File

@ -12,6 +12,7 @@
#include "postgres_fe.h" #include "postgres_fe.h"
#include "dumputils.h" #include "dumputils.h"
#include "fe_utils/connect.h"
#include "fe_utils/string_utils.h" #include "fe_utils/string_utils.h"
#include "parallel.h" #include "parallel.h"
#include "pg_backup_archiver.h" #include "pg_backup_archiver.h"
@ -114,6 +115,11 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
PQfinish(AH->connection); PQfinish(AH->connection);
AH->connection = newConn; 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; return 1;
} }
@ -322,6 +328,11 @@ ConnectDatabase(Archive *AHX,
PQdb(AH->connection) ? PQdb(AH->connection) : "", PQdb(AH->connection) ? PQdb(AH->connection) : "",
PQerrorMessage(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 * 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. * it by prompting. So we don't just store the password variable.

View File

@ -61,6 +61,7 @@
#include "pg_backup_db.h" #include "pg_backup_db.h"
#include "pg_backup_utils.h" #include "pg_backup_utils.h"
#include "pg_dump.h" #include "pg_dump.h"
#include "fe_utils/connect.h"
#include "fe_utils/string_utils.h" #include "fe_utils/string_utils.h"
@ -982,6 +983,9 @@ setup_connection(Archive *AH, const char *dumpencoding,
PGconn *conn = GetConnection(AH); PGconn *conn = GetConnection(AH);
const char *std_strings; const char *std_strings;
if (AH->remoteVersion >= 70300)
PQclear(ExecuteSqlQueryForSingleRow(AH, ALWAYS_SECURE_SEARCH_PATH_SQL));
/* /*
* Set the client encoding if requested. * Set the client encoding if requested.
*/ */
@ -1280,18 +1284,28 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next) 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.
*/
appendPQExpBuffer(query, appendPQExpBuffer(query,
"SELECT c.oid" "SELECT c.oid"
"\nFROM pg_catalog.pg_class c" "\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace" "\n LEFT JOIN pg_catalog.pg_namespace n"
"\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\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_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE); RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true, processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL, false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)"); "pg_catalog.pg_table_is_visible(c.oid)");
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
PQclear(ExecuteSqlQueryForSingleRow(fout,
ALWAYS_SECURE_SEARCH_PATH_SQL));
if (strict_names && PQntuples(res) == 0) if (strict_names && PQntuples(res) == 0)
exit_horribly(NULL, "no matching tables were found for pattern \"%s\"\n", cell->val); exit_horribly(NULL, "no matching tables were found for pattern \"%s\"\n", cell->val);

View File

@ -26,6 +26,7 @@
#include "dumputils.h" #include "dumputils.h"
#include "pg_backup.h" #include "pg_backup.h"
#include "fe_utils/connect.h"
#include "fe_utils/string_utils.h" #include "fe_utils/string_utils.h"
/* version string we expect back from pg_dump */ /* version string we expect back from pg_dump */
@ -2079,12 +2080,8 @@ connectDatabase(const char *dbname, const char *connection_string,
exit_nicely(1); 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) if (server_version >= 70300)
executeCommand(conn, "SET search_path = pg_catalog"); PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
return conn; return conn;
} }

View File

@ -29,6 +29,7 @@
#include "libpq-fe.h" #include "libpq-fe.h"
#include "catalog/catalog.h" #include "catalog/catalog.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "fe_utils/connect.h"
static PGconn *conn = NULL; static PGconn *conn = NULL;
@ -58,6 +59,12 @@ libpqConnect(const char *connstr)
pg_log(PG_PROGRESS, "connected to server\n"); pg_log(PG_PROGRESS, "connected to server\n");
res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
pg_fatal("could not clear search_path: %s",
PQresultErrorMessage(res));
PQclear(res);
/* /*
* Check that the server is not in hot standby mode. There is no * Check that the server is not in hot standby mode. There is no
* fundamental reason that couldn't be made to work, but it doesn't * fundamental reason that couldn't be made to work, but it doesn't

View File

@ -9,6 +9,7 @@
#include "postgres_fe.h" #include "postgres_fe.h"
#include "fe_utils/connect.h"
#include "fe_utils/string_utils.h" #include "fe_utils/string_utils.h"
#include "pg_upgrade.h" #include "pg_upgrade.h"
@ -40,6 +41,8 @@ connectToServer(ClusterInfo *cluster, const char *db_name)
exit(1); exit(1);
} }
PQclear(executeQueryOrDie(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
return conn; return conn;
} }

View File

@ -195,17 +195,21 @@ cluster_one_database(const char *dbname, bool verbose, const char *table,
PGconn *conn; PGconn *conn;
conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, echo, false, false);
initPQExpBuffer(&sql); initPQExpBuffer(&sql);
appendPQExpBufferStr(&sql, "CLUSTER"); appendPQExpBufferStr(&sql, "CLUSTER");
if (verbose) if (verbose)
appendPQExpBufferStr(&sql, " VERBOSE"); appendPQExpBufferStr(&sql, " VERBOSE");
if (table) if (table)
appendPQExpBuffer(&sql, " %s", table); {
appendPQExpBufferChar(&sql, ' ');
appendQualifiedRelation(&sql, table, conn, progname, echo);
}
appendPQExpBufferChar(&sql, ';'); appendPQExpBufferChar(&sql, ';');
conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false);
if (!executeMaintenanceCommand(conn, sql.data, echo)) if (!executeMaintenanceCommand(conn, sql.data, echo))
{ {
if (table) if (table)
@ -234,7 +238,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db,
int i; int i;
conn = connectMaintenanceDatabase(maintenance_db, host, port, username, 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); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
PQfinish(conn); PQfinish(conn);

View File

@ -18,6 +18,8 @@
#include <unistd.h> #include <unistd.h>
#include "common.h" #include "common.h"
#include "fe_utils/connect.h"
#include "fe_utils/string_utils.h"
static PGcancel *volatile cancelConn = NULL; static PGcancel *volatile cancelConn = NULL;
@ -63,9 +65,10 @@ handle_help_version_opts(int argc, char *argv[],
* as before, else we might create password exposure hazards.) * as before, else we might create password exposure hazards.)
*/ */
PGconn * PGconn *
connectDatabase(const char *dbname, const char *pghost, const char *pgport, connectDatabase(const char *dbname, const char *pghost,
const char *pguser, enum trivalue prompt_password, const char *pgport, const char *pguser,
const char *progname, bool fail_ok, bool allow_password_reuse) enum trivalue prompt_password, const char *progname,
bool echo, bool fail_ok, bool allow_password_reuse)
{ {
PGconn *conn; PGconn *conn;
static char *password = NULL; static char *password = NULL;
@ -143,6 +146,10 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
exit(1); exit(1);
} }
if (PQserverVersion(conn) >= 70300)
PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL,
progname, echo));
return conn; return conn;
} }
@ -150,24 +157,24 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
* Try to connect to the appropriate maintenance database. * Try to connect to the appropriate maintenance database.
*/ */
PGconn * PGconn *
connectMaintenanceDatabase(const char *maintenance_db, const char *pghost, connectMaintenanceDatabase(const char *maintenance_db,
const char *pgport, const char *pguser, const char *pghost, const char *pgport,
enum trivalue prompt_password, const char *pguser, enum trivalue prompt_password,
const char *progname) const char *progname, bool echo)
{ {
PGconn *conn; PGconn *conn;
/* If a maintenance database name was specified, just connect to it. */ /* If a maintenance database name was specified, just connect to it. */
if (maintenance_db) if (maintenance_db)
return connectDatabase(maintenance_db, pghost, pgport, pguser, return connectDatabase(maintenance_db, pghost, pgport, pguser,
prompt_password, progname, false, false); prompt_password, progname, echo, false, false);
/* Otherwise, try postgres first and then template1. */ /* Otherwise, try postgres first and then template1. */
conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password, conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password,
progname, true, false); progname, echo, true, false);
if (!conn) if (!conn)
conn = connectDatabase("template1", pghost, pgport, pguser, conn = connectDatabase("template1", pghost, pgport, pguser,
prompt_password, progname, false, false); prompt_password, progname, echo, false, false);
return conn; return conn;
} }
@ -253,6 +260,116 @@ executeMaintenanceCommand(PGconn *conn, const char *query, bool echo)
return r; 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. * Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither.
*/ */

View File

@ -32,11 +32,12 @@ extern void handle_help_version_opts(int argc, char *argv[],
extern PGconn *connectDatabase(const char *dbname, const char *pghost, extern PGconn *connectDatabase(const char *dbname, const char *pghost,
const char *pgport, const char *pguser, const char *pgport, const char *pguser,
enum trivalue prompt_password, const char *progname, enum trivalue prompt_password, const char *progname,
bool fail_ok, bool allow_password_reuse); bool echo, bool fail_ok, bool allow_password_reuse);
extern PGconn *connectMaintenanceDatabase(const char *maintenance_db, extern PGconn *connectMaintenanceDatabase(const char *maintenance_db,
const char *pghost, const char *pgport, const char *pguser, const char *pghost, const char *pgport,
enum trivalue prompt_password, const char *progname); const char *pguser, enum trivalue prompt_password,
const char *progname, bool echo);
extern PGresult *executeQuery(PGconn *conn, const char *query, extern PGresult *executeQuery(PGconn *conn, const char *query,
const char *progname, bool echo); const char *progname, bool echo);
@ -47,6 +48,9 @@ extern void executeCommand(PGconn *conn, const char *query,
extern bool executeMaintenanceCommand(PGconn *conn, const char *query, extern bool executeMaintenanceCommand(PGconn *conn, const char *query,
bool echo); 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 bool yesno_prompt(const char *question);
extern void setup_cancel_handler(void); extern void setup_cancel_handler(void);

View File

@ -202,7 +202,7 @@ main(int argc, char *argv[])
maintenance_db = "template1"; maintenance_db = "template1";
conn = connectMaintenanceDatabase(maintenance_db, host, port, username, conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
prompt_password, progname); prompt_password, progname, echo);
if (echo) if (echo)
printf("%s\n", sql.data); printf("%s\n", sql.data);

View File

@ -141,7 +141,7 @@ main(int argc, char *argv[])
static const bool translate_columns[] = {false, true}; static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", " printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" " "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@ -181,7 +181,7 @@ main(int argc, char *argv[])
*p += ('a' - 'A'); *p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
/* /*
* Make sure the language isn't already installed * Make sure the language isn't already installed

View File

@ -252,7 +252,7 @@ main(int argc, char *argv[])
login = TRI_YES; login = TRI_YES;
conn = connectDatabase("postgres", host, port, username, prompt_password, conn = connectDatabase("postgres", host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
initPQExpBuffer(&sql); initPQExpBuffer(&sql);

View File

@ -129,7 +129,8 @@ main(int argc, char *argv[])
maintenance_db = "template1"; maintenance_db = "template1";
conn = connectMaintenanceDatabase(maintenance_db, conn = connectMaintenanceDatabase(maintenance_db,
host, port, username, prompt_password, progname); host, port, username, prompt_password,
progname, echo);
if (echo) if (echo)
printf("%s\n", sql.data); printf("%s\n", sql.data);

View File

@ -140,7 +140,7 @@ main(int argc, char *argv[])
static const bool translate_columns[] = {false, true}; static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", " printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" " "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@ -182,13 +182,7 @@ main(int argc, char *argv[])
*p += ('a' - 'A'); *p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, 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);
/* /*
* Make sure the language is installed * Make sure the language is installed

View File

@ -129,7 +129,7 @@ main(int argc, char *argv[])
(if_exists ? "IF EXISTS " : ""), fmtId(dropuser)); (if_exists ? "IF EXISTS " : ""), fmtId(dropuser));
conn = connectDatabase("postgres", host, port, username, prompt_password, conn = connectDatabase("postgres", host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
if (echo) if (echo)
printf("%s\n", sql.data); printf("%s\n", sql.data);

View File

@ -282,23 +282,24 @@ reindex_one_database(const char *name, const char *dbname, const char *type,
PGconn *conn; PGconn *conn;
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
initPQExpBuffer(&sql); initPQExpBuffer(&sql);
appendPQExpBufferStr(&sql, "REINDEX"); appendPQExpBufferStr(&sql, "REINDEX ");
if (verbose) if (verbose)
appendPQExpBufferStr(&sql, " (VERBOSE)"); appendPQExpBufferStr(&sql, "(VERBOSE) ");
if (strcmp(type, "TABLE") == 0) appendPQExpBufferStr(&sql, type);
appendPQExpBuffer(&sql, " TABLE %s", name); appendPQExpBufferChar(&sql, ' ');
else if (strcmp(type, "INDEX") == 0) if (strcmp(type, "TABLE") == 0 ||
appendPQExpBuffer(&sql, " INDEX %s", name); strcmp(type, "INDEX") == 0)
appendQualifiedRelation(&sql, name, conn, progname, echo);
else if (strcmp(type, "SCHEMA") == 0) else if (strcmp(type, "SCHEMA") == 0)
appendPQExpBuffer(&sql, " SCHEMA %s", name); appendPQExpBufferStr(&sql, name);
else if (strcmp(type, "DATABASE") == 0) else if (strcmp(type, "DATABASE") == 0)
appendPQExpBuffer(&sql, " DATABASE %s", fmtId(PQdb(conn))); appendPQExpBufferStr(&sql, fmtId(PQdb(conn)));
appendPQExpBufferChar(&sql, ';'); appendPQExpBufferChar(&sql, ';');
if (!executeMaintenanceCommand(conn, sql.data, echo)) if (!executeMaintenanceCommand(conn, sql.data, echo))
@ -335,7 +336,7 @@ reindex_all_databases(const char *maintenance_db,
int i; int i;
conn = connectMaintenanceDatabase(maintenance_db, host, port, username, 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); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
PQfinish(conn); PQfinish(conn);
@ -372,7 +373,7 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port,
PQExpBufferData sql; PQExpBufferData sql;
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, false); progname, echo, false, false);
initPQExpBuffer(&sql); initPQExpBuffer(&sql);

View File

@ -26,5 +26,5 @@ $node->safe_psql('postgres',
); );
$node->issues_sql_like( $node->issues_sql_like(
[ 'clusterdb', '-t', 'test1' ], [ 'clusterdb', '-t', 'test1' ],
qr/statement: CLUSTER test1;/, qr/statement: CLUSTER public\.test1;/,
'cluster specific table'); 'cluster specific table');

View File

@ -24,11 +24,11 @@ $node->safe_psql('postgres',
'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);'); 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);');
$node->issues_sql_like( $node->issues_sql_like(
[ 'reindexdb', '-t', 'test1', 'postgres' ], [ 'reindexdb', '-t', 'test1', 'postgres' ],
qr/statement: REINDEX TABLE test1;/, qr/statement: REINDEX TABLE public\.test1;/,
'reindex specific table'); 'reindex specific table');
$node->issues_sql_like( $node->issues_sql_like(
[ 'reindexdb', '-i', 'test1x', 'postgres' ], [ 'reindexdb', '-i', 'test1x', 'postgres' ],
qr/statement: REINDEX INDEX test1x;/, qr/statement: REINDEX INDEX public\.test1x;/,
'reindex specific index'); 'reindex specific index');
$node->issues_sql_like( $node->issues_sql_like(
[ 'reindexdb', '-S', 'pg_catalog', 'postgres' ], [ 'reindexdb', '-S', 'pg_catalog', 'postgres' ],
@ -40,5 +40,5 @@ $node->issues_sql_like(
'reindex system tables'); 'reindex system tables');
$node->issues_sql_like( $node->issues_sql_like(
[ 'reindexdb', '-v', '-t', 'test1', 'postgres' ], [ 'reindexdb', '-v', '-t', 'test1', 'postgres' ],
qr/statement: REINDEX \(VERBOSE\) TABLE test1;/, qr/statement: REINDEX \(VERBOSE\) TABLE public\.test1;/,
'reindex with verbose output'); 'reindex with verbose output');

View File

@ -3,7 +3,7 @@ use warnings;
use PostgresNode; use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 18; use Test::More tests => 23;
program_help_ok('vacuumdb'); program_help_ok('vacuumdb');
program_version_ok('vacuumdb'); program_version_ok('vacuumdb');
@ -26,10 +26,32 @@ $node->issues_sql_like(
qr/statement: VACUUM \(FREEZE\);/, qr/statement: VACUUM \(FREEZE\);/,
'vacuumdb -F'); 'vacuumdb -F');
$node->issues_sql_like( $node->issues_sql_like(
[ 'vacuumdb', '-z', 'postgres' ], [ 'vacuumdb', '-zj2', 'postgres' ],
qr/statement: VACUUM \(ANALYZE\);/, qr/statement: VACUUM \(ANALYZE\) pg_catalog\./,
'vacuumdb -z'); 'vacuumdb -zj2');
$node->issues_sql_like( $node->issues_sql_like(
[ 'vacuumdb', '-Z', 'postgres' ], [ 'vacuumdb', '-Z', 'postgres' ],
qr/statement: ANALYZE;/, qr/statement: ANALYZE;/,
'vacuumdb -Z'); 'vacuumdb -Z');
$node->command_ok([qw(vacuumdb -Z --table=pg_am dbname=template1)],
'vacuumdb with connection string');
$node->command_fails([qw(vacuumdb -Zt pg_am;ABORT postgres)],
'trailing command in "-t", without COLUMNS');
# Unwanted; better if it failed.
$node->command_ok([qw(vacuumdb -Zt pg_am(amname);ABORT postgres)],
'trailing command in "-t", with COLUMNS');
$node->safe_psql('postgres', q|
CREATE TABLE "need""q(uot" (")x" text);
CREATE FUNCTION f0(int) RETURNS int LANGUAGE SQL AS 'SELECT $1 * $1';
CREATE FUNCTION f1(int) RETURNS int LANGUAGE SQL AS 'SELECT f0($1)';
CREATE TABLE funcidx (x int);
INSERT INTO funcidx VALUES (0),(1),(2),(3);
CREATE INDEX i0 ON funcidx ((f1(x)));
|);
$node->command_ok([qw|vacuumdb -Z --table="need""q(uot"(")x") postgres|],
'column list');
$node->command_fails([qw|vacuumdb -Zt funcidx postgres|],
'unqualifed name via functional index');

View File

@ -59,7 +59,9 @@ static void vacuum_all_databases(vacuumingOptions *vacopts,
const char *progname, bool echo, bool quiet); const char *progname, bool echo, bool quiet);
static void prepare_vacuum_command(PQExpBuffer sql, PGconn *conn, static void prepare_vacuum_command(PQExpBuffer sql, PGconn *conn,
vacuumingOptions *vacopts, const char *table); vacuumingOptions *vacopts, const char *table,
bool table_pre_qualified,
const char *progname, bool echo);
static void run_vacuum_command(PGconn *conn, const char *sql, bool echo, static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
const char *table, const char *progname, bool async); const char *table, const char *progname, bool async);
@ -359,7 +361,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
(stage >= 0 && stage < ANALYZE_NUM_STAGES)); (stage >= 0 && stage < ANALYZE_NUM_STAGES));
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, true); progname, echo, false, true);
if (!quiet) if (!quiet)
{ {
@ -431,7 +433,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
for (i = 1; i < concurrentCons; i++) for (i = 1; i < concurrentCons; i++)
{ {
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, prompt_password,
progname, false, true); progname, echo, false, true);
init_slot(slots + i, conn, progname); init_slot(slots + i, conn, progname);
} }
} }
@ -457,7 +459,8 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
ParallelSlot *free_slot; ParallelSlot *free_slot;
const char *tabname = cell ? cell->val : NULL; const char *tabname = cell ? cell->val : NULL;
prepare_vacuum_command(&sql, conn, vacopts, tabname); prepare_vacuum_command(&sql, conn, vacopts, tabname,
tables == &dbtables, progname, echo);
if (CancelRequested) if (CancelRequested)
{ {
@ -548,8 +551,8 @@ vacuum_all_databases(vacuumingOptions *vacopts,
int stage; int stage;
int i; int i;
conn = connectMaintenanceDatabase(maintenance_db, host, port, conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
username, prompt_password, progname); prompt_password, progname, echo);
result = executeQuery(conn, result = executeQuery(conn,
"SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;",
progname, echo); progname, echo);
@ -612,8 +615,10 @@ vacuum_all_databases(vacuumingOptions *vacopts,
* quoted. The command is semicolon-terminated. * quoted. The command is semicolon-terminated.
*/ */
static void static void
prepare_vacuum_command(PQExpBuffer sql, PGconn *conn, vacuumingOptions *vacopts, prepare_vacuum_command(PQExpBuffer sql, PGconn *conn,
const char *table) vacuumingOptions *vacopts, const char *table,
bool table_pre_qualified,
const char *progname, bool echo)
{ {
resetPQExpBuffer(sql); resetPQExpBuffer(sql);
@ -669,7 +674,13 @@ prepare_vacuum_command(PQExpBuffer sql, PGconn *conn, vacuumingOptions *vacopts,
} }
if (table) if (table)
appendPQExpBuffer(sql, " %s", table); {
appendPQExpBufferChar(sql, ' ');
if (table_pre_qualified)
appendPQExpBufferStr(sql, table);
else
appendQualifiedRelation(sql, table, conn, progname, echo);
}
appendPQExpBufferChar(sql, ';'); appendPQExpBufferChar(sql, ';');
} }

View File

@ -927,8 +927,9 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
} }
/* /*
* Now decide what we need to emit. Note there will be a leading "^(" in * Now decide what we need to emit. We may run under a hostile
* the patterns in any case. * search_path, so qualify EVERY name. Note there will be a leading "^("
* in the patterns in any case.
*/ */
if (namebuf.len > 2) if (namebuf.len > 2)
{ {
@ -941,15 +942,18 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
WHEREAND(); WHEREAND();
if (altnamevar) if (altnamevar)
{ {
appendPQExpBuffer(buf, "(%s ~ ", namevar); appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
appendStringLiteralConn(buf, namebuf.data, conn); 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); appendStringLiteralConn(buf, namebuf.data, conn);
appendPQExpBufferStr(buf, ")\n"); appendPQExpBufferStr(buf, ")\n");
} }
else else
{ {
appendPQExpBuffer(buf, "%s ~ ", namevar); appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
appendStringLiteralConn(buf, namebuf.data, conn); appendStringLiteralConn(buf, namebuf.data, conn);
appendPQExpBufferChar(buf, '\n'); appendPQExpBufferChar(buf, '\n');
} }
@ -965,7 +969,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar) if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
{ {
WHEREAND(); WHEREAND();
appendPQExpBuffer(buf, "%s ~ ", schemavar); appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
appendStringLiteralConn(buf, schemabuf.data, conn); appendStringLiteralConn(buf, schemabuf.data, conn);
appendPQExpBufferChar(buf, '\n'); appendPQExpBufferChar(buf, '\n');
} }

View File

@ -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 */

View File

@ -7,6 +7,7 @@
*/ */
#include "postgres_fe.h" #include "postgres_fe.h"
#include "fe_utils/connect.h"
#include "libpq-fe.h" #include "libpq-fe.h"
#include "pqexpbuffer.h" #include "pqexpbuffer.h"
@ -44,6 +45,14 @@ main(int argc, char **argv)
exit(EXIT_FAILURE); 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 */ /* Get a list of relations that have OIDs */
printfPQExpBuffer(&sql, "%s", printfPQExpBuffer(&sql, "%s",