diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2024-11-11 10:29:54 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2024-11-11 10:29:54 -0500 |
commit | 5a2fed911a85ed6d8a015a6bafe3a0d9a69334ae (patch) | |
tree | cfefb92673eb97a03fd6c960abd313447dda19f4 /src/backend/commands/variable.c | |
parent | cd7ab57532bb4fbf2e636b1f7d132e6e2d9ac5fc (diff) | |
download | postgresql-5a2fed911a85ed6d8a015a6bafe3a0d9a69334ae.tar.gz postgresql-5a2fed911a85ed6d8a015a6bafe3a0d9a69334ae.zip |
Fix improper interactions between session_authorization and role.
The SQL spec mandates that SET SESSION AUTHORIZATION implies
SET ROLE NONE. We tried to implement that within the lowest-level
functions that manipulate these settings, but that was a bad idea.
In particular, guc.c assumes that it doesn't matter in what order
it applies GUC variable updates, but that was not the case for these
two variables. This problem, compounded by some hackish attempts to
work around it, led to some security-grade issues:
* Rolling back a transaction that had done SET SESSION AUTHORIZATION
would revert to SET ROLE NONE, even if that had not been the previous
state, so that the effective user ID might now be different from what
it had been.
* The same for SET SESSION AUTHORIZATION in a function SET clause.
* If a parallel worker inspected current_setting('role'), it saw
"none" even when it should see something else.
Also, although the parallel worker startup code intended to cope
with the current role's pg_authid row having disappeared, its
implementation of that was incomplete so it would still fail.
Fix by fully separating the miscinit.c functions that assign
session_authorization from those that assign role. To implement the
spec's requirement, teach set_config_option itself to perform "SET
ROLE NONE" when it sets session_authorization. (This is undoubtedly
ugly, but the alternatives seem worse. In particular, there's no way
to do it within assign_session_authorization without incompatible
changes in the API for GUC assign hooks.) Also, improve
ParallelWorkerMain to directly set all the relevant user-ID variables
instead of relying on some of them to get set indirectly. That
allows us to survive not finding the pg_authid row during worker
startup.
In v16 and earlier, this includes back-patching 9987a7bf3 which
fixed a violation of GUC coding rules: SetSessionAuthorization
is not an appropriate place to be throwing errors from.
Security: CVE-2024-10978
Diffstat (limited to 'src/backend/commands/variable.c')
-rw-r--r-- | src/backend/commands/variable.c | 121 |
1 files changed, 70 insertions, 51 deletions
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index 7df26942aff..99462681925 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -822,63 +822,77 @@ check_session_authorization(char **newval, void **extra, GucSource source) if (*newval == NULL) return true; - if (!IsTransactionState()) + if (InitializingParallelWorker) { /* - * Can't do catalog lookups, so fail. The result of this is that - * session_authorization cannot be set in postgresql.conf, which seems - * like a good thing anyway, so we don't work hard to avoid it. + * In parallel worker initialization, we want to copy the leader's + * state even if it no longer matches the catalogs. ParallelWorkerMain + * already installed the correct role OID and superuser state. */ - return false; + roleid = GetSessionUserId(); + is_superuser = GetSessionUserIsSuperuser(); } + else + { + if (!IsTransactionState()) + { + /* + * Can't do catalog lookups, so fail. The result of this is that + * session_authorization cannot be set in postgresql.conf, which + * seems like a good thing anyway, so we don't work hard to avoid + * it. + */ + return false; + } - /* - * When source == PGC_S_TEST, we don't throw a hard error for a - * nonexistent user name or insufficient privileges, only a NOTICE. See - * comments in guc.h. - */ + /* + * When source == PGC_S_TEST, we don't throw a hard error for a + * nonexistent user name or insufficient privileges, only a NOTICE. + * See comments in guc.h. + */ - /* Look up the username */ - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); - if (!HeapTupleIsValid(roleTup)) - { - if (source == PGC_S_TEST) + /* Look up the username */ + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); + if (!HeapTupleIsValid(roleTup)) { - ereport(NOTICE, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", *newval))); - return true; + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", *newval))); + return true; + } + GUC_check_errmsg("role \"%s\" does not exist", *newval); + return false; } - GUC_check_errmsg("role \"%s\" does not exist", *newval); - return false; - } - roleform = (Form_pg_authid) GETSTRUCT(roleTup); - roleid = roleform->oid; - is_superuser = roleform->rolsuper; + roleform = (Form_pg_authid) GETSTRUCT(roleTup); + roleid = roleform->oid; + is_superuser = roleform->rolsuper; - ReleaseSysCache(roleTup); + ReleaseSysCache(roleTup); - /* - * Only superusers may SET SESSION AUTHORIZATION a role other than itself. - * Note that in case of multiple SETs in a single session, the original - * authenticated user's superuserness is what matters. - */ - if (roleid != GetAuthenticatedUserId() && - !superuser_arg(GetAuthenticatedUserId())) - { - if (source == PGC_S_TEST) + /* + * Only superusers may SET SESSION AUTHORIZATION a role other than + * itself. Note that in case of multiple SETs in a single session, the + * original authenticated user's superuserness is what matters. + */ + if (roleid != GetAuthenticatedUserId() && + !superuser_arg(GetAuthenticatedUserId())) { - ereport(NOTICE, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission will be denied to set session authorization \"%s\"", - *newval))); - return true; + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission will be denied to set session authorization \"%s\"", + *newval))); + return true; + } + GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE); + GUC_check_errmsg("permission denied to set session authorization \"%s\"", + *newval); + return false; } - GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE); - GUC_check_errmsg("permission denied to set session authorization \"%s\"", - *newval); - return false; } /* Set up "extra" struct for assign_session_authorization to use */ @@ -928,6 +942,16 @@ check_role(char **newval, void **extra, GucSource source) roleid = InvalidOid; is_superuser = false; } + else if (InitializingParallelWorker) + { + /* + * In parallel worker initialization, we want to copy the leader's + * state even if it no longer matches the catalogs. ParallelWorkerMain + * already installed the correct role OID and superuser state. + */ + roleid = GetCurrentRoleId(); + is_superuser = current_role_is_superuser; + } else { if (!IsTransactionState()) @@ -967,13 +991,8 @@ check_role(char **newval, void **extra, GucSource source) ReleaseSysCache(roleTup); - /* - * Verify that session user is allowed to become this role, but skip - * this in parallel mode, where we must blindly recreate the parallel - * leader's state. - */ - if (!InitializingParallelWorker && - !member_can_set_role(GetSessionUserId(), roleid)) + /* Verify that session user is allowed to become this role */ + if (!member_can_set_role(GetSessionUserId(), roleid)) { if (source == PGC_S_TEST) { |