]> granicus.if.org Git - postgresql/commitdiff
Document security implications of search_path and the public schema.
authorNoah Misch <noah@leadboat.com>
Mon, 26 Feb 2018 15:39:44 +0000 (07:39 -0800)
committerNoah Misch <noah@leadboat.com>
Mon, 26 Feb 2018 15:39:44 +0000 (07:39 -0800)
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

19 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/contrib.sgml
doc/src/sgml/dblink.sgml
doc/src/sgml/ddl.sgml
doc/src/sgml/ecpg.sgml
doc/src/sgml/extend.sgml
doc/src/sgml/libpq.sgml
doc/src/sgml/lobj.sgml
doc/src/sgml/ref/pgbench.sgml
doc/src/sgml/ref/psql-ref.sgml
doc/src/sgml/user-manag.sgml
src/test/examples/testlibpq.c
src/test/examples/testlibpq2.c
src/test/examples/testlibpq2.sql
src/test/examples/testlibpq3.c
src/test/examples/testlibpq3.sql
src/test/examples/testlibpq4.c
src/test/examples/testlo.c
src/test/examples/testlo64.c

index 4c998fe51f92cf5046d53883ca9fa0d87b1d5150..00fc364c0ab232b1a45e526021b9b8851a4f0b34 100644 (file)
@@ -6329,6 +6329,13 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         setting, either globally or per-user.
        </para>
 
+       <para>
+        For more information on schema handling, see
+        <xref linkend="ddl-schemas"/>.  In particular, the default
+        configuration is suitable only when the database has a single user or
+        a few mutually-trusting users.
+       </para>
+
        <para>
         The current effective value of the search path can be examined
         via the <acronym>SQL</acronym> function
@@ -6340,9 +6347,6 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         appearing in <varname>search_path</varname> were resolved.
        </para>
 
-       <para>
-        For more information on schema handling, see <xref linkend="ddl-schemas"/>.
-       </para>
       </listitem>
      </varlistentry>
 
index 0622227bee02dbafa9d1cd2bac68d90449742e47..b626a345f3eec49a67a7f97b089a849ece688a03 100644 (file)
@@ -75,7 +75,7 @@ CREATE EXTENSION <replaceable>module_name</replaceable>;
   choice.  To do that, add <literal>SCHEMA
   <replaceable>schema_name</replaceable></literal> to the <command>CREATE EXTENSION</command>
   command.  By default, the objects will be placed in your current creation
-  target schema, typically <literal>public</literal>.
+  target schema, which in turn defaults to <literal>public</literal>.
  </para>
 
  <para>
index 4c07f886aa79a3ee1e734fdebff9a780736afece..87e14ea093def7e481f79ec52e30d8b98616340f 100644 (file)
@@ -83,7 +83,7 @@ dblink_connect(text connname, text connstr) returns text
      <listitem>
       <para><application>libpq</application>-style connection info string, for example
        <literal>hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres
-       password=mypasswd</literal>.
+       password=mypasswd options=-csearch_path=</literal>.
        For details see <xref linkend="libpq-connstring"/>.
        Alternatively, the name of a foreign server.
       </para>
@@ -104,6 +104,17 @@ dblink_connect(text connname, text connstr) returns text
   <refsect1>
    <title>Notes</title>
 
+   <para>
+    If untrusted users have access to a database that has not adopted a
+    <link linkend="ddl-schemas-patterns">secure schema usage pattern</link>,
+    begin each session by removing publicly-writable schemas from
+    <varname>search_path</varname>.  One could, for example,
+    add <literal>options=-csearch_path=</literal> to
+    <parameter>connstr</parameter>.  This consideration is not specific
+    to <filename>dblink</filename>; it applies to every interface for
+    executing arbitrary SQL commands.
+   </para>
+
    <para>
     Only superusers may use <function>dblink_connect</function> to create
     non-password-authenticated connections.  If non-superusers need this
@@ -121,13 +132,13 @@ dblink_connect(text connname, text connstr) returns text
    <title>Examples</title>
 
 <screen>
-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
 
 <programlisting>
 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%';
 </programlisting>
@@ -450,7 +462,8 @@ SELECT *
 <programlisting>
 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%';
    <title>Examples</title>
 
 <screen>
-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
    <title>Examples</title>
 
 <screen>
-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])
    <title>Examples</title>
 
 <screen>
-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
    <title>Examples</title>
 
 <screen>
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
  dblink_connect
 ----------------
  OK
index 15a9285136debefebfcf548e7ede36d1ae638bb0..2b879ead4b2c672a29e8155c9c38ca1c0442e414 100644 (file)
@@ -2172,6 +2172,20 @@ CREATE TABLE public.products ( ... );
     in other schemas in the database.
    </para>
 
+  <para>
+    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 <productname>PostgreSQL</productname> internals, adding a schema
+    to <varname>search_path</varname> effectively trusts all users having
+    <literal>CREATE</literal> 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.
+   </para>
+
    <indexterm>
     <primary>schema</primary>
     <secondary>current</secondary>
@@ -2288,8 +2302,9 @@ SELECT 3 OPERATOR(pg_catalog.+) 4;
     the schema
     <literal>public</literal>.  This allows all users that are able to
     connect to a given database to create objects in its
-    <literal>public</literal> schema.  If you do
-    not want to allow that, you can revoke that privilege:
+    <literal>public</literal> schema.
+    Some <link linkend="ddl-schemas-patterns">usage patterns</link> call for
+    revoking that privilege:
 <programlisting>
 REVOKE CREATE ON SCHEMA public FROM PUBLIC;
 </programlisting>
@@ -2339,50 +2354,75 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
    <title>Usage Patterns</title>
 
    <para>
-    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:
     <itemizedlist>
      <listitem>
+      <!-- "DROP SCHEMA public" is inferior to this REVOKE, because pg_dump
+           doesn't preserve that DROP. -->
       <para>
-       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.
+       Constrain ordinary users to user-private schemas.  To implement this,
+       issue <literal>REVOKE CREATE ON SCHEMA public FROM PUBLIC</literal>,
+       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 <literal>pg_catalog</literal>.  Recall that the default search
+       path starts with <literal>$user</literal>, which resolves to the user
+       name.  Therefore, if each user has a separate schema, they access their
+       own schemas by default.
       </para>
      </listitem>
 
      <listitem>
       <para>
-       You can create a schema for each user with the same name as
-       that user.  Recall that the default search path starts with
-       <literal>$user</literal>, which resolves to the user name.
-       Therefore, if each user has a separate schema, they access their
-       own schemas by default.
+       Remove the public schema from each user's default search path
+       using <literal>ALTER ROLE <replaceable>user</replaceable> SET
+       search_path = "$user"</literal>.  Everyone retains the ability to
+       create objects in the public schema, but only qualified names will
+       choose those objects.  A user holding the <literal>CREATEROLE</literal>
+       privilege can undo this setting and issue arbitrary queries under the
+       identity of users relying on the setting.  If you
+       grant <literal>CREATEROLE</literal> to users not warranting this
+       almost-superuser ability, use the first pattern instead.
       </para>
+     </listitem>
 
+     <listitem>
       <para>
-       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 <varname>search_path</varname> in
+       <link linkend="config-setting-configuration-file"><filename>postgresql.conf</filename></link>.
+       The ensuing user experience matches the previous pattern.  In addition
+       to that pattern's implications for <literal>CREATEROLE</literal>, this
+       trusts database owners the same way.  If you assign
+       the <literal>CREATEROLE</literal>
+       privilege, <literal>CREATEDB</literal> privilege or individual database
+       ownership to users not warranting almost-superuser access, use the
+       first pattern instead.
       </para>
      </listitem>
 
      <listitem>
       <para>
-       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.
+       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.
       </para>
      </listitem>
     </itemizedlist>
    </para>
+
+   <para>
+    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.
+   </para>
   </sect2>
 
   <sect2 id="ddl-schemas-portability">
@@ -2405,7 +2445,7 @@ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
    <para>
     Also, there is no concept of a <literal>public</literal> schema in the
     SQL standard.  For maximum conformance to the standard, you should
-    not use (perhaps even remove) the <literal>public</literal> schema.
+    not use the <literal>public</literal> schema.
    </para>
 
    <para>
