1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

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:49 +00:00
parent 4e6c6a40e0
commit d694bdd1c9
7 changed files with 468 additions and 60 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.133 2006/11/16 14:41:49 petere Exp $
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.133.2.1 2007/04/20 02:37:48 tgl Exp $
*
* NOTES
* See acl.h.
@ -1832,7 +1832,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.88 2006/10/04 00:29:50 momjian Exp $
* $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.88.2.1 2007/04/20 02:37:48 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -62,14 +62,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
@ -97,7 +115,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++;
}
@ -752,6 +791,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;
@ -854,12 +896,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++;
}
@ -1007,6 +1053,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),
@ -1089,6 +1138,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),
@ -1215,6 +1267,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);
@ -1235,7 +1300,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)
@ -1243,6 +1312,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);
@ -1268,21 +1346,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),
@ -1297,6 +1382,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,
@ -1304,7 +1395,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
errmsg("no schema has been selected to create in")));
}
*objname_p = objname;
return namespaceId;
}
@ -1495,6 +1585,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;
@ -1520,6 +1614,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;
@ -1541,6 +1638,7 @@ recomputeNamespacePath(void)
List *oidlist;
List *newpath;
ListCell *l;
bool temp_missing;
Oid firstNS;
MemoryContext oldcxt;
@ -1568,6 +1666,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);
@ -1597,6 +1696,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 */
@ -1612,7 +1726,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;
@ -1652,9 +1768,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;
@ -1676,6 +1799,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:
@ -1874,8 +1999,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
@ -1889,6 +2015,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))
@ -1933,6 +2061,7 @@ InitializeSearchPath(void)
MemoryContextSwitchTo(oldcxt);
defaultCreationNamespace = PG_CATALOG_NAMESPACE;
firstExplicitNamespace = PG_CATALOG_NAMESPACE;
tempCreationPending = false;
namespaceSearchPathValid = true;
namespaceUser = GetUserId();
}
@ -1968,6 +2097,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)
@ -1976,6 +2108,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)
{