OSDN Git Service

Support explicit placement of the temporary-table schema within search_path.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 20 Apr 2007 02:37:38 +0000 (02:37 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 20 Apr 2007 02:37:38 +0000 (02:37 +0000)
This is needed to allow a security-definer function to set a truly secure
value of search_path.  Without it, a malicious user can use temporary objects
to execute code with the privileges of the security-definer function.  Even
pushing the temp schema to the back of the search path is not quite good
enough, because a function or operator at the back of the path might still
capture control from one nearer the front due to having a more exact datatype
match.  Hence, disable searching the temp schema altogether for functions and
operators.

Security: CVE-2007-2138

doc/src/sgml/config.sgml
doc/src/sgml/ref/create_function.sgml
doc/src/sgml/release.sgml
src/backend/catalog/aclchk.c
src/backend/catalog/namespace.c
src/test/regress/expected/temp.out
src/test/regress/sql/temp.sql

index c5670dd..4d4200e 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.121 2007/04/18 16:44:17 alvherre Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.122 2007/04/20 02:37:37 tgl Exp $ -->
 
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -3405,9 +3405,17 @@ SELECT * FROM parent WHERE key = 2400;
         mentioned in the path then it will be searched in the specified
         order.  If <literal>pg_catalog</> is not in the path then it will
         be searched <emphasis>before</> searching any of the path items.
-        It should also be noted that the temporary-table schema,
-        <literal>pg_temp_<replaceable>nnn</></>, is implicitly searched before any of
-        these.
+       </para>
+
+       <para>
+        Likewise, the current session's temporary-table schema,
+        <literal>pg_temp_<replaceable>nnn</></>, is always searched if it
+        exists.  It can be explicitly listed in the path by using the
+        alias <literal>pg_temp</>.  If it is not listed in the path then
+        it is searched first (before even <literal>pg_catalog</>).  However,
+        the temporary schema is only searched for relation (table, view,
+        sequence, etc) and data type names.  It will never be searched for
+        function or operator names.
        </para>
 
        <para>
index f4811f0..a0e7c44 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.73 2007/02/01 19:10:24 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.74 2007/04/20 02:37:37 tgl Exp $
 -->
 
 <refentry id="SQL-CREATEFUNCTION">
@@ -508,6 +508,54 @@ SELECT * FROM dup(42);
   </para>
  </refsect1>
 
+ <refsect1 id="sql-createfunction-security">
+  <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
+
+   <para>
+    Because a <literal>SECURITY DEFINER</literal> function is executed
+    with the privileges of the user that created it, care is needed to
+    ensure that the function cannot be misused.  For security,
+    <xref linkend="guc-search-path"> should be set to exclude any schemas
+    writable by untrusted users.  This prevents
+    malicious users from creating objects that mask objects used by the
+    function.  Particularly important is in this regard is the
+    temporary-table schema, which is searched first by default, and
+    is normally writable by anyone.  A secure arrangement can be had
+    by forcing the temporary schema to be searched last.  To do this,
+    write <literal>pg_temp</> as the last entry in <varname>search_path</>.
+    This function illustrates safe usage:
+   </para>
+
+<programlisting>
+CREATE FUNCTION check_password(uname TEXT, pass TEXT)
+RETURNS BOOLEAN AS $$
+DECLARE passed BOOLEAN;
+        old_path TEXT;
+BEGIN
+        -- Save old search_path; notice we must qualify current_setting
+        -- to ensure we invoke the right function
+        old_path := pg_catalog.current_setting('search_path');
+
+        -- Set a secure search_path: trusted schemas, then 'pg_temp'.
+        -- We set is_local = true so that the old value will be restored
+        -- in event of an error before we reach the function end.
+        PERFORM pg_catalog.set_config('search_path', 'admin, pg_temp', true);
+
+        -- Do whatever secure work we came for.
+        SELECT  (pwd = $2) INTO passed
+        FROM    pwds
+        WHERE   username = $1;
+
+        -- Restore caller's search_path
+        PERFORM pg_catalog.set_config('search_path', old_path, true);
+
+        RETURN passed;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+</programlisting>
+
+ </refsect1>
+
  
  <refsect1 id="sql-createfunction-compat">
   <title>Compatibility</title>
index 9a2733f..fd938eb 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.500 2007/04/19 13:02:49 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.501 2007/04/20 02:37:37 tgl Exp $ -->
 <!--
 
 Typical markup:
@@ -44,7 +44,8 @@ do it for earlier branch release files.
    </note>
 
    <para>
-    This release contains fixes from 8.2.3.
+    This release contains a variety of fixes from 8.2.3,
+    including a security fix.
    </para>
 
    <sect2>
@@ -63,8 +64,24 @@ do it for earlier branch release files.
 
      <listitem>
      <para>
-      Fix <varname>shared_preload_libraries</> for Win32 by forcing reload in each backend
-      (Korry Douglas)
+      Support explicit placement of the temporary-table schema within
+      <varname>search_path</>, and disable searching it for functions
+      and operators (Tom)
+     </para>
+     <para>
+      This is needed to allow a security-definer function to set a
+      truly secure value of <varname>search_path</>.  Without it,
+      an unprivileged SQL user can use temporary objects to execute code
+      with the privileges of the security-definer function (CVE-2007-2138).
+      See <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title"> for more information.
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix <varname>shared_preload_libraries</> for Windows
+      by forcing reload in each backend (Korry Douglas)
      </para>
      </listitem>
 
@@ -77,20 +94,21 @@ do it for earlier branch release files.
 
      <listitem>
      <para>
-      <filename>/contrib/tsearch2</> fixes (Teodor)
+      <filename>/contrib/tsearch2</> crash fixes (Teodor)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Require <command>COMMIT TRANSACTION</> to be executed in the same database as
-      it was prepared (Heikki)
+      Require <command>COMMIT PREPARED</> to be executed in the same
+      database as the transaction was prepared in (Heikki)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Allow Win32 <command>pg_dump</> to do binary backups larger than two gigabytes (Magnus)
+      Allow <command>pg_dump</> to do binary backups larger than two gigabytes
+      on Windows (Magnus)
      </para>
      </listitem>
 
@@ -108,32 +126,49 @@ do it for earlier branch release files.
 
      <listitem>
      <para>
-      Improve detection of <acronym>POSIX</>-style time zone names (Tom)
+      Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
+      <command>UPDATE</> chains (Tom, Pavan Deolasee)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
+      Fix bug in domains that use array types (Tom)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix bug in domains that use array types (Tom)
+      Fix <command>pg_dump</> so it can dump a serial column's sequence
+      using <option>-t</> when not also dumping the owning table
+      (Tom)
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Planner fixes, including improving outer join and bitmap scan
+      selection logic (Tom)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix <command>pg_dump</> so it can dump a sequence using <option>-t</> when not also dumping the owning table
+      Fix possible wrong answers or crash when a PL/pgSQL function tries
+      to <literal>RETURN</> from within an <literal>EXCEPTION</> block
       (Tom)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Improve outer join and bitmap join selection logic (Tom)
+      Fix PANIC during enlargement of a hash index (Tom)
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix POSIX-style timezone specs to follow new USA DST rules (Tom)
      </para>
      </listitem>
 
@@ -3040,7 +3075,8 @@ do it for earlier branch release files.
    </note>
 
    <para>
-    This release contains fixes from 8.1.8.
+    This release contains a variety of fixes from 8.1.8,
+    including a security fix.
    </para>
 
    <sect2>
@@ -3061,39 +3097,57 @@ do it for earlier branch release files.
 
      <listitem>
      <para>
-      Fix <function>to_char()</> so it properly upper/lower cases localized day or month
-      names (Pavel Stehule)
+      Support explicit placement of the temporary-table schema within
+      <varname>search_path</>, and disable searching it for functions
+      and operators (Tom)
+     </para>
+     <para>
+      This is needed to allow a security-definer function to set a
+      truly secure value of <varname>search_path</>.  Without it,
+      an unprivileged SQL user can use temporary objects to execute code
+      with the privileges of the security-definer function (CVE-2007-2138).
+      See <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title"> for more information.
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      <filename>/contrib/tsearch2</> crash fixes (Teodor)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      <filename>/contrib/tsearch2</> fixes (Teodor)
+      Require <command>COMMIT PREPARED</> to be executed in the same
+      database as the transaction was prepared in (Heikki)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Require <command>COMMIT TRANSACTION</> to be executed in the same database as
-      it was prepared (Heikki)
+      Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
+      <command>UPDATE</> chains (Tom, Pavan Deolasee)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Improve detection of <acronym>POSIX</>-style time zone names (Tom)
+      Planner fixes, including improving outer join and bitmap scan
+      selection logic (Tom)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
+      Fix PANIC during enlargement of a hash index (bug introduced in 8.1.6)
+      (Tom)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Improve outer join and bitmap join selection logic (Tom)
+      Fix POSIX-style timezone specs to follow new USA DST rules (Tom)
      </para>
      </listitem>
 
@@ -6061,7 +6115,8 @@ psql -t -f fixseq.sql db1 | psql -e db1
    </note>
 
    <para>
-    This release contains fixes from 8.0.12.
+    This release contains a variety of fixes from 8.0.12,
+    including a security fix.
    </para>
 
    <sect2>
@@ -6082,25 +6137,43 @@ psql -t -f fixseq.sql db1 | psql -e db1
 
      <listitem>
      <para>
-      <filename>/contrib/tsearch2</> fixes (Teodor)
+      Support explicit placement of the temporary-table schema within
+      <varname>search_path</>, and disable searching it for functions
+      and operators (Tom)
+     </para>
+     <para>
+      This is needed to allow a security-definer function to set a
+      truly secure value of <varname>search_path</>.  Without it,
+      an unprivileged SQL user can use temporary objects to execute code
+      with the privileges of the security-definer function (CVE-2007-2138).
+      See <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title"> for more information.
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Improve detection of <acronym>POSIX</>-style time zone names (Tom)
+      <filename>/contrib/tsearch2</> crash fixes (Teodor)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
+      Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
+      <command>UPDATE</> chains (Tom, Pavan Deolasee)
      </para>
      </listitem>
 
      <listitem>
      <para>
-      <filename>/contrib/tsearch2</> fixes (Teodor)
+      Fix PANIC during enlargement of a hash index (bug introduced in 8.0.10)
+      (Tom)
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix POSIX-style timezone specs to follow new USA DST rules (Tom)
      </para>
      </listitem>
 
@@ -9552,7 +9625,8 @@ typedefs (Michael)</para></listitem>
    </note>
 
    <para>
-    This release contains a variety of fixes from 7.4.16.
+    This release contains fixes from 7.4.16,
+    including a security fix.
    </para>
 
    <sect2>
@@ -9573,13 +9647,37 @@ typedefs (Michael)</para></listitem>
 
      <listitem>
      <para>
-      <filename>/contrib/tsearch2</> fixes (Teodor)
+      Support explicit placement of the temporary-table schema within
+      <varname>search_path</>, and disable searching it for functions
+      and operators (Tom)
+     </para>
+     <para>
+      This is needed to allow a security-definer function to set a
+      truly secure value of <varname>search_path</>.  Without it,
+      an unprivileged SQL user can use temporary objects to execute code
+      with the privileges of the security-definer function (CVE-2007-2138).
+      See <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title"> for more information.
      </para>
      </listitem>
 
      <listitem>
      <para>
-      Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
+      <filename>/contrib/tsearch2</> crash fixes (Teodor)
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
+      <command>UPDATE</> chains (Tom, Pavan Deolasee)
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix PANIC during enlargement of a hash index (bug introduced in 7.4.15)
+      (Tom)
      </para>
      </listitem>
 
@@ -12714,7 +12812,8 @@ DROP SCHEMA information_schema CASCADE;
    </note>
 
    <para>
-    This release contains a variety of fixes from 7.3.18.
+    This release contains fixes from 7.3.18,
+    including a security fix.
    </para>
 
    <sect2>
@@ -12735,7 +12834,24 @@ DROP SCHEMA information_schema CASCADE;
 
      <listitem>
      <para>
-      Fix bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee)
+      Support explicit placement of the temporary-table schema within
+      <varname>search_path</>, and disable searching it for functions
+      and operators (Tom)
+     </para>
+     <para>
+      This is needed to allow a security-definer function to set a
+      truly secure value of <varname>search_path</>.  Without it,
+      an unprivileged SQL user can use temporary objects to execute code
+      with the privileges of the security-definer function (CVE-2007-2138).
+      See <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title"> for more information.
+     </para>
+     </listitem>
+
+     <listitem>
+     <para>
+      Fix potential-data-corruption bug in how <command>VACUUM FULL</> handles
+      <command>UPDATE</> chains (Tom, Pavan Deolasee)
      </para>
      </listitem>
 
index b837ba4..292a737 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.138 2007/03/26 16:58:38 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.139 2007/04/20 02:37:37 tgl Exp $
  *
  * NOTES
  *       See acl.h.
@@ -1833,7 +1833,7 @@ pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
         */
        if (isTempNamespace(nsp_oid))
        {
-               if (pg_database_aclcheck(MyDatabaseId, GetUserId(),
+               if (pg_database_aclcheck(MyDatabaseId, roleid,
                                                                 ACL_CREATE_TEMP) == ACLCHECK_OK)
                        return mask & ACL_ALL_RIGHTS_NAMESPACE;
                else
index ca51b99..55379b6 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.95 2007/04/12 22:34:45 neilc Exp $
+ *       $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.96 2007/04/20 02:37:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  * SQL99.  Also, this provides a way to search the system namespace first
  * without thereby making it the default creation target namespace.)
  *
+ * For security reasons, searches using the search path will ignore the temp
+ * namespace when searching for any object type other than relations and
+ * types.  (We must allow types since temp tables have rowtypes.)
+ *
  * The default creation target namespace is always the first element of the
  * explicit list.  If the explicit list is empty, there is no default target.
  *
- * In bootstrap mode, the search path is set equal to 'pg_catalog', so that
+ * The textual specification of search_path can include "$user" to refer to
+ * the namespace named the same as the current user, if any.  (This is just
+ * ignored if there is no such namespace.)  Also, it can include "pg_temp"
+ * to refer to the current backend's temp namespace.  This is usually also
+ * ignorable if the temp namespace hasn't been set up, but there's a special
+ * case: if "pg_temp" appears first then it should be the default creation
+ * target.  We kluge this case a little bit so that the temp namespace isn't
+ * set up until the first attempt to create something in it.  (The reason for
+ * klugery is that we can't create the temp namespace outside a transaction,
+ * but initial GUC processing of search_path happens outside a transaction.)
+ * activeTempCreationPending is TRUE if "pg_temp" appears first in the string
+ * but is not reflected in activeCreationNamespace because the namespace isn't
+ * set up yet.
+ *
+ * In bootstrap mode, the search path is set equal to "pg_catalog", so that
  * the system namespace is the only one searched or inserted into.
- * initdb is also careful to set search_path to 'pg_catalog' for its
+ * initdb is also careful to set search_path to "pg_catalog" for its
  * post-bootstrap standalone backend runs.     Otherwise the default search
  * path is determined by GUC.  The factory default path contains the PUBLIC
  * namespace (if it exists), preceded by the user's personal namespace
@@ -102,15 +120,20 @@ static List *activeSearchPath = NIL;
 /* default place to create stuff; if InvalidOid, no default */
 static Oid     activeCreationNamespace = InvalidOid;
 
+/* if TRUE, activeCreationNamespace is wrong, it should be temp namespace */
+static bool activeTempCreationPending = false;
+
 /* These variables are the values last derived from namespace_search_path: */
 
 static List *baseSearchPath = NIL;
 
 static Oid     baseCreationNamespace = InvalidOid;
 
+static bool baseTempCreationPending = false;
+
 static Oid     namespaceUser = InvalidOid;
 
-/* The above three values are valid only if baseSearchPathValid */
+/* The above four values are valid only if baseSearchPathValid */
 static bool baseSearchPathValid = true;
 
 /* Override requests are remembered in a stack of OverrideStackEntry structs */
@@ -262,6 +285,14 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
 
        if (newRelation->schemaname)
        {
+               /* check for pg_temp alias */
+               if (strcmp(newRelation->schemaname, "pg_temp") == 0)
+               {
+                       /* Initialize temp namespace if first time through */
+                       if (!OidIsValid(myTempNamespace))
+                               InitTempTableNamespace();
+                       return myTempNamespace;
+               }
                /* use exact schema given */
                namespaceId = GetSysCacheOid(NAMESPACENAME,
                                                                         CStringGetDatum(newRelation->schemaname),
@@ -277,6 +308,12 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
        {
                /* use the default creation namespace */
                recomputeNamespacePath();
+               if (activeTempCreationPending)
+               {
+                       /* Need to initialize temp namespace */
+                       InitTempTableNamespace();
+                       return myTempNamespace;
+               }
                namespaceId = activeCreationNamespace;
                if (!OidIsValid(namespaceId))
                        ereport(ERROR,
@@ -549,12 +586,16 @@ FuncnameGetCandidates(List *names, int nargs)
                }
                else
                {
-                       /* Consider only procs that are in the search path */
+                       /*
+                        * Consider only procs that are in the search path and are not
+                        * in the temp namespace.
+                        */
                        ListCell   *nsp;
 
                        foreach(nsp, activeSearchPath)
                        {
-                               if (procform->pronamespace == lfirst_oid(nsp))
+                               if (procform->pronamespace == lfirst_oid(nsp) &&
+                                       procform->pronamespace != myTempNamespace)
                                        break;
                                pathpos++;
                        }
@@ -770,6 +811,9 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
                Oid                     namespaceId = lfirst_oid(l);
                int                     i;
 
+               if (namespaceId == myTempNamespace)
+                       continue;                       /* do not look in temp namespace */
+
                for (i = 0; i < catlist->n_members; i++)
                {
                        HeapTuple       opertup = &catlist->members[i]->tuple;
@@ -872,12 +916,16 @@ OpernameGetCandidates(List *names, char oprkind)
                }
                else
                {
-                       /* Consider only opers that are in the search path */
+                       /*
+                        * Consider only opers that are in the search path and are not
+                        * in the temp namespace.
+                        */
                        ListCell   *nsp;
 
                        foreach(nsp, activeSearchPath)
                        {
-                               if (operform->oprnamespace == lfirst_oid(nsp))
+                               if (operform->oprnamespace == lfirst_oid(nsp) &&
+                                       operform->oprnamespace != myTempNamespace)
                                        break;
                                pathpos++;
                        }
@@ -1025,6 +1073,9 @@ OpclassnameGetOpcid(Oid amid, const char *opcname)
        {
                Oid                     namespaceId = lfirst_oid(l);
 
+               if (namespaceId == myTempNamespace)
+                       continue;                       /* do not look in temp namespace */
+
                opcid = GetSysCacheOid(CLAAMNAMENSP,
                                                           ObjectIdGetDatum(amid),
                                                           PointerGetDatum(opcname),
@@ -1108,6 +1159,9 @@ OpfamilynameGetOpfid(Oid amid, const char *opfname)
        {
                Oid                     namespaceId = lfirst_oid(l);
 
+               if (namespaceId == myTempNamespace)
+                       continue;                       /* do not look in temp namespace */
+
                opfid = GetSysCacheOid(OPFAMILYAMNAMENSP,
                                                           ObjectIdGetDatum(amid),
                                                           PointerGetDatum(opfname),
@@ -1190,6 +1244,9 @@ ConversionGetConid(const char *conname)
        {
                Oid                     namespaceId = lfirst_oid(l);
 
+               if (namespaceId == myTempNamespace)
+                       continue;                       /* do not look in temp namespace */
+
                conid = GetSysCacheOid(CONNAMENSP,
                                                           PointerGetDatum(conname),
                                                           ObjectIdGetDatum(namespaceId),
@@ -1316,6 +1373,19 @@ LookupExplicitNamespace(const char *nspname)
        Oid                     namespaceId;
        AclResult       aclresult;
 
+       /* check for pg_temp alias */
+       if (strcmp(nspname, "pg_temp") == 0)
+       {
+               if (OidIsValid(myTempNamespace))
+                       return myTempNamespace;
+               /*
+                * Since this is used only for looking up existing objects, there
+                * is no point in trying to initialize the temp namespace here;
+                * and doing so might create problems for some callers.
+                * Just fall through and give the "does not exist" error.
+                */
+       }
+
        namespaceId = GetSysCacheOid(NAMESPACENAME,
                                                                 CStringGetDatum(nspname),
                                                                 0, 0, 0);
@@ -1336,7 +1406,11 @@ LookupExplicitNamespace(const char *nspname)
  * LookupCreationNamespace
  *             Look up the schema and verify we have CREATE rights on it.
  *
- * This is just like LookupExplicitNamespace except for the permission check.
+ * This is just like LookupExplicitNamespace except for the permission check,
+ * and that we are willing to create pg_temp if needed.
+ *
+ * Note: calling this may result in a CommandCounterIncrement operation,
+ * if we have to create or clean out the temp namespace.
  */
 Oid
 LookupCreationNamespace(const char *nspname)
@@ -1344,6 +1418,15 @@ LookupCreationNamespace(const char *nspname)
        Oid                     namespaceId;
        AclResult       aclresult;
 
+       /* check for pg_temp alias */
+       if (strcmp(nspname, "pg_temp") == 0)
+       {
+               /* Initialize temp namespace if first time through */
+               if (!OidIsValid(myTempNamespace))
+                       InitTempTableNamespace();
+               return myTempNamespace;
+       }
+
        namespaceId = GetSysCacheOid(NAMESPACENAME,
                                                                 CStringGetDatum(nspname),
                                                                 0, 0, 0);
@@ -1369,21 +1452,28 @@ LookupCreationNamespace(const char *nspname)
  * Note: this does not apply any permissions check.  Callers must check
  * for CREATE rights on the selected namespace when appropriate.
  *
- * This is *not* used for tables.  Hence, the TEMP table namespace is
- * never selected as the creation target.
+ * Note: calling this may result in a CommandCounterIncrement operation,
+ * if we have to create or clean out the temp namespace.
  */
 Oid
 QualifiedNameGetCreationNamespace(List *names, char **objname_p)
 {
        char       *schemaname;
-       char       *objname;
        Oid                     namespaceId;
 
        /* deconstruct the name list */
-       DeconstructQualifiedName(names, &schemaname, &objname);
+       DeconstructQualifiedName(names, &schemaname, objname_p);
 
        if (schemaname)
        {
+               /* check for pg_temp alias */
+               if (strcmp(schemaname, "pg_temp") == 0)
+               {
+                       /* Initialize temp namespace if first time through */
+                       if (!OidIsValid(myTempNamespace))
+                               InitTempTableNamespace();
+                       return myTempNamespace;
+               }
                /* use exact schema given */
                namespaceId = GetSysCacheOid(NAMESPACENAME,
                                                                         CStringGetDatum(schemaname),
@@ -1398,6 +1488,12 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
        {
                /* use the default creation namespace */
                recomputeNamespacePath();
+               if (activeTempCreationPending)
+               {
+                       /* Need to initialize temp namespace */
+                       InitTempTableNamespace();
+                       return myTempNamespace;
+               }
                namespaceId = activeCreationNamespace;
                if (!OidIsValid(namespaceId))
                        ereport(ERROR,
@@ -1405,7 +1501,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
                                         errmsg("no schema has been selected to create in")));
        }
 
-       *objname_p = objname;
        return namespaceId;
 }
 
@@ -1634,6 +1729,7 @@ PushOverrideSearchPath(OverrideSearchPath *newpath)
        /* And make it active. */
        activeSearchPath = entry->searchPath;
        activeCreationNamespace = entry->creationNamespace;
+       activeTempCreationPending = false;                      /* XXX is this OK? */
 
        MemoryContextSwitchTo(oldcxt);
 }
@@ -1667,12 +1763,14 @@ PopOverrideSearchPath(void)
                entry = (OverrideStackEntry *) linitial(overrideStack);
                activeSearchPath = entry->searchPath;
                activeCreationNamespace = entry->creationNamespace;
+               activeTempCreationPending = false;                      /* XXX is this OK? */
        }
        else
        {
                /* If not baseSearchPathValid, this is useless but harmless */
                activeSearchPath = baseSearchPath;
                activeCreationNamespace = baseCreationNamespace;
+               activeTempCreationPending = baseTempCreationPending;
        }
 }
 
@@ -1706,6 +1804,10 @@ FindConversionByName(List *name)
                foreach(l, activeSearchPath)
                {
                        namespaceId = lfirst_oid(l);
+
+                       if (namespaceId == myTempNamespace)
+                               continue;                       /* do not look in temp namespace */
+
                        conoid = FindConversion(conversion_name, namespaceId);
                        if (OidIsValid(conoid))
                                return conoid;
@@ -1731,6 +1833,9 @@ FindDefaultConversionProc(int4 for_encoding, int4 to_encoding)
        {
                Oid                     namespaceId = lfirst_oid(l);
 
+               if (namespaceId == myTempNamespace)
+                       continue;                       /* do not look in temp namespace */
+
                proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding);
                if (OidIsValid(proc))
                        return proc;
@@ -1752,6 +1857,7 @@ recomputeNamespacePath(void)
        List       *oidlist;
        List       *newpath;
        ListCell   *l;
+       bool            temp_missing;
        Oid                     firstNS;
        MemoryContext oldcxt;
 
@@ -1781,6 +1887,7 @@ recomputeNamespacePath(void)
         * already been accepted.)      Don't make duplicate entries, either.
         */
        oidlist = NIL;
+       temp_missing = false;
        foreach(l, namelist)
        {
                char       *curname = (char *) lfirst(l);
@@ -1810,6 +1917,21 @@ recomputeNamespacePath(void)
                                        oidlist = lappend_oid(oidlist, namespaceId);
                        }
                }
+               else if (strcmp(curname, "pg_temp") == 0)
+               {
+                       /* pg_temp --- substitute temp namespace, if any */
+                       if (OidIsValid(myTempNamespace))
+                       {
+                               if (!list_member_oid(oidlist, myTempNamespace))
+                                       oidlist = lappend_oid(oidlist, myTempNamespace);
+                       }
+                       else
+                       {
+                               /* If it ought to be the creation namespace, set flag */
+                               if (oidlist == NIL)
+                                       temp_missing = true;
+                       }
+               }
                else
                {
                        /* normal namespace reference */
@@ -1825,7 +1947,9 @@ recomputeNamespacePath(void)
        }
 
        /*
-        * Remember the first member of the explicit list.
+        * Remember the first member of the explicit list.  (Note: this is
+        * nominally wrong if temp_missing, but we need it anyway to distinguish
+        * explicit from implicit mention of pg_catalog.)
         */
        if (oidlist == NIL)
                firstNS = InvalidOid;
@@ -1856,6 +1980,7 @@ recomputeNamespacePath(void)
        list_free(baseSearchPath);
        baseSearchPath = newpath;
        baseCreationNamespace = firstNS;
+       baseTempCreationPending = temp_missing;
 
        /* Mark the path valid. */
        baseSearchPathValid = true;
@@ -1864,6 +1989,7 @@ recomputeNamespacePath(void)
        /* And make it active. */
        activeSearchPath = baseSearchPath;
        activeCreationNamespace = baseCreationNamespace;
+       activeTempCreationPending = baseTempCreationPending;
 
        /* Clean up. */
        pfree(rawname);
@@ -1881,6 +2007,8 @@ InitTempTableNamespace(void)
        char            namespaceName[NAMEDATALEN];
        Oid                     namespaceId;
 
+       Assert(!OidIsValid(myTempNamespace));
+
        /*
         * First, do permission check to see if we are authorized to make temp
         * tables.      We use a nonstandard error message here since "databasename:
@@ -1941,16 +2069,6 @@ InitTempTableNamespace(void)
 }
 
 /*
- * Remove all temp tables from the temporary namespace.
- */
-void
-ResetTempTableNamespace(void)
-{
-       if (OidIsValid(myTempNamespace))
-               RemoveTempRelations(myTempNamespace);
-}
-
-/*
  * End-of-transaction cleanup for namespaces.
  */
 void
@@ -1995,6 +2113,7 @@ AtEOXact_Namespace(bool isCommit)
                /* If not baseSearchPathValid, this is useless but harmless */
                activeSearchPath = baseSearchPath;
                activeCreationNamespace = baseCreationNamespace;
+               activeTempCreationPending = baseTempCreationPending;
        }
 }
 
@@ -2046,12 +2165,14 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
                entry = (OverrideStackEntry *) linitial(overrideStack);
                activeSearchPath = entry->searchPath;
                activeCreationNamespace = entry->creationNamespace;
+               activeTempCreationPending = false;                      /* XXX is this OK? */
        }
        else
        {
                /* If not baseSearchPathValid, this is useless but harmless */
                activeSearchPath = baseSearchPath;
                activeCreationNamespace = baseCreationNamespace;
+               activeTempCreationPending = baseTempCreationPending;
        }
 }
 
@@ -2099,6 +2220,16 @@ RemoveTempRelationsCallback(int code, Datum arg)
        }
 }
 
+/*
+ * Remove all temp tables from the temporary namespace.
+ */
+void
+ResetTempTableNamespace(void)
+{
+       if (OidIsValid(myTempNamespace))
+               RemoveTempRelations(myTempNamespace);
+}
+
 
 /*
  * Routines for handling the GUC variable 'search_path'.
@@ -2132,8 +2263,9 @@ assign_search_path(const char *newval, bool doit, GucSource source)
        {
                /*
                 * Verify that all the names are either valid namespace names or
-                * "$user".  We do not require $user to correspond to a valid
-                * namespace.  We do not check for USAGE rights, either; should we?
+                * "$user" or "pg_temp".  We do not require $user to correspond to a
+                * valid namespace, and pg_temp might not exist yet.  We do not check
+                * for USAGE rights, either; should we?
                 *
                 * When source == PGC_S_TEST, we are checking the argument of an ALTER
                 * DATABASE SET or ALTER USER SET command.      It could be that the
@@ -2147,6 +2279,8 @@ assign_search_path(const char *newval, bool doit, GucSource source)
 
                        if (strcmp(curname, "$user") == 0)
                                continue;
+                       if (strcmp(curname, "pg_temp") == 0)
+                               continue;
                        if (!SearchSysCacheExists(NAMESPACENAME,
                                                                          CStringGetDatum(curname),
                                                                          0, 0, 0))
@@ -2190,10 +2324,12 @@ InitializeSearchPath(void)
                baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE);
                MemoryContextSwitchTo(oldcxt);
                baseCreationNamespace = PG_CATALOG_NAMESPACE;
+               baseTempCreationPending = false;
                baseSearchPathValid = true;
                namespaceUser = GetUserId();
                activeSearchPath = baseSearchPath;
                activeCreationNamespace = baseCreationNamespace;
+               activeTempCreationPending = baseTempCreationPending;
        }
        else
        {
@@ -2227,6 +2363,9 @@ NamespaceCallback(Datum arg, Oid relid)
  *
  * The returned list includes the implicitly-prepended namespaces only if
  * includeImplicit is true.
+ *
+ * Note: calling this may result in a CommandCounterIncrement operation,
+ * if we have to create or clean out the temp namespace.
  */
 List *
 fetch_search_path(bool includeImplicit)
@@ -2235,6 +2374,19 @@ fetch_search_path(bool includeImplicit)
 
        recomputeNamespacePath();
 
+       /*
+        * If the temp namespace should be first, force it to exist.  This is
+        * so that callers can trust the result to reflect the actual default
+        * creation namespace.  It's a bit bogus to do this here, since
+        * current_schema() is supposedly a stable function without side-effects,
+        * but the alternatives seem worse.
+        */
+       if (activeTempCreationPending)
+       {
+               InitTempTableNamespace();
+               recomputeNamespacePath();
+       }
+
        result = list_copy(activeSearchPath);
        if (!includeImplicit)
        {
index 3ba19b5..335a48c 100644 (file)
@@ -137,3 +137,61 @@ CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
 COMMIT;
 ERROR:  unsupported ON COMMIT and foreign key combination
 DETAIL:  Table "temptest4" references "temptest3", but they do not have the same ON COMMIT setting.
+-- Test manipulation of temp schema's placement in search path
+create table public.whereami (f1 text);
+insert into public.whereami values ('public');
+create temp table whereami (f1 text);
+insert into whereami values ('temp');
+create function public.whoami() returns text
+  as $$select 'public'::text$$ language sql;
+create function pg_temp.whoami() returns text
+  as $$select 'temp'::text$$ language sql;
+-- default should have pg_temp implicitly first, but only for tables
+select * from whereami;
+  f1  
+------
+ temp
+(1 row)
+
+select whoami();
+ whoami 
+--------
+ public
+(1 row)
+
+-- can list temp first explicitly, but it still doesn't affect functions
+set search_path = pg_temp, public;
+select * from whereami;
+  f1  
+------
+ temp
+(1 row)
+
+select whoami();
+ whoami 
+--------
+ public
+(1 row)
+
+-- or put it last for security
+set search_path = public, pg_temp;
+select * from whereami;
+   f1   
+--------
+ public
+(1 row)
+
+select whoami();
+ whoami 
+--------
+ public
+(1 row)
+
+-- you can invoke a temp function explicitly, though
+select pg_temp.whoami();
+ whoami 
+--------
+ temp
+(1 row)
+
+drop table public.whereami;
index 6a4b856..82d7834 100644 (file)
@@ -118,3 +118,36 @@ BEGIN;
 CREATE TEMP TABLE temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
 CREATE TEMP TABLE temptest4(col int REFERENCES temptest3);
 COMMIT;
+
+-- Test manipulation of temp schema's placement in search path
+
+create table public.whereami (f1 text);
+insert into public.whereami values ('public');
+
+create temp table whereami (f1 text);
+insert into whereami values ('temp');
+
+create function public.whoami() returns text
+  as $$select 'public'::text$$ language sql;
+
+create function pg_temp.whoami() returns text
+  as $$select 'temp'::text$$ language sql;
+
+-- default should have pg_temp implicitly first, but only for tables
+select * from whereami;
+select whoami();
+
+-- can list temp first explicitly, but it still doesn't affect functions
+set search_path = pg_temp, public;
+select * from whereami;
+select whoami();
+
+-- or put it last for security
+set search_path = public, pg_temp;
+select * from whereami;
+select whoami();
+
+-- you can invoke a temp function explicitly, though
+select pg_temp.whoami();
+
+drop table public.whereami;