diff options
Diffstat (limited to 'src/backend/utils')
-rw-r--r-- | src/backend/utils/adt/ri_triggers.c | 24 | ||||
-rw-r--r-- | src/backend/utils/fmgr/fmgr.c | 11 | ||||
-rw-r--r-- | src/backend/utils/init/miscinit.c | 95 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 119 |
4 files changed, 182 insertions, 67 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 8dae05d4046..c6e45729eae 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -17,7 +17,7 @@ * * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.89.2.2 2008/01/03 21:23:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.89.2.3 2009/12/09 21:58:29 tgl Exp $ * * ---------- */ @@ -3009,7 +3009,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, void *qplan; Relation query_rel; Oid save_userid; - bool save_secdefcxt; + int save_sec_context; /* * The query is always run against the FK table except when this is an @@ -3023,8 +3023,9 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, query_rel = fk_rel; /* Switch to proper UID to perform check as */ - GetUserIdAndContext(&save_userid, &save_secdefcxt); - SetUserIdAndContext(RelationGetForm(query_rel)->relowner, true); + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Create the plan */ qplan = SPI_prepare(querystr, nargs, argtypes); @@ -3032,8 +3033,8 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, if (qplan == NULL) elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr); - /* Restore UID */ - SetUserIdAndContext(save_userid, save_secdefcxt); + /* Restore UID and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); /* Save the plan if requested */ if (cache_plan) @@ -3063,7 +3064,7 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan, int limit; int spi_result; Oid save_userid; - bool save_secdefcxt; + int save_sec_context; Datum vals[RI_MAX_NUMKEYS * 2]; char nulls[RI_MAX_NUMKEYS * 2]; @@ -3141,8 +3142,9 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan, limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0; /* Switch to proper UID to perform check as */ - GetUserIdAndContext(&save_userid, &save_secdefcxt); - SetUserIdAndContext(RelationGetForm(query_rel)->relowner, true); + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Finally we can run the query. */ spi_result = SPI_execute_snapshot(qplan, @@ -3150,8 +3152,8 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan, test_snapshot, crosscheck_snapshot, false, false, limit); - /* Restore UID */ - SetUserIdAndContext(save_userid, save_secdefcxt); + /* Restore UID and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); /* Check result */ if (spi_result < 0) diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 2aeb46120f7..43f7712cd67 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.102.2.3 2009/01/07 20:39:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.102.2.4 2009/12/09 21:58:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -785,7 +785,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS) FmgrInfo *save_flinfo; struct fmgr_security_definer_cache *volatile fcache; Oid save_userid; - bool save_secdefcxt; + int save_sec_context; HeapTuple tuple; if (!fcinfo->flinfo->fn_extra) @@ -811,8 +811,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS) else fcache = fcinfo->flinfo->fn_extra; - GetUserIdAndContext(&save_userid, &save_secdefcxt); - SetUserIdAndContext(fcache->userid, true); + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(fcache->userid, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* * We don't need to restore the userid settings on error, because the @@ -836,7 +837,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS) fcinfo->flinfo = save_flinfo; - SetUserIdAndContext(save_userid, save_secdefcxt); + SetUserIdAndSecContext(save_userid, save_sec_context); return result; } diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index fbd31df49d1..36518212104 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.159.2.1 2008/01/03 21:23:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.159.2.2 2009/12/09 21:58:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -271,8 +271,10 @@ make_absolute_path(const char *path) * be the same as OuterUserId, but it changes during calls to SECURITY * DEFINER functions, as well as locally in some specialized commands. * - * SecurityDefinerContext is TRUE if we are within a SECURITY DEFINER function - * or another context that temporarily changes CurrentUserId. + * SecurityRestrictionContext holds flags indicating reason(s) for changing + * CurrentUserId. In some cases we need to lock down operations that are + * not directly controlled by privilege settings, and this provides a + * convenient way to do it. * ---------------------------------------------------------------- */ static Oid AuthenticatedUserId = InvalidOid; @@ -284,7 +286,7 @@ static Oid CurrentUserId = InvalidOid; static bool AuthenticatedUserIsSuperuser = false; static bool SessionUserIsSuperuser = false; -static bool SecurityDefinerContext = false; +static int SecurityRestrictionContext = 0; /* We also remember if a SET ROLE is currently active */ static bool SetRoleIsActive = false; @@ -293,7 +295,7 @@ static bool SetRoleIsActive = false; /* * GetUserId - get the current effective user ID. * - * Note: there's no SetUserId() anymore; use SetUserIdAndContext(). + * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). */ Oid GetUserId(void) @@ -317,7 +319,7 @@ GetOuterUserId(void) static void SetOuterUserId(Oid userid) { - AssertState(!SecurityDefinerContext); + AssertState(SecurityRestrictionContext == 0); AssertArg(OidIsValid(userid)); OuterUserId = userid; @@ -340,7 +342,7 @@ GetSessionUserId(void) static void SetSessionUserId(Oid userid, bool is_superuser) { - AssertState(!SecurityDefinerContext); + AssertState(SecurityRestrictionContext == 0); AssertArg(OidIsValid(userid)); SessionUserId = userid; SessionUserIsSuperuser = is_superuser; @@ -353,11 +355,29 @@ SetSessionUserId(Oid userid, bool is_superuser) /* - * GetUserIdAndContext/SetUserIdAndContext - get/set the current user ID - * and the SecurityDefinerContext flag. + * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID + * and the SecurityRestrictionContext flags. * - * Unlike GetUserId, GetUserIdAndContext does *not* Assert that the current - * value of CurrentUserId is valid; nor does SetUserIdAndContext require + * Currently there are two valid bits in SecurityRestrictionContext: + * + * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation + * that is temporarily changing CurrentUserId via these functions. This is + * needed to indicate that the actual value of CurrentUserId is not in sync + * with guc.c's internal state, so SET ROLE has to be disallowed. + * + * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation + * that does not wish to trust called user-defined functions at all. This + * bit prevents not only SET ROLE, but various other changes of session state + * that normally is unprotected but might possibly be used to subvert the + * calling session later. An example is replacing an existing prepared + * statement with new code, which will then be executed with the outer + * session's permissions when the prepared statement is next used. Since + * these restrictions are fairly draconian, we apply them only in contexts + * where the called functions are really supposed to be side-effect-free + * anyway, such as VACUUM/ANALYZE/REINDEX. + * + * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current + * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require * the new value to be valid. In fact, these routines had better not * ever throw any kind of error. This is because they are used by * StartTransaction and AbortTransaction to save/restore the settings, @@ -366,27 +386,66 @@ SetSessionUserId(Oid userid, bool is_superuser) * through AbortTransaction without asserting in case InitPostgres fails. */ void -GetUserIdAndContext(Oid *userid, bool *sec_def_context) +GetUserIdAndSecContext(Oid *userid, int *sec_context) { *userid = CurrentUserId; - *sec_def_context = SecurityDefinerContext; + *sec_context = SecurityRestrictionContext; } void -SetUserIdAndContext(Oid userid, bool sec_def_context) +SetUserIdAndSecContext(Oid userid, int sec_context) { CurrentUserId = userid; - SecurityDefinerContext = sec_def_context; + SecurityRestrictionContext = sec_context; } /* - * InSecurityDefinerContext - are we inside a SECURITY DEFINER context? + * InLocalUserIdChange - are we inside a local change of CurrentUserId? */ bool -InSecurityDefinerContext(void) +InLocalUserIdChange(void) { - return SecurityDefinerContext; + return (SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0; +} + +/* + * InSecurityRestrictedOperation - are we inside a security-restricted command? + */ +bool +InSecurityRestrictedOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0; +} + + +/* + * These are obsolete versions of Get/SetUserIdAndSecContext that are + * only provided for bug-compatibility with some rather dubious code in + * pljava. We allow the userid to be set, but only when not inside a + * security restriction context. + */ +void +GetUserIdAndContext(Oid *userid, bool *sec_def_context) +{ + *userid = CurrentUserId; + *sec_def_context = InLocalUserIdChange(); +} + +void +SetUserIdAndContext(Oid userid, bool sec_def_context) +{ + /* We throw the same error SET ROLE would. */ + if (InSecurityRestrictedOperation()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + "role"))); + CurrentUserId = userid; + if (sec_def_context) + SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE; + else + SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 03c84b7b475..0976a411b98 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut <peter_e@gmx.net>. * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.360.2.4 2009/09/03 22:08:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.360.2.5 2009/12/09 21:58:29 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -2077,7 +2077,7 @@ static struct config_string ConfigureNamesString[] = {"role", PGC_USERSET, UNGROUPED, gettext_noop("Sets the current role."), NULL, - GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_DEF + GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST }, &role_string, "none", assign_role, show_role @@ -2088,7 +2088,7 @@ static struct config_string ConfigureNamesString[] = {"session_authorization", PGC_USERSET, UNGROUPED, gettext_noop("Sets the session user name."), NULL, - GUC_IS_NAME | GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_DEF + GUC_IS_NAME | GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST }, &session_authorization_string, NULL, assign_session_authorization, show_session_authorization @@ -2311,6 +2311,8 @@ static bool guc_dirty; /* TRUE if need to do commit/abort work */ static bool reporting_enabled; /* TRUE to enable GUC_REPORT */ +static int GUCNestLevel = 0; /* 1 when in main transaction */ + static int guc_var_compare(const void *a, const void *b); static int guc_name_compare(const char *namea, const char *nameb); @@ -3172,17 +3174,16 @@ ResetAllOptions(void) static void push_old_value(struct config_generic * gconf) { - int my_level = GetCurrentTransactionNestLevel(); GucStack *stack; /* If we're not inside a transaction, do nothing */ - if (my_level == 0) + if (GUCNestLevel == 0) return; for (;;) { /* Done if we already pushed it at this nesting depth */ - if (gconf->stack && gconf->stack->nest_level >= my_level) + if (gconf->stack && gconf->stack->nest_level >= GUCNestLevel) return; /* @@ -3241,20 +3242,53 @@ push_old_value(struct config_generic * gconf) } /* - * Do GUC processing at transaction or subtransaction commit or abort. + * Do GUC processing at main transaction start. + */ +void +AtStart_GUC(void) +{ + /* + * The nest level should be 0 between transactions; if it isn't, + * somebody didn't call AtEOXact_GUC, or called it with the wrong + * nestLevel. We throw a warning but make no other effort to clean up. + */ + if (GUCNestLevel != 0) + elog(WARNING, "GUC nest level = %d at transaction start", + GUCNestLevel); + GUCNestLevel = 1; +} + +/* + * Enter a new nesting level for GUC values. This is called at subtransaction + * start and when entering a function that has proconfig settings. NOTE that + * we must not risk error here, else subtransaction start will be unhappy. + */ +int +NewGUCNestLevel(void) +{ + return ++GUCNestLevel; +} + +/* + * Do GUC processing at transaction or subtransaction commit or abort, or + * when exiting a function that has proconfig settings. (The name is thus + * a bit of a misnomer; perhaps it should be ExitGUCNestLevel or some such.) + * During abort, we discard all GUC settings that were applied at nesting + * levels >= nestLevel. nestLevel == 1 corresponds to the main transaction. */ void -AtEOXact_GUC(bool isCommit, bool isSubXact) +AtEOXact_GUC(bool isCommit, int nestLevel) { - int my_level; int i; + Assert(nestLevel > 0 && nestLevel <= GUCNestLevel); + /* Quick exit if nothing's changed in this transaction */ if (!guc_dirty) + { + GUCNestLevel = nestLevel - 1; return; - - my_level = GetCurrentTransactionNestLevel(); - Assert(isSubXact ? (my_level > 1) : (my_level == 1)); + } for (i = 0; i < num_guc_variables; i++) { @@ -3275,9 +3309,9 @@ AtEOXact_GUC(bool isCommit, bool isSubXact) /* Assert that we stacked old value before changing it */ Assert(stack != NULL && (my_status & GUC_HAVE_STACK)); /* However, the last change may have been at an outer xact level */ - if (stack->nest_level < my_level) + if (stack->nest_level < nestLevel) continue; - Assert(stack->nest_level == my_level); + Assert(stack->nest_level == nestLevel); /* * We will pop the stack entry. Start by restoring outer xact status @@ -3461,7 +3495,7 @@ AtEOXact_GUC(bool isCommit, bool isSubXact) set_string_field(conf, &stack->tentative_val.stringval, NULL); /* Don't store tentative value separately after commit */ - if (!isSubXact) + if (nestLevel == 1) set_string_field(conf, &conf->tentative_val, NULL); break; } @@ -3475,7 +3509,7 @@ AtEOXact_GUC(bool isCommit, bool isSubXact) * If we're now out of all xact levels, forget TENTATIVE status bit; * there's nothing tentative about the value anymore. */ - if (!isSubXact) + if (nestLevel == 1) { Assert(gconf->stack == NULL); gconf->status = 0; @@ -3492,8 +3526,11 @@ AtEOXact_GUC(bool isCommit, bool isSubXact) * that all outer transaction levels will have stacked values to deal * with.) */ - if (!isSubXact) + if (nestLevel == 1) guc_dirty = false; + + /* Update nesting level */ + GUCNestLevel = nestLevel - 1; } @@ -4026,29 +4063,45 @@ set_config_option(const char *name, const char *value, } /* - * Disallow changing GUC_NOT_WHILE_SEC_DEF values if we are inside a - * security-definer function. We can reject this regardless of - * the context or source, mainly because sources that it might be + * Disallow changing GUC_NOT_WHILE_SEC_REST values if we are inside a + * security restriction context. We can reject this regardless of + * the GUC context or source, mainly because sources that it might be * reasonable to override for won't be seen while inside a function. * - * Note: variables marked GUC_NOT_WHILE_SEC_DEF should probably be marked + * Note: variables marked GUC_NOT_WHILE_SEC_REST should usually be marked * GUC_NO_RESET_ALL as well, because ResetAllOptions() doesn't check this. + * An exception might be made if the reset value is assumed to be "safe". * * Note: this flag is currently used for "session_authorization" and - * "role". We need to prohibit this because when we exit the sec-def - * context, GUC won't be notified, leaving things out of sync. - * - * XXX it would be nice to allow these cases in future, with the behavior - * being that the SET's effects end when the security definer context is - * exited. + * "role". We need to prohibit changing these inside a local userid + * context because when we exit it, GUC won't be notified, leaving things + * out of sync. (This could be fixed by forcing a new GUC nesting level, + * but that would change behavior in possibly-undesirable ways.) Also, + * we prohibit changing these in a security-restricted operation because + * otherwise RESET could be used to regain the session user's privileges. */ - if ((record->flags & GUC_NOT_WHILE_SEC_DEF) && InSecurityDefinerContext()) + if (record->flags & GUC_NOT_WHILE_SEC_REST) { - ereport(elevel, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("cannot set parameter \"%s\" within security-definer function", - name))); - return false; + if (InLocalUserIdChange()) + { + /* + * Phrasing of this error message is historical, but it's the + * most common case. + */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-definer function", + name))); + return false; + } + if (InSecurityRestrictedOperation()) + { + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + name))); + return false; + } } /* |