Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 10d5983

Browse files
committed
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
1 parent b8a2908 commit 10d5983

File tree

24 files changed

+338
-66
lines changed

24 files changed

+338
-66
lines changed

contrib/oid2name/oid2name.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "catalog/pg_class.h"
1313

14+
#include "fe_utils/connect.h"
1415
#include "libpq-fe.h"
1516
#include "pg_getopt.h"
1617

@@ -266,6 +267,7 @@ sql_conn(struct options *my_opts)
266267
bool have_password = false;
267268
char password[100];
268269
bool new_pass;
270+
PGresult *res;
269271

270272
/*
271273
* Start the connection. Loop until we have a password if requested by
@@ -323,6 +325,17 @@ sql_conn(struct options *my_opts)
323325
exit(1);
324326
}
325327

328+
res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
329+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
330+
{
331+
fprintf(stderr, "oid2name: could not clear search_path: %s\n",
332+
PQerrorMessage(conn));
333+
PQclear(res);
334+
PQfinish(conn);
335+
exit(-1);
336+
}
337+
PQclear(res);
338+
326339
/* return the conn if good */
327340
return conn;
328341
}

contrib/vacuumlo/vacuumlo.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "catalog/pg_class.h"
2525

26+
#include "fe_utils/connect.h"
2627
#include "libpq-fe.h"
2728
#include "pg_getopt.h"
2829

@@ -140,11 +141,8 @@ vacuumlo(const char *database, const struct _param *param)
140141
fprintf(stdout, "Test run: no large objects will be removed!\n");
141142
}
142143

143-
/*
144-
* Don't get fooled by any non-system catalogs
145-
*/
146-
res = PQexec(conn, "SET search_path = pg_catalog");
147-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
144+
res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
145+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
148146
{
149147
fprintf(stderr, "Failed to set search_path:\n");
150148
fprintf(stderr, "%s", PQerrorMessage(conn));

src/backend/postmaster/autovacuum.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,12 @@ AutoVacLauncherMain(int argc, char *argv[])
573573
/* must unblock signals before calling rebuild_database_list */
574574
PG_SETMASK(&UnBlockSig);
575575

576+
/*
577+
* Set always-secure search path. Launcher doesn't connect to a database,
578+
* so this has no effect.
579+
*/
580+
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
581+
576582
/*
577583
* Force zero_damaged_pages OFF in the autovac process, even if it is set
578584
* in postgresql.conf. We don't really want such a dangerous option being
@@ -1583,6 +1589,14 @@ AutoVacWorkerMain(int argc, char *argv[])
15831589

15841590
PG_SETMASK(&UnBlockSig);
15851591

1592+
/*
1593+
* Set always-secure search path, so malicious users can't redirect user
1594+
* code (e.g. pg_index.indexprs). (That code runs in a
1595+
* SECURITY_RESTRICTED_OPERATION sandbox, so malicious users could not
1596+
* take control of the entire autovacuum worker in any case.)
1597+
*/
1598+
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
1599+
15861600
/*
15871601
* Force zero_damaged_pages OFF in the autovac process, even if it is set
15881602
* in postgresql.conf. We don't really want such a dangerous option being

src/bin/pg_basebackup/streamutil.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "pqexpbuffer.h"
2929
#include "common/fe_memutils.h"
3030
#include "datatype/timestamp.h"
31+
#include "fe_utils/connect.h"
3132

3233
#define ERRCODE_DUPLICATE_OBJECT "42710"
3334

@@ -205,6 +206,23 @@ GetConnection(void)
205206
if (conn_opts)
206207
PQconninfoFree(conn_opts);
207208

209+
/* Set always-secure search path, so malicious users can't get control. */
210+
if (dbname != NULL)
211+
{
212+
PGresult *res;
213+
214+
res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
215+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
216+
{
217+
fprintf(stderr, _("%s: could not clear search_path: %s\n"),
218+
progname, PQerrorMessage(tmpconn));
219+
PQclear(res);
220+
PQfinish(tmpconn);
221+
exit(1);
222+
}
223+
PQclear(res);
224+
}
225+
208226
/*
209227
* Ensure we have the same value of integer_datetimes (now always "on") as
210228
* the server we are connecting to.

src/bin/pg_dump/pg_backup_db.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "postgres_fe.h"
1313

1414
#include "dumputils.h"
15+
#include "fe_utils/connect.h"
1516
#include "fe_utils/string_utils.h"
1617
#include "parallel.h"
1718
#include "pg_backup_archiver.h"
@@ -112,6 +113,10 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
112113
PQfinish(AH->connection);
113114
AH->connection = newConn;
114115

116+
/* Start strict; later phases may override this. */
117+
PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
118+
ALWAYS_SECURE_SEARCH_PATH_SQL));
119+
115120
return 1;
116121
}
117122

@@ -315,6 +320,10 @@ ConnectDatabase(Archive *AHX,
315320
PQdb(AH->connection) ? PQdb(AH->connection) : "",
316321
PQerrorMessage(AH->connection));
317322

