diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2007-04-20 02:38:05 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2007-04-20 02:38:05 +0000 |
commit | fc52d13ca68de10ab837ee4d9a13fd68f69ea33e (patch) | |
tree | 385bc397942e5457b1ecae4df3c8acd5ce752e18 | |
parent | 2a1b76f04653b28796aa376f8fcb94721b282e44 (diff) | |
download | postgresql-fc52d13ca68de10ab837ee4d9a13fd68f69ea33e.tar.gz postgresql-fc52d13ca68de10ab837ee4d9a13fd68f69ea33e.zip |
Support explicit placement of the temporary-table schema within search_path.
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
-rw-r--r-- | doc/src/sgml/config.sgml | 16 | ||||
-rw-r--r-- | doc/src/sgml/ref/create_function.sgml | 50 | ||||
-rw-r--r-- | doc/src/sgml/release.sgml | 121 | ||||
-rw-r--r-- | src/backend/catalog/aclchk.c | 4 | ||||
-rw-r--r-- | src/backend/catalog/namespace.c | 176 | ||||
-rw-r--r-- | src/test/regress/expected/temp.out | 58 | ||||
-rw-r--r-- | src/test/regress/sql/temp.sql | 33 |
7 files changed, 414 insertions, 44 deletions
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 75da5f2bfd9..4a56e85b5da 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.36.2.7 2006/11/04 18:20:40 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.36.2.8 2007/04/20 02:38:04 tgl Exp $ --> <chapter Id="runtime-config"> <title>Server Configuration</title> @@ -3103,9 +3103,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> diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml index 022a26c6b09..e761f088417 100644 --- a/doc/src/sgml/ref/create_function.sgml +++ b/doc/src/sgml/ref/create_function.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.67 2005/11/01 21:09:50 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.67.2.1 2007/04/20 02:38:04 tgl Exp $ --> <refentry id="SQL-CREATEFUNCTION"> @@ -473,6 +473,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> diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml index 81dd645e8dd..b1bf376f2b4 100644 --- a/doc/src/sgml/release.sgml +++ b/doc/src/sgml/release.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.400.2.41 2007/04/19 13:02:30 momjian Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.400.2.42 2007/04/20 02:38:04 tgl Exp $ --> <!-- Typical markup: @@ -28,7 +28,8 @@ For new features, add links to the documentation sections. </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> @@ -49,39 +50,57 @@ For new features, add links to the documentation sections. <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</> 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> - 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) + Planner fixes, including improving outer join and bitmap scan + selection logic (Tom) </para> </listitem> <listitem> <para> - Improve outer join and bitmap join selection logic (Tom) + Fix PANIC during enlargement of a hash index (bug introduced in 8.1.6) + (Tom) + </para> + </listitem> + + <listitem> + <para> + Fix POSIX-style timezone specs to follow new USA DST rules (Tom) </para> </listitem> @@ -3047,7 +3066,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> @@ -3068,25 +3088,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> + <filename>/contrib/tsearch2</> crash fixes (Teodor) </para> </listitem> <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 PANIC during enlargement of a hash index (bug introduced in 8.0.10) + (Tom) </para> </listitem> <listitem> <para> - <filename>/contrib/tsearch2</> fixes (Teodor) + Fix POSIX-style timezone specs to follow new USA DST rules (Tom) </para> </listitem> @@ -6537,7 +6575,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> @@ -6558,13 +6597,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> + <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 bug in how <command>VACUUM FULL</> handles <command>UPDATE</> chains (Tom, Pavan Deolasee) + Fix PANIC during enlargement of a hash index (bug introduced in 7.4.15) + (Tom) </para> </listitem> @@ -9697,7 +9760,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> @@ -9718,7 +9782,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> diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 86bead6b5ee..3f813c54ba8 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.120.2.1 2005/11/22 18:23:06 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.120.2.2 2007/04/20 02:38:04 tgl Exp $ * * NOTES * See acl.h. @@ -1746,7 +1746,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 diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index b01a396aadd..9d965c6eb54 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.79.2.2 2006/02/10 19:01:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.79.2.3 2007/04/20 02:38:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -64,14 +64,32 @@ * 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 normally equal to the first * element of the explicit list, but is the "special" namespace when one * has been set. If the explicit list is empty and there is no special * namespace, 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.) + * tempCreationPending is TRUE if "pg_temp" appears first in the string but + * is not reflected in defaultCreationNamespace 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. - * The initdb script is also careful to set search_path to 'pg_catalog' for + * The initdb script 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 @@ -99,7 +117,10 @@ static Oid defaultCreationNamespace = InvalidOid; /* first explicit member of list; usually same as defaultCreationNamespace */ static Oid firstExplicitNamespace = InvalidOid; -/* The above four values are valid only if namespaceSearchPathValid */ +/* if TRUE, defaultCreationNamespace is wrong, it should be temp namespace */ +static bool tempCreationPending = false; + +/* The above five values are valid only if namespaceSearchPathValid */ static bool namespaceSearchPathValid = true; /* @@ -244,6 +265,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), @@ -259,6 +288,12 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation) { /* use the default creation namespace */ recomputeNamespacePath(); + if (tempCreationPending) + { + /* Need to initialize temp namespace */ + InitTempTableNamespace(); + return myTempNamespace; + } namespaceId = defaultCreationNamespace; if (!OidIsValid(namespaceId)) ereport(ERROR, @@ -531,12 +566,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, namespaceSearchPath) { - if (procform->pronamespace == lfirst_oid(nsp)) + if (procform->pronamespace == lfirst_oid(nsp) && + procform->pronamespace != myTempNamespace) break; pathpos++; } @@ -765,12 +804,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, namespaceSearchPath) { - if (operform->oprnamespace == lfirst_oid(nsp)) + if (operform->oprnamespace == lfirst_oid(nsp) && + operform->oprnamespace != myTempNamespace) break; pathpos++; } @@ -931,6 +974,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), @@ -1013,6 +1059,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), @@ -1139,6 +1188,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); @@ -1159,7 +1221,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) @@ -1167,6 +1233,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); @@ -1192,21 +1267,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), @@ -1221,6 +1303,12 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) { /* use the default creation namespace */ recomputeNamespacePath(); + if (tempCreationPending) + { + /* Need to initialize temp namespace */ + InitTempTableNamespace(); + return myTempNamespace; + } namespaceId = defaultCreationNamespace; if (!OidIsValid(namespaceId)) ereport(ERROR, @@ -1228,7 +1316,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) errmsg("no schema has been selected to create in"))); } - *objname_p = objname; return namespaceId; } @@ -1419,6 +1506,10 @@ FindConversionByName(List *name) foreach(l, namespaceSearchPath) { namespaceId = lfirst_oid(l); + + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + conoid = FindConversion(conversion_name, namespaceId); if (OidIsValid(conoid)) return conoid; @@ -1444,6 +1535,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; @@ -1465,6 +1559,7 @@ recomputeNamespacePath(void) List *oidlist; List *newpath; ListCell *l; + bool temp_missing; Oid firstNS; MemoryContext oldcxt; @@ -1492,6 +1587,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); @@ -1521,6 +1617,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 */ @@ -1536,7 +1647,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; @@ -1576,9 +1689,16 @@ recomputeNamespacePath(void) */ firstExplicitNamespace = firstNS; if (OidIsValid(mySpecialNamespace)) + { defaultCreationNamespace = mySpecialNamespace; + /* don't have to create temp in this state */ + tempCreationPending = false; + } else + { defaultCreationNamespace = firstNS; + tempCreationPending = temp_missing; + } /* Mark the path valid. */ namespaceSearchPathValid = true; @@ -1600,6 +1720,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: @@ -1798,8 +1920,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 @@ -1813,6 +1936,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)) @@ -1857,6 +1982,7 @@ InitializeSearchPath(void) MemoryContextSwitchTo(oldcxt); defaultCreationNamespace = PG_CATALOG_NAMESPACE; firstExplicitNamespace = PG_CATALOG_NAMESPACE; + tempCreationPending = false; namespaceSearchPathValid = true; namespaceUser = GetUserId(); } @@ -1892,6 +2018,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) @@ -1900,6 +2029,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 (tempCreationPending) + { + InitTempTableNamespace(); + recomputeNamespacePath(); + } + result = list_copy(namespaceSearchPath); if (!includeImplicit) { diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index 897ae751bd9..a8a33b9f59a 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -109,3 +109,61 @@ CREATE TEMP TABLE temptest4(col int REFERENCES temptest3); COMMIT; ERROR: unsupported ON COMMIT and foreign key combination DETAIL: Table "temptest4" references "temptest3" via foreign key constraint "temptest4_col_fkey", 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; diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql index 972d511ab76..19f3051c81e 100644 --- a/src/test/regress/sql/temp.sql +++ b/src/test/regress/sql/temp.sql @@ -99,3 +99,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; |