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
This commit is contained in:
Tom Lane 2007-04-20 02:37:38 +00:00
parent 9350056eaa
commit aa27977fe2
7 changed files with 485 additions and 70 deletions

View 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>

View 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>

View 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,13 +126,8 @@ do it for earlier branch release files.
<listitem>
<para>
Improve detection of <acronym>POSIX</>-style time zone names (Tom)
</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>
@ -126,14 +139,36 @@ do it for earlier branch release files.
<listitem>
<para>
Fix <command>pg_dump</> so it can dump a sequence using <option>-t</> when not also dumping the owning table
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>
Improve outer join and bitmap join selection logic (Tom)
Planner fixes, including improving outer join and bitmap scan
selection logic (Tom)
</para>
</listitem>
<listitem>
<para>
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>
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</> 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>
@ -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>

View 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

View 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 $
*
*-------------------------------------------------------------------------
*/
@ -62,12 +62,30 @@
* 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:
@ -1940,16 +2068,6 @@ InitTempTableNamespace(void)
baseSearchPathValid = false; /* need to rebuild list */
}
/*
* Remove all temp tables from the temporary namespace.
*/
void
ResetTempTableNamespace(void)
{
if (OidIsValid(myTempNamespace))
RemoveTempRelations(myTempNamespace);
}
/*
* End-of-transaction cleanup for namespaces.
*/
@ -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)
{

View 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;

View 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;