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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy