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
appearing in <varname>search_path</varname> were resolved.
</para>
- <para>
- For more information on schema handling, see <xref linkend="ddl-schemas"/>.
- </para>
</listitem>
</varlistentry>
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>
<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>
<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
<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
<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>
<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%';
<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
------------+------------
byteaout | byteaout
(12 rows)
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
byteaout | byteaout
(12 rows)
-SELECT dblink_connect('myconn', 'dbname=regression');
+SELECT dblink_connect('myconn', 'dbname=regression options=-csearch_path=');
dblink_connect
----------------
OK
<title>Examples</title>
<screen>
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
<title>Examples</title>
<screen>
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
<title>Examples</title>
<screen>
-SELECT dblink_connect('dbname=postgres');
+SELECT dblink_connect('dbname=postgres options=-csearch_path=');
dblink_connect
----------------
OK
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>
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>
<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">
<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>
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>
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;
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;
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();
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;
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;
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;
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;
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);
TestCpp::TestCpp()
{
EXEC SQL CONNECT TO testdb1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}
void Test::test()
db_connect()
{
EXEC SQL CONNECT TO testdb1;
+ EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}
void
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;
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 */
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;
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;
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>
-- 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>
# pair extension
comment = 'A key/value pair data type'
default_version = '1.0'
-relocatable = true
+relocatable = false
</programlisting>
</para>
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
{
mydata *data;
PGresult *res;
- PGconn *conn = PQconnectdb("dbname = postgres");
+ PGconn *conn =
+ PQconnectdb("dbname=postgres options=-csearch_path=");
if (PQstatus(conn) != CONNECTION_OK)
{
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
PQclear(res);
exit_nicely(conn);
}
-
- /*
- * Should PQclear PGresult whenever it is no longer needed to avoid memory
- * leaks
- */
PQclear(res);
/*
* 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
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.
*/
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. */
* 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');
*
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.
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);
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>
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
</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>
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
PQclear(res);
exit_nicely(conn);
}
-
- /*
- * Should PQclear PGresult whenever it is no longer needed to avoid memory
- * leaks
- */
PQclear(res);
/*
* 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
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.
*/
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. */
+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);
* 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');
*
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.
+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');
}
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)
{
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
/* 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");
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);
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);