index 5a8d1f1b95ba152790347b5e5faca9b9a161f42d..98b68405207ba9f625769570f2b6ed3b331b0ad1 100644 (file)
@@ -186,6 +186,18 @@ EXEC SQL CONNECT TO <replaceable>target</replaceable> <optional>AS <replaceable>
    chapter).
   </para>
 
+  <para>
+   If untrusted users have access to a database that has not adopted a
+   <link linkend="ddl-schemas-patterns">secure schema usage pattern</link>,
+   begin each session by removing publicly-writable schemas
+   from <varname>search_path</varname>.  For example,
+   add <literal>options=-csearch_path=</literal>
+   to <literal><replaceable>options</replaceable></literal>, or
+   issue <literal>EXEC SQL SELECT pg_catalog.set_config('search_path', '',
+   false);</literal> after connecting.  This consideration is not specific to
+   ECPG; it applies to every interface for executing arbitrary SQL commands.
+  </para>
+
   <para>
    Here are some examples of <command>CONNECT</command> statements:
 <programlisting>
@@ -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;
index 5f1bb70e97ab05e2e3d74a86c6c68bd0c94b8119..6c043cdd02aeb709a546203a5702e7caffe08049 100644 (file)
     dropping the whole extension.
    </para>
 
+   <sect2 id="extend-extensions-style">
+    <title>Defining Extension Objects</title>
+
+    <!-- XXX It's not enough to use qualified names, because one might write a
+         qualified name to an object that itself uses unqualified names.  Many
+         information_schema functions have that defect, for example.  However,
+         that's a defect in the referenced object, and relatively few queries
+         will be affected.  Also, we direct applications to secure search_path
+         when connecting to an untrusted database; if applications do that,
+         they are immune to known attacks even if some extension refers to a
+         defective object.  Therefore, guide extension authors as though core
+         PostgreSQL contained no such defect. -->
+    <para>
+     Widely-distributed extensions should assume little about the database
+     they occupy.  In particular, unless you issued <literal>SET search_path =
+     pg_temp</literal>, assume each unqualified name could resolve to an
+     object that a malicious user has defined.  Beware of constructs that
+     depend on <varname>search_path</varname> implicitly: <token>IN</token>
+     and <literal>CASE <replaceable>expression</replaceable> WHEN</literal>
+     always select an operator using the search path.  In their place, use
+     <literal>OPERATOR(<replaceable>schema</replaceable>.=) ANY</literal>
+     and <literal>CASE WHEN <replaceable>expression</replaceable></literal>.
+    </para>
+
+   </sect2>
+
    <sect2>
     <title>Extension Files</title>
 
@@ -984,24 +1010,24 @@ SELECT * FROM pg_extension_update_paths('<replaceable>extension_name</replaceabl
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION pair" to load this file. \quit
 
-CREATE TYPE pair AS ( k text, v text );
-
-CREATE OR REPLACE FUNCTION pair(anyelement, text)
-RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+CREATE TYPE pair AS ( k pg_catalog.text, v pg_catalog.text );
 
-CREATE OR REPLACE FUNCTION pair(text, anyelement)
-RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+CREATE OR REPLACE FUNCTION pair(pg_catalog.text, pg_catalog.text)
+RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@extschema@.pair;';
 
-CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
-RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+CREATE OPERATOR ~> (LEFTARG = pg_catalog.text,
+                    RIGHTARG = pg_catalog.text, PROCEDURE = pair);
 
-CREATE OR REPLACE FUNCTION pair(text, text)
-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 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;';
 ]]>
 </programlisting>
     </para>
@@ -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
 </programlisting>
     </para>
 
index f327d4b5b5b4b383e7fd7f8e50bd727597713ab3..2a8e1f2e07d99665c457baaa5bf2342c7c2768af 100644 (file)
    the return value for a successful connection before queries are sent
    via the connection object.
 