323+
/* Start strict; later phases may override this. */
324+
PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
325+
ALWAYS_SECURE_SEARCH_PATH_SQL));
326+
318327
/*
319328
* We want to remember connection's actual password, whether or not we got
320329
* it by prompting. So we don't just store the password variable.

src/bin/pg_dump/pg_dump.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include "pg_backup_db.h"
6060
#include "pg_backup_utils.h"
6161
#include "pg_dump.h"
62+
#include "fe_utils/connect.h"
6263
#include "fe_utils/string_utils.h"
6364

6465

@@ -1002,6 +1003,8 @@ setup_connection(Archive *AH, const char *dumpencoding,
10021003
PGconn *conn = GetConnection(AH);
10031004
const char *std_strings;
10041005

1006+
PQclear(ExecuteSqlQueryForSingleRow(AH, ALWAYS_SECURE_SEARCH_PATH_SQL));
1007+
10051008
/*
10061009
* Set the client encoding if requested.
10071010
*/
@@ -1292,19 +1295,29 @@ expand_table_name_patterns(Archive *fout,
12921295

12931296
for (cell = patterns->head; cell; cell = cell->next)
12941297
{
1298+
/*
1299+
* Query must remain ABSOLUTELY devoid of unqualified names. This
1300+
* would be unnecessary given a pg_table_is_visible() variant taking a
1301+
* search_path argument.
1302+
*/
12951303
appendPQExpBuffer(query,
12961304
"SELECT c.oid"
12971305
"\nFROM pg_catalog.pg_class c"
1298-
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
1299-
"\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
1306+
"\n LEFT JOIN pg_catalog.pg_namespace n"
1307+
"\n ON n.oid OPERATOR(pg_catalog.=) c.relnamespace"
1308+
"\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY"
1309+
"\n (array['%c', '%c', '%c', '%c', '%c', '%c'])\n",
13001310
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
13011311
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
13021312
RELKIND_PARTITIONED_TABLE);
13031313
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
13041314
false, "n.nspname", "c.relname", NULL,
13051315
"pg_catalog.pg_table_is_visible(c.oid)");
13061316

1317+
ExecuteSqlStatement(fout, "RESET search_path");
13071318
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
1319+
PQclear(ExecuteSqlQueryForSingleRow(fout,
1320+
ALWAYS_SECURE_SEARCH_PATH_SQL));
13081321
if (strict_names && PQntuples(res) == 0)
13091322
exit_horribly(NULL, "no matching tables were found for pattern \"%s\"\n", cell->val);
13101323

src/bin/pg_dump/pg_dumpall.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "dumputils.h"
2424
#include "pg_backup.h"
2525
#include "common/file_utils.h"
26+
#include "fe_utils/connect.h"
2627
#include "fe_utils/string_utils.h"
2728

2829
/* version string we expect back from pg_dump */
@@ -2068,10 +2069,7 @@ connectDatabase(const char *dbname, const char *connection_string,
20682069
exit_nicely(1);
20692070
}
20702071

2071-
/*
2072-
* Make sure we are not fooled by non-system schemas in the search path.
2073-
*/
2074-
executeCommand(conn, "SET search_path = pg_catalog");
2072+
PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
20752073

20762074
return conn;
20772075
}

src/bin/pg_rewind/libpq_fetch.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "libpq-fe.h"
2929
#include "catalog/catalog.h"
3030
#include "catalog/pg_type.h"
31+
#include "fe_utils/connect.h"
3132

3233
static PGconn *conn = NULL;
3334

@@ -57,6 +58,12 @@ libpqConnect(const char *connstr)
5758

5859
pg_log(PG_PROGRESS, "connected to server\n");
5960

61+
res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
62+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
63+
pg_fatal("could not clear search_path: %s",
64+
PQresultErrorMessage(res));
65+
PQclear(res);
66+
6067
/*
6168
* Check that the server is not in hot standby mode. There is no
6269
* fundamental reason that couldn't be made to work, but it doesn't

src/bin/pg_upgrade/server.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "postgres_fe.h"
1111

12+
#include "fe_utils/connect.h"
1213
#include "fe_utils/string_utils.h"
1314
#include "pg_upgrade.h"
1415

@@ -40,6 +41,8 @@ connectToServer(ClusterInfo *cluster, const char *db_name)
4041
exit(1);
4142
}
4243

44+
PQclear(executeQueryOrDie(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
45+
4346
return conn;
4447
}
4548

src/bin/scripts/clusterdb.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,21 @@ cluster_one_database(const char *dbname, bool verbose, const char *table,
195195

196196
PGconn *conn;
197197

198+
conn = connectDatabase(dbname, host, port, username, prompt_password,
199+
progname, echo, false, false);
200+
198201
initPQExpBuffer(&sql);
199202

200203
appendPQExpBufferStr(&sql, "CLUSTER");
201204
if (verbose)
202205
appendPQExpBufferStr(&sql, " VERBOSE");
203206
if (table)
204-
appendPQExpBuffer(&sql, " %s", table);
207+
{
208+
appendPQExpBufferChar(&sql, ' ');
209+
appendQualifiedRelation(&sql, table, conn, progname, echo);
210+
}
205211
appendPQExpBufferChar(&sql, ';');
206212

207-
conn = connectDatabase(dbname, host, port, username, prompt_password,
208-
progname, false, false);
209213
if (!executeMaintenanceCommand(conn, sql.data, echo))
210214
{
211215
if (table)
@@ -234,7 +238,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db,
234238
int i;
235239

236240
conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
237-
prompt_password, progname);
241+
prompt_password, progname, echo);
238242
result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
239243
PQfinish(conn);
240244

0 commit comments

Comments
 (0)