diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4c998fe51f..00fc364c0a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6329,6 +6329,13 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
setting, either globally or per-user.
+
+ For more information on schema handling, see
+ . In particular, the default
+ configuration is suitable only when the database has a single user or
+ a few mutually-trusting users.
+
+
The current effective value of the search path can be examined
via the SQL function
@@ -6340,9 +6347,6 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
appearing in search_path were resolved.
-
- For more information on schema handling, see .
-
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 0622227bee..b626a345f3 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -75,7 +75,7 @@ CREATE EXTENSION module_name;
choice. To do that, add SCHEMA
schema_name to the CREATE EXTENSION
command. By default, the objects will be placed in your current creation
- target schema, typically public.
+ target schema, which in turn defaults to public.
diff --git a/doc/src/sgml/dblink.sgml b/doc/src/sgml/dblink.sgml
index 4c07f886aa..87e14ea093 100644
--- a/doc/src/sgml/dblink.sgml
+++ b/doc/src/sgml/dblink.sgml
@@ -83,7 +83,7 @@ dblink_connect(text connname, text connstr) returns text
libpq-style connection info string, for example
hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres
- password=mypasswd.
+ password=mypasswd options=-csearch_path=.
For details see .
Alternatively, the name of a foreign server.
@@ -104,6 +104,17 @@ dblink_connect(text connname, text connstr) returns text
Notes
+
+ If untrusted users have access to a database that has not adopted a
+ secure schema usage pattern,
+ begin each session by removing publicly-writable schemas from
+ search_path. One could, for example,
+ add options=-csearch_path= to
+ connstr. This consideration is not specific
+ to dblink; it applies to every interface for
+ executing arbitrary SQL commands.
+
+
Only superusers may use dblink_connect to create
non-password-authenticated connections. If non-superusers need this
@@ -121,13 +132,13 @@ dblink_connect(text connname, text connstr) returns text
Examples
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
(1 row)
-SELECT dblink_connect('myconn', 'dbname=postgres');
+SELECT dblink_connect('myconn', 'dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
@@ -416,7 +427,8 @@ dblink(text sql [, bool fail_on_error]) returns setof record
SELECT *
- FROM dblink('dbname=mydb', 'select proname, prosrc from pg_proc')
+ FROM dblink('dbname=mydb options=-csearch_path=',
+ 'select proname, prosrc from pg_proc')
AS t1(proname name, prosrc text)
WHERE proname LIKE 'bytea%';
@@ -450,7 +462,8 @@ SELECT *
CREATE VIEW myremote_pg_proc AS
SELECT *
- FROM dblink('dbname=postgres', 'select proname, prosrc from pg_proc')
+ FROM dblink('dbname=postgres options=-csearch_path=',
+ 'select proname, prosrc from pg_proc')
AS t1(proname name, prosrc text);
SELECT * FROM myremote_pg_proc WHERE proname LIKE 'bytea%';
@@ -461,7 +474,8 @@ SELECT * FROM myremote_pg_proc WHERE proname LIKE 'bytea%';
Examples
-SELECT * FROM dblink('dbname=postgres', 'select proname, prosrc from pg_proc')
+SELECT * FROM dblink('dbname=postgres options=-csearch_path=',
+ 'select proname, prosrc from pg_proc')
AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
proname | prosrc
------------+------------
@@ -479,7 +493,7 @@ SELECT * FROM dblink('dbname=postgres', 'select proname, prosrc from pg_proc')
byteaout | byteaout
(12 rows)
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
@@ -503,7 +517,7 @@ SELECT * FROM dblink('select proname, prosrc from pg_proc')
byteaout | byteaout
(12 rows)
-SELECT dblink_connect('myconn', 'dbname=regression');
+SELECT dblink_connect('myconn', 'dbname=regression options=-csearch_path=');
dblink_connect
----------------
OK
@@ -778,7 +792,7 @@ dblink_open(text connname, text cursorname, text sql [, bool fail_on_error]) ret
Examples
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
@@ -899,7 +913,7 @@ dblink_fetch(text connname, text cursorname, int howmany [, bool fail_on_error])
Examples
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
@@ -1036,7 +1050,7 @@ dblink_close(text connname, text cursorname [, bool fail_on_error]) returns text
Examples
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 15a9285136..2b879ead4b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2172,6 +2172,20 @@ CREATE TABLE public.products ( ... );
in other schemas in the database.
+
+ The ability to create like-named objects in different schemas complicates
+ writing a query that references precisely the same objects every time. It
+ also opens up the potential for users to change the behavior of other
+ users' queries, maliciously or accidentally. Due to the prevalence of
+ unqualified names in queries and their use
+ in PostgreSQL internals, adding a schema
+ to search_path effectively trusts all users having
+ CREATE privilege on that schema. When you run an
+ ordinary query, a malicious user able to create objects in a schema of
+ your search path can take control and execute arbitrary SQL functions as
+ though you executed them.
+
+
schemacurrent
@@ -2288,8 +2302,9 @@ SELECT 3 OPERATOR(pg_catalog.+) 4;
the schema
public. This allows all users that are able to
connect to a given database to create objects in its
- public schema. If you do
- not want to allow that, you can revoke that privilege:
+ public schema.
+ Some usage patterns call for
+ revoking that privilege:
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
@@ -2339,50 +2354,75 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
Usage Patterns
- Schemas can be used to organize your data in many ways. There are
- a few usage patterns that are recommended and are easily supported by
- the default configuration:
+ Schemas can be used to organize your data in many ways. There are a few
+ usage patterns easily supported by the default configuration, only one of
+ which suffices when database users mistrust other database users:
+
- If you do not create any schemas then all users access the
- public schema implicitly. This simulates the situation where
- schemas are not available at all. This setup is mainly
- recommended when there is only a single user or a few cooperating
- users in a database. This setup also allows smooth transition
- from the non-schema-aware world.
-
-
-
-
-
- You can create a schema for each user with the same name as
- that user. Recall that the default search path starts with
- $user, which resolves to the user name.
- Therefore, if each user has a separate schema, they access their
+ Constrain ordinary users to user-private schemas. To implement this,
+ issue REVOKE CREATE ON SCHEMA public FROM PUBLIC,
+ and create a schema for each user with the same name as that user. If
+ affected users had logged in before this, consider auditing the public
+ schema for objects named like objects in
+ schema pg_catalog. Recall that the default search
+ path starts with $user, which resolves to the user
+ name. Therefore, if each user has a separate schema, they access their
own schemas by default.
+
+
- If you use this setup then you might also want to revoke access
- to the public schema (or drop it altogether), so users are
- truly constrained to their own schemas.
+ Remove the public schema from each user's default search path
+ using ALTER ROLE user SET
+ search_path = "$user". Everyone retains the ability to
+ create objects in the public schema, but only qualified names will
+ choose those objects. A user holding the CREATEROLE
+ privilege can undo this setting and issue arbitrary queries under the
+ identity of users relying on the setting. If you
+ grant CREATEROLE to users not warranting this
+ almost-superuser ability, use the first pattern instead.
- To install shared applications (tables to be used by everyone,
- additional functions provided by third parties, etc.), put them
- into separate schemas. Remember to grant appropriate
- privileges to allow the other users to access them. Users can
- then refer to these additional objects by qualifying the names
- with a schema name, or they can put the additional schemas into
- their search path, as they choose.
+ Remove the public schema from search_path in
+ postgresql.conf.
+ The ensuing user experience matches the previous pattern. In addition
+ to that pattern's implications for CREATEROLE, this
+ trusts database owners the same way. If you assign
+ the CREATEROLE
+ privilege, CREATEDB privilege or individual database
+ ownership to users not warranting almost-superuser access, use the
+ first pattern instead.
+
+
+
+
+
+ Keep the default. All users access the public schema implicitly. This
+ simulates the situation where schemas are not available at all, giving
+ a smooth transition from the non-schema-aware world. However, any user
+ can issue arbitrary queries under the identity of any user not electing
+ to protect itself individually. This pattern is acceptable only when
+ the database has a single user or a few mutually-trusting users.
+
+
+ For any pattern, to install shared applications (tables to be used by
+ everyone, additional functions provided by third parties, etc.), put them
+ into separate schemas. Remember to grant appropriate privileges to allow
+ the other users to access them. Users can then refer to these additional
+ objects by qualifying the names with a schema name, or they can put the
+ additional schemas into their search path, as they choose.
+
@@ -2405,7 +2445,7 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
Also, there is no concept of a public schema in the
SQL standard. For maximum conformance to the standard, you should
- not use (perhaps even remove) the public schema.
+ not use the public schema.
diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 5a8d1f1b95..98b6840520 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -186,6 +186,18 @@ EXEC SQL CONNECT TO targetAS
chapter).
+
+ If untrusted users have access to a database that has not adopted a
+ secure schema usage pattern,
+ begin each session by removing publicly-writable schemas
+ from search_path. For example,
+ add options=-csearch_path=
+ to options, or
+ issue EXEC SQL SELECT pg_catalog.set_config('search_path', '',
+ false); after connecting. This consideration is not specific to
+ ECPG; it applies to every interface for executing arbitrary SQL commands.
+
+
Here are some examples of CONNECT statements:
@@ -266,8 +278,11 @@ int
main()
{
EXEC SQL CONNECT TO testdb1 AS con1 USER testuser;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL CONNECT TO testdb2 AS con2 USER testuser;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL CONNECT TO testdb3 AS con3 USER testuser;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
/* This query would be executed in the last opened database "testdb3". */
EXEC SQL SELECT current_database() INTO :dbname;
@@ -1093,6 +1108,7 @@ EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
in = PGTYPESinterval_new();
EXEC SQL SELECT '1 min'::interval INTO :in;
@@ -1147,6 +1163,7 @@ EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
num = PGTYPESnumeric_new();
dec = PGTYPESdecimal_new();
@@ -1221,6 +1238,7 @@ EXEC SQL END DECLARE SECTION;
memset(dbid, 0, sizeof(int) * 8);
EXEC SQL CONNECT TO testdb;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
/* Retrieve multiple rows into arrays at once. */
EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;
@@ -1887,6 +1905,7 @@ char *stmt = "SELECT u.usename as dbaname, d.datname "
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb AS con1 USER testuser;
+EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL PREPARE stmt1 FROM :stmt;
@@ -4317,6 +4336,7 @@ main(void)
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb AS con1 USER testuser;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL PREPARE stmt1 FROM :query;
EXEC SQL DECLARE cur1 CURSOR FOR stmt1;
@@ -4478,6 +4498,7 @@ main(void)
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO uptimedb AS con1 USER uptime;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL PREPARE stmt1 FROM :query;
EXEC SQL DECLARE cur1 CURSOR FOR stmt1;
@@ -5909,6 +5930,7 @@ main(void)
memset(buf, 1, buflen);
EXEC SQL CONNECT TO testdb AS con1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
conn = ECPGget_PGconn("con1");
printf("conn = %p\n", conn);
@@ -6038,6 +6060,7 @@ class TestCpp
TestCpp::TestCpp()
{
EXEC SQL CONNECT TO testdb1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}
void Test::test()
@@ -6117,6 +6140,7 @@ void
db_connect()
{
EXEC SQL CONNECT TO testdb1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}
void
@@ -6510,12 +6534,14 @@ EXEC SQL END DECLARE SECTION;
ECPGdebug(1, stderr);
EXEC SQL CONNECT TO :dbname USER :user;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL SELECT version() INTO :ver;
EXEC SQL DISCONNECT;
printf("version: %s\n", ver);
EXEC SQL CONNECT TO :connection USER :user;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL SELECT version() INTO :ver;
EXEC SQL DISCONNECT;
@@ -7116,6 +7142,7 @@ EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb AS con1 USER testuser;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL ALLOCATE DESCRIPTOR d;
/* Declare, open a cursor, and assign a descriptor to the cursor */
@@ -7673,6 +7700,7 @@ EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb AS con1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL SELECT current_database(), 256 INTO :t:t_ind LIMIT 1;
@@ -7829,6 +7857,7 @@ int
main(void)
{
EXEC SQL CONNECT TO testdb AS con1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL ALLOCATE DESCRIPTOR d;
EXEC SQL DECLARE cur CURSOR FOR SELECT current_database(), 'hoge', 256;
EXEC SQL OPEN cur;
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 5f1bb70e97..6c043cdd02 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -430,6 +430,32 @@
dropping the whole extension.
+
+ Defining Extension Objects
+
+
+
+ Widely-distributed extensions should assume little about the database
+ they occupy. In particular, unless you issued SET search_path =
+ pg_temp, assume each unqualified name could resolve to an
+ object that a malicious user has defined. Beware of constructs that
+ depend on search_path implicitly: IN
+ and CASE expression WHEN
+ always select an operator using the search path. In their place, use
+ OPERATOR(schema.=) ANY
+ and CASE WHEN expression.
+
+
+
+
Extension Files
@@ -984,24 +1010,24 @@ SELECT * FROM pg_extension_update_paths('extension_name (LEFTARG = pg_catalog.text,
+ RIGHTARG = pg_catalog.text, PROCEDURE = pair);
-CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
-RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+-- "SET search_path" is easy to get right, but qualified names perform better.
+CREATE OR REPLACE FUNCTION lower(pair)
+RETURNS pair LANGUAGE SQL
+AS 'SELECT ROW(lower($1.k), lower($1.v))::@extschema@.pair;'
+SET search_path = pg_temp;
-CREATE OR REPLACE FUNCTION pair(text, text)
-RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
-
-CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = anyelement, PROCEDURE = pair);
-CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = text, PROCEDURE = pair);
-CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = anyelement, PROCEDURE = pair);
-CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);
+CREATE OR REPLACE FUNCTION pair_concat(pair, pair)
+RETURNS pair LANGUAGE SQL
+AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k,
+ $1.v OPERATOR(pg_catalog.||) $2.v)::@extschema@.pair;';
]]>
@@ -1013,7 +1039,7 @@ CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);
# pair extension
comment = 'A key/value pair data type'
default_version = '1.0'
-relocatable = true
+relocatable = false
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f327d4b5b5..2a8e1f2e07 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -65,6 +65,22 @@
the return value for a successful connection before queries are sent
via the connection object.
+
+
+ If untrusted users have access to a database that has not adopted a
+ secure schema usage pattern,
+ begin each session by removing publicly-writable schemas from
+ search_path. One can set parameter key
+ word options to
+ value -csearch_path=. Alternately, one can
+ issue PQexec(conn, "SELECT
+ pg_catalog.set_config('search_path', '', false)") after
+ connecting. This consideration is not specific
+ to libpq; it applies to every interface for
+ executing arbitrary SQL commands.
+
+
+
On Unix, forking a process with open libpq connections can lead to
@@ -6878,7 +6894,8 @@ main(void)
{
mydata *data;
PGresult *res;
- PGconn *conn = PQconnectdb("dbname = postgres");
+ PGconn *conn =
+ PQconnectdb("dbname=postgres options=-csearch_path=");
if (PQstatus(conn) != CONNECTION_OK)
{
@@ -8305,6 +8322,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
@@ -8320,11 +8353,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);
/*
@@ -8400,16 +8428,16 @@ main(int argc, char **argv)
* 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
@@ -8464,6 +8492,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.
*/
@@ -8474,11 +8518,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. */
@@ -8545,8 +8584,9 @@ main(int argc, char **argv)
* 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');
*
@@ -8678,6 +8718,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/doc/src/sgml/lobj.sgml b/doc/src/sgml/lobj.sgml
index 6b5aaebbbc..771795ae66 100644
--- a/doc/src/sgml/lobj.sgml
+++ b/doc/src/sgml/lobj.sgml
@@ -933,6 +933,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/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 3dd492cec1..59b573b501 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -1688,5 +1688,16 @@ statement latencies in milliseconds:
database server.
+
+ Security
+
+
+ If untrusted users have access to a database that has not adopted a
+ secure schema usage pattern,
+ do not run pgbench in that
+ database. pgbench uses unqualified names and
+ does not manipulate the search path.
+
+
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 8bd9b9387e..bfdf859731 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -735,6 +735,18 @@ testdb=>
of the command are displayed on the screen.
+
+ If untrusted users have access to a database that has not adopted a
+ secure schema usage pattern,
+ begin your session by removing publicly-writable schemas
+ from search_path. One can
+ add options=-csearch_path= to the connection string or
+ issue SELECT pg_catalog.set_config('search_path', '',
+ false) before other SQL commands. This consideration is not
+ specific to psql; it applies to every interface
+ for executing arbitrary SQL commands.
+
+
Whenever a command is executed, psql also polls
for asynchronous notification events generated by
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index ae15efed95..94fd4ebf58 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -571,14 +571,17 @@ GRANT pg_signal_backend TO admin_user;
- Function and Trigger Security
+ Function Security
- Functions and triggers allow users to insert code into the backend
- server that other users might execute unintentionally. Hence, both
- mechanisms permit users to Trojan horse
- others with relative ease. The only real protection is tight
- control over who can define functions.
+ Functions, triggers and row-level security policies allow users to insert
+ code into the backend server that other users might execute
+ unintentionally. Hence, these mechanisms permit users to Trojan
+ horse others with relative ease. The strongest protection is tight
+ control over who can define objects. Where that is infeasible, write
+ queries referring only to objects having trusted owners. Remove
+ from search_path the public schema and any other schemas
+ that permit untrusted users to create objects.
diff --git a/src/test/examples/testlibpq.c b/src/test/examples/testlibpq.c
index 4d9af82dd1..92a05e5309 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 07c6317a21..76787fe010 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 1686c3ed0d..e8173e4293 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 e11e0567ca..00e62b43d2 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 9d9e217e5d..2213306509 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 0ec04313c0..a20f6249b4 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 7afe24714a..be5c72b9a6 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 bb188cc3a1..39ba009c52 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);