aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNoah Misch <noah@leadboat.com>2018-02-26 07:39:44 -0800
committerNoah Misch <noah@leadboat.com>2018-02-26 07:39:48 -0800
commit1f47ea7b87185ff370ef9b27ea7c561c58583be3 (patch)
treedc34ffa36047661045e174bf58837b57ed015b84 /src
parent91f3ffc5249eff99c311fb27e7b29a44d9c62be1 (diff)
downloadpostgresql-1f47ea7b87185ff370ef9b27ea7c561c58583be3.tar.gz
postgresql-1f47ea7b87185ff370ef9b27ea7c561c58583be3.zip
Document security implications of search_path and the public schema.
The ability to create like-named objects in different schemas opens up the potential for users to change the behavior of other users' queries, maliciously or accidentally. When you connect to a PostgreSQL server, you should remove from your search_path any schema for which a user other than yourself or superusers holds the CREATE privilege. If you do not, other users holding CREATE privilege can redefine the behavior of your commands, causing them to perform arbitrary SQL statements under your identity. "SET search_path = ..." and "SELECT pg_catalog.set_config(...)" are not vulnerable to such hijacking, so one can use either as the first command of a session. As special exceptions, the following client applications behave as documented regardless of search_path settings and schema privileges: clusterdb createdb createlang createuser dropdb droplang dropuser ecpg (not programs it generates) initdb oid2name pg_archivecleanup pg_basebackup pg_config pg_controldata pg_ctl pg_dump pg_dumpall pg_isready pg_receivewal pg_recvlogical pg_resetwal pg_restore pg_rewind pg_standby pg_test_fsync pg_test_timing pg_upgrade pg_waldump reindexdb vacuumdb vacuumlo. Not included are core client programs that run user-specified SQL commands, namely psql and pgbench. PostgreSQL encourages non-core client applications to do likewise. Document this in the context of libpq connections, psql connections, dblink connections, ECPG connections, extension packaging, and schema usage patterns. The principal defense for applications is "SELECT pg_catalog.set_config('search_path', '', false)", and the principal defense for databases is "REVOKE CREATE ON SCHEMA public FROM PUBLIC". Either one is sufficient to prevent attack. After a REVOKE, consider auditing the public schema for objects named like pg_catalog objects. Authors of SECURITY DEFINER functions use some of the same defenses, and the CREATE FUNCTION reference page already covered them thoroughly. This is a good opportunity to audit SECURITY DEFINER functions for robust security practice. Back-patch to 9.3 (all supported versions). Reviewed by Michael Paquier and Jonathan S. Katz. Reported by Arseniy Sharoglazov. Security: CVE-2018-1058
Diffstat (limited to 'src')
-rw-r--r--src/test/examples/testlibpq.c21
-rw-r--r--src/test/examples/testlibpq2.c29
-rw-r--r--src/test/examples/testlibpq2.sql4
-rw-r--r--src/test/examples/testlibpq3.c13
-rw-r--r--src/test/examples/testlibpq3.sql3
-rw-r--r--src/test/examples/testlibpq4.c19
-rw-r--r--src/test/examples/testlo.c11
-rw-r--r--src/test/examples/testlo64.c11
8 files changed, 90 insertions, 21 deletions
diff --git a/src/test/examples/testlibpq.c b/src/test/examples/testlibpq.c
index 4d9af82dd18..92a05e53093 100644
--- a/src/test/examples/testlibpq.c
+++ b/src/test/examples/testlibpq.c
@@ -48,6 +48,22 @@ main(int argc, char **argv)
exit_nicely(conn);
}
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ /*
+ * Should PQclear PGresult whenever it is no longer needed to avoid memory
+ * leaks
+ */
+ PQclear(res);
+
/*
* Our test case here involves using a cursor, for which we must be inside
* a transaction block. We could do the whole thing with a single
@@ -63,11 +79,6 @@ main(int argc, char **argv)
PQclear(res);
exit_nicely(conn);
}
-
- /*
- * Should PQclear PGresult whenever it is no longer needed to avoid memory
- * leaks
- */
PQclear(res);
/*
diff --git a/src/test/examples/testlibpq2.c b/src/test/examples/testlibpq2.c
index 07c6317a212..76787fe010b 100644
--- a/src/test/examples/testlibpq2.c
+++ b/src/test/examples/testlibpq2.c
@@ -13,16 +13,16 @@
* populate a database with the following commands
* (provided in src/test/examples/testlibpq2.sql):
*
+ * CREATE SCHEMA TESTLIBPQ2;
+ * SET search_path = TESTLIBPQ2;
* CREATE TABLE TBL1 (i int4);
- *
* CREATE TABLE TBL2 (i int4);
- *
* CREATE RULE r1 AS ON INSERT TO TBL1 DO
* (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
*
- * and do this four times:
+ * Start this program, then from psql do this four times:
*
- * INSERT INTO TBL1 VALUES (10);
+ * INSERT INTO TESTLIBPQ2.TBL1 VALUES (10);
*/
#ifdef WIN32
@@ -77,6 +77,22 @@ main(int argc, char **argv)
exit_nicely(conn);
}
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+
+ /*
+ * Should PQclear PGresult whenever it is no longer needed to avoid memory
+ * leaks
+ */
+ PQclear(res);
+
/*
* Issue LISTEN command to enable notifications from the rule's NOTIFY.
*/
@@ -87,11 +103,6 @@ main(int argc, char **argv)
PQclear(res);
exit_nicely(conn);
}
-
- /*
- * should PQclear PGresult whenever it is no longer needed to avoid memory
- * leaks
- */
PQclear(res);
/* Quit after four notifies are received. */
diff --git a/src/test/examples/testlibpq2.sql b/src/test/examples/testlibpq2.sql
index 1686c3ed0df..e8173e4293b 100644
--- a/src/test/examples/testlibpq2.sql
+++ b/src/test/examples/testlibpq2.sql
@@ -1,6 +1,6 @@
+CREATE SCHEMA TESTLIBPQ2;
+SET search_path = TESTLIBPQ2;
CREATE TABLE TBL1 (i int4);
-
CREATE TABLE TBL2 (i int4);
-
CREATE RULE r1 AS ON INSERT TO TBL1 DO
(INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
diff --git a/src/test/examples/testlibpq3.c b/src/test/examples/testlibpq3.c
index e11e0567cac..00e62b43d28 100644
--- a/src/test/examples/testlibpq3.c
+++ b/src/test/examples/testlibpq3.c
@@ -8,8 +8,9 @@
* Before running this, populate a database with the following commands
* (provided in src/test/examples/testlibpq3.sql):
*
+ * CREATE SCHEMA testlibpq3;
+ * SET search_path = testlibpq3;
* CREATE TABLE test1 (i int4, t text, b bytea);
- *
* INSERT INTO test1 values (1, 'joe''s place', '\\000\\001\\002\\003\\004');
* INSERT INTO test1 values (2, 'ho there', '\\004\\003\\002\\001\\000');
*
@@ -141,6 +142,16 @@ main(int argc, char **argv)
exit_nicely(conn);
}
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn, "SET search_path = testlibpq3");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
/*
* The point of this program is to illustrate use of PQexecParams() with
* out-of-line parameters, as well as binary transmission of data.
diff --git a/src/test/examples/testlibpq3.sql b/src/test/examples/testlibpq3.sql
index 9d9e217e5dd..22133065099 100644
--- a/src/test/examples/testlibpq3.sql
+++ b/src/test/examples/testlibpq3.sql
@@ -1,4 +1,5 @@
+CREATE SCHEMA testlibpq3;
+SET search_path = testlibpq3;
CREATE TABLE test1 (i int4, t text, b bytea);
-
INSERT INTO test1 values (1, 'joe''s place', '\\000\\001\\002\\003\\004');
INSERT INTO test1 values (2, 'ho there', '\\004\\003\\002\\001\\000');
diff --git a/src/test/examples/testlibpq4.c b/src/test/examples/testlibpq4.c
index 0ec04313c0c..a20f6249b4e 100644
--- a/src/test/examples/testlibpq4.c
+++ b/src/test/examples/testlibpq4.c
@@ -22,8 +22,10 @@ exit_nicely(PGconn *conn1, PGconn *conn2)
}
static void
-check_conn(PGconn *conn, const char *dbName)
+check_prepare_conn(PGconn *conn, const char *dbName)
{
+ PGresult *res;
+
/* check to see that the backend connection was successfully made */
if (PQstatus(conn) != CONNECTION_OK)
{
@@ -31,6 +33,17 @@ check_conn(PGconn *conn, const char *dbName)
dbName, PQerrorMessage(conn));
exit(1);
}
+
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit(1);
+ }
+ PQclear(res);
}
int
@@ -80,10 +93,10 @@ main(int argc, char **argv)
/* make a connection to the database */
conn1 = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName1);
- check_conn(conn1, dbName1);
+ check_prepare_conn(conn1, dbName1);
conn2 = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName2);
- check_conn(conn2, dbName2);
+ check_prepare_conn(conn2, dbName2);
/* start a transaction block */
res1 = PQexec(conn1, "BEGIN");
diff --git a/src/test/examples/testlo.c b/src/test/examples/testlo.c
index 4700aad0ac6..8a65f897cd7 100644
--- a/src/test/examples/testlo.c
+++ b/src/test/examples/testlo.c
@@ -232,6 +232,17 @@ main(int argc, char **argv)
exit_nicely(conn);
}
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
res = PQexec(conn, "begin");
PQclear(res);
printf("importing file \"%s\" ...\n", in_filename);
diff --git a/src/test/examples/testlo64.c b/src/test/examples/testlo64.c
index 4ce52f2e9c2..5512107dfad 100644
--- a/src/test/examples/testlo64.c
+++ b/src/test/examples/testlo64.c
@@ -256,6 +256,17 @@ main(int argc, char **argv)
exit_nicely(conn);
}
+ /* Set always-secure search path, so malicous users can't take control. */
+ res = PQexec(conn,
+ "SELECT pg_catalog.set_config('search_path', '', false)");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+ PQclear(res);
+ exit_nicely(conn);
+ }
+ PQclear(res);
+
res = PQexec(conn, "begin");
PQclear(res);
printf("importing file \"%s\" ...\n", in_filename);