+   <warning>
+    <para>
+     If untrusted users have access to a database that has not adopted a
+     <link linkend="ddl-schemas-patterns">secure schema usage pattern</link>,
+     begin each session by removing publicly-writable schemas from
+     <varname>search_path</varname>.  One can set parameter key
+     word <literal>options</literal> to
+     value <literal>-csearch_path=</literal>.  Alternately, one can
+     issue <literal>PQexec(<replaceable>conn</replaceable>, "SELECT
+     pg_catalog.set_config('search_path', '', false)")</literal> after
+     connecting.  This consideration is not specific
+     to <application>libpq</application>; it applies to every interface for
+     executing arbitrary SQL commands.
+    </para>
+   </warning>
+
    <warning>
     <para>
      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.
index 6b5aaebbbc4ec4ef3680a111eb1e0f4777357b9e..771795ae668cf43d9b4df78d370a06769a7516a6 100644 (file)
@@ -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);
index 3dd492cec11a47ab04805d7c4cc64c63c45339e7..59b573b50116fd603da43dafd48490ec4b9b5390 100644 (file)
@@ -1688,5 +1688,16 @@ statement latencies in milliseconds:
    database server.
   </para>
  </refsect2>
+ <refsect2>
+  <title>Security</title>
+
+  <para>
+    If untrusted users have access to a database that has not adopted a
+    <link linkend="ddl-schemas-patterns">secure schema usage pattern</link>,
+    do not run <application>pgbench</application> in that
+    database.  <application>pgbench</application> uses unqualified names and
+    does not manipulate the search path.
+  </para>
+ </refsect2>
  </refsect1>
 </refentry>
index 8bd9b9387ec4fcf0c7654741b2e6c1e779f1076a..bfdf859731145e58de9956dfb56ec881bd9a473a 100644 (file)
@@ -735,6 +735,18 @@ testdb=&gt;
     of the command are displayed on the screen.
     </para>
 
+    <para>
+    If untrusted users have access to a database that has not adopted a
+    <link linkend="ddl-schemas-patterns">secure schema usage pattern</link>,
+    begin your session by removing publicly-writable schemas
+    from <varname>search_path</varname>.  One can
+    add <literal>options=-csearch_path=</literal> to the connection string or
+    issue <literal>SELECT pg_catalog.set_config('search_path', '',
+    false)</literal> before other SQL commands.  This consideration is not
+    specific to <application>psql</application>; it applies to every interface
+    for executing arbitrary SQL commands.
+    </para>
+
     <para>
     Whenever a command is executed, <application>psql</application> also polls
     for asynchronous notification events generated by
index ae15efed957514fa413c707cc02c36f396b4ca5f..94fd4ebf5821e68f31c08714b612999adf73b18f 100644 (file)
@@ -571,14 +571,17 @@ GRANT pg_signal_backend TO admin_user;
  </sect1>
 
  <sect1 id="perm-functions">
-  <title>Function and Trigger Security</title>
+  <title>Function Security</title>
 
   <para>
-   Functions and triggers allow users to insert code into the backend
-   server that other users might execute unintentionally. Hence, both
-   mechanisms permit users to <quote>Trojan horse</quote>
-   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 <quote>Trojan
+   horse</quote> 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 <varname>search_path</varname> the public schema and any other schemas
+   that permit untrusted users to create objects.
   </para>
 
   <para>
index 4d9af82dd18d53bf088960f2708e85c5e8c2de3a..92a05e53093ee88c819610f727e50542aa91d8c7 100644 (file)
@@ -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);
 
        /*
index 07c6317a2123dd3b7808bef9895bd35bb9a9cf0e..76787fe010b8ed942f5321c2583a04326aae43f9 100644 (file)
  * 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. */
index 1686c3ed0dfff0c19f21ce599405f6ba6fabb039..e8173e4293b55840384b5d67a9d47a87f61f19ef 100644 (file)
@@ -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);
index e11e0567cac815239b0a5e34960cf5b81b2a9e2d..00e62b43d2874697fb693b1539f93df1d382499c 100644 (file)
@@ -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.
index 9d9e217e5ddf1289a399cbeb1b7d3727d32eff2f..221330650990a1a1ecc68adffcd66cc682c9d4aa 100644 (file)
@@ -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');
index 0ec04313c0cc03fcd8a147dc72c79ea7ac4fa479..a20f6249b4eeb3dda9ebd17b35a98d88c6051e57 100644 (file)
@@ -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");
index 7afe24714a26e52e5674c172c01ca8fbdbfc0176..be5c72b9a67ef82a2462f853f68b5369f7d87913 100644 (file)
@@ -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);
index bb188cc3a18e2d5c7f8461c8db5e7d86424a3937..39ba009c52355f3f212c43df36592af79f769e09 100644 (file)
@@ -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);