From ae340d0318521ae7234ed3b7221a1f65f39a52c0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 11 Nov 2024 10:29:54 -0500 Subject: [PATCH] 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 --- src/backend/access/transam/parallel.c | 36 ++++-- src/backend/commands/variable.c | 103 ++++++++++----- src/backend/utils/init/miscinit.c | 157 ++++++++++++++--------- src/backend/utils/misc/guc.c | 43 +++++-- src/include/miscadmin.h | 3 + src/test/regress/expected/privileges.out | 67 ++++++++++ src/test/regress/sql/privileges.sql | 33 +++++ 7 files changed, 331 insertions(+), 111 deletions(-) diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index 93f90cbd91..e8b047e64f 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -84,12 +84,15 @@ typedef struct FixedParallelState /* Fixed-size state that workers must restore. */ Oid database_id; Oid authenticated_user_id; - Oid current_user_id; + Oid session_user_id; Oid outer_user_id; + Oid current_user_id; Oid temp_namespace_id; Oid temp_toast_namespace_id; int sec_context; - bool is_superuser; + bool authenticated_user_is_superuser; + bool session_user_is_superuser; + bool role_is_superuser; PGPROC *parallel_leader_pgproc; pid_t parallel_leader_pid; BackendId parallel_leader_backend_id; @@ -334,9 +337,12 @@ InitializeParallelDSM(ParallelContext *pcxt) shm_toc_allocate(pcxt->toc, sizeof(FixedParallelState)); fps->database_id = MyDatabaseId; fps->authenticated_user_id = GetAuthenticatedUserId(); + fps->session_user_id = GetSessionUserId(); fps->outer_user_id = GetCurrentRoleId(); - fps->is_superuser = session_auth_is_superuser; GetUserIdAndSecContext(&fps->current_user_id, &fps->sec_context); + fps->authenticated_user_is_superuser = GetAuthenticatedUserIsSuperuser(); + fps->session_user_is_superuser = GetSessionUserIsSuperuser(); + fps->role_is_superuser = session_auth_is_superuser; GetTempNamespaceState(&fps->temp_namespace_id, &fps->temp_toast_namespace_id); fps->parallel_leader_pgproc = MyProc; @@ -1408,6 +1414,18 @@ ParallelWorkerMain(Datum main_arg) entrypt = LookupParallelWorkerFunction(library_name, function_name); + /* + * Restore current session authorization and role id. No verification + * happens here, we just blindly adopt the leader's state. Note that this + * has to happen before InitPostgres, since InitializeSessionUserId will + * not set these variables. + */ + SetAuthenticatedUserId(fps->authenticated_user_id, + fps->authenticated_user_is_superuser); + SetSessionAuthorization(fps->session_user_id, + fps->session_user_is_superuser); + SetCurrentRoleId(fps->outer_user_id, fps->role_is_superuser); + /* Restore database connection. */ BackgroundWorkerInitializeConnectionByOid(fps->database_id, fps->authenticated_user_id, @@ -1473,13 +1491,13 @@ ParallelWorkerMain(Datum main_arg) InvalidateSystemCaches(); /* - * Restore current role id. Skip verifying whether session user is - * allowed to become this role and blindly restore the leader's state for - * current role. + * Restore current user ID and security context. No verification happens + * here, we just blindly adopt the leader's state. We can't do this till + * after restoring GUCs, else we'll get complaints about restoring + * session_authorization and role. (In effect, we're assuming that all + * the restored values are okay to set, even if we are now inside a + * restricted context.) */ - SetCurrentRoleId(fps->outer_user_id, fps->is_superuser); - - /* Restore user ID and security context. */ SetUserIdAndSecContext(fps->current_user_id, fps->sec_context); /* Restore temp-namespace state to ensure search path matches leader's. */ diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index f0f2e07655..a81d236c6a 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -811,41 +811,79 @@ 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(); } - - /* Look up the username */ - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); - if (!HeapTupleIsValid(roleTup)) + 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, only a NOTICE. See comments in guc.h. + * nonexistent user name or insufficient privileges, only a NOTICE. + * See comments in guc.h. */ - 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; + } + + roleform = (Form_pg_authid) GETSTRUCT(roleTup); + roleid = roleform->oid; + is_superuser = roleform->rolsuper; + + 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() && + !GetAuthenticatedUserIsSuperuser()) + { + 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_errmsg("role \"%s\" does not exist", *newval); - return false; } - roleform = (Form_pg_authid) GETSTRUCT(roleTup); - roleid = roleform->oid; - is_superuser = roleform->rolsuper; - - ReleaseSysCache(roleTup); - /* Set up "extra" struct for assign_session_authorization to use */ myextra = (role_auth_extra *) guc_malloc(LOG, sizeof(role_auth_extra)); if (!myextra) @@ -894,6 +932,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 = session_auth_is_superuser; + } else { if (!IsTransactionState()) @@ -933,13 +981,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) { diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 4cfeaeb114..e7bffcd11b 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -29,6 +29,7 @@ #include #include "access/htup_details.h" +#include "access/parallel.h" #include "catalog/pg_authid.h" #include "common/file_perm.h" #include "libpq/libpq.h" @@ -525,7 +526,7 @@ GetOuterUserId(void) static void -SetOuterUserId(Oid userid) +SetOuterUserId(Oid userid, bool is_superuser) { Assert(SecurityRestrictionContext == 0); Assert(OidIsValid(userid)); @@ -533,6 +534,11 @@ SetOuterUserId(Oid userid) /* We force the effective user ID to match, too */ CurrentUserId = userid; + + /* Also update the is_superuser GUC to match OuterUserId's property */ + SetConfigOption("is_superuser", + is_superuser ? "on" : "off", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); } @@ -546,6 +552,12 @@ GetSessionUserId(void) return SessionUserId; } +bool +GetSessionUserIsSuperuser(void) +{ + Assert(OidIsValid(SessionUserId)); + return SessionUserIsSuperuser; +} static void SetSessionUserId(Oid userid, bool is_superuser) @@ -554,11 +566,6 @@ SetSessionUserId(Oid userid, bool is_superuser) Assert(OidIsValid(userid)); SessionUserId = userid; SessionUserIsSuperuser = is_superuser; - SetRoleIsActive = false; - - /* We force the effective user IDs to match, too */ - OuterUserId = userid; - CurrentUserId = userid; } /* @@ -572,7 +579,8 @@ GetSystemUser(void) } /* - * GetAuthenticatedUserId - get the authenticated user ID + * GetAuthenticatedUserId/SetAuthenticatedUserId - get/set the authenticated + * user ID */ Oid GetAuthenticatedUserId(void) @@ -581,6 +589,32 @@ GetAuthenticatedUserId(void) return AuthenticatedUserId; } +/* + * Return whether the authenticated user was superuser at connection start. + */ +bool +GetAuthenticatedUserIsSuperuser(void) +{ + Assert(OidIsValid(AuthenticatedUserId)); + return AuthenticatedUserIsSuperuser; +} + +void +SetAuthenticatedUserId(Oid userid, bool is_superuser) +{ + Assert(OidIsValid(userid)); + + /* call only once */ + Assert(!OidIsValid(AuthenticatedUserId)); + + AuthenticatedUserId = userid; + AuthenticatedUserIsSuperuser = is_superuser; + + /* Also mark our PGPROC entry with the authenticated user id */ + /* (We assume this is an atomic store so no lock is needed) */ + MyProc->roleId = userid; +} + /* * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID @@ -730,6 +764,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid) HeapTuple roleTup; Form_pg_authid rform; char *rname; + bool is_superuser; /* * Don't do scans if we're bootstrapping, none of the system catalogs @@ -737,9 +772,6 @@ InitializeSessionUserId(const char *rolename, Oid roleid) */ Assert(!IsBootstrapProcessingMode()); - /* call only once */ - Assert(!OidIsValid(AuthenticatedUserId)); - /* * Make sure syscache entries are flushed for recent catalog changes. This * allows us to find roles that were created on-the-fly during @@ -747,36 +779,52 @@ InitializeSessionUserId(const char *rolename, Oid roleid) */ AcceptInvalidationMessages(); + /* + * Look up the role, either by name if that's given or by OID if not. + * Normally we have to fail if we don't find it, but in parallel workers + * just return without doing anything: all the critical work has been done + * already. The upshot of that is that if the role has been deleted, we + * will not enforce its rolconnlimit against parallel workers anymore. + */ if (rolename != NULL) { roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); if (!HeapTupleIsValid(roleTup)) + { + if (InitializingParallelWorker) + return; ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" does not exist", rolename))); + } } else { roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (!HeapTupleIsValid(roleTup)) + { + if (InitializingParallelWorker) + return; ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role with OID %u does not exist", roleid))); + } } rform = (Form_pg_authid) GETSTRUCT(roleTup); roleid = rform->oid; rname = NameStr(rform->rolname); + is_superuser = rform->rolsuper; - AuthenticatedUserId = roleid; - AuthenticatedUserIsSuperuser = rform->rolsuper; + /* In a parallel worker, ParallelWorkerMain already set these variables */ + if (!InitializingParallelWorker) + { + SetAuthenticatedUserId(roleid, is_superuser); - /* This sets OuterUserId/CurrentUserId too */ - SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); - - /* Also mark our PGPROC entry with the authenticated user id */ - /* (We assume this is an atomic store so no lock is needed) */ - MyProc->roleId = roleid; + /* Set SessionUserId and related variables via the GUC mechanisms */ + SetConfigOption("session_authorization", rname, + PGC_BACKEND, PGC_S_OVERRIDE); + } /* * These next checks are not enforced when in standalone mode, so that @@ -805,7 +853,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid) * just document that the connection limit is approximate. */ if (rform->rolconnlimit >= 0 && - !AuthenticatedUserIsSuperuser && + !is_superuser && CountUserBackends(roleid) > rform->rolconnlimit) ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), @@ -813,13 +861,6 @@ InitializeSessionUserId(const char *rolename, Oid roleid) rname))); } - /* Record username and superuser status as GUC settings too */ - SetConfigOption("session_authorization", rname, - PGC_BACKEND, PGC_S_OVERRIDE); - SetConfigOption("is_superuser", - AuthenticatedUserIsSuperuser ? "on" : "off", - PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); - ReleaseSysCache(roleTup); } @@ -842,15 +883,18 @@ InitializeSessionUserIdStandalone(void) AuthenticatedUserId = BOOTSTRAP_SUPERUSERID; AuthenticatedUserIsSuperuser = true; - SetSessionUserId(BOOTSTRAP_SUPERUSERID, true); - /* - * XXX This should set SetConfigOption("session_authorization"), too. - * Since we don't, C code will get NULL, and current_setting() will get an - * empty string. + * XXX Ideally we'd do this via SetConfigOption("session_authorization"), + * but we lack the role name needed to do that, and we can't fetch it + * because one reason for this special case is to be able to start up even + * if something's happened to the BOOTSTRAP_SUPERUSERID's pg_authid row. + * Since we don't set the GUC itself, C code will see the value as NULL, + * and current_setting() will report an empty string within this session. */ - SetConfigOption("is_superuser", "on", - PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); + SetSessionAuthorization(BOOTSTRAP_SUPERUSERID, true); + + /* We could do SetConfigOption("role"), but let's be consistent */ + SetCurrentRoleId(InvalidOid, false); } /* @@ -896,33 +940,21 @@ system_user(PG_FUNCTION_ARGS) /* * Change session auth ID while running * - * Only a superuser may set auth ID to something other than himself. Note - * that in case of multiple SETs in a single session, the original userid's - * superuserness is what matters. But we set the GUC variable is_superuser - * to indicate whether the *current* session userid is a superuser. - * - * Note: this is not an especially clean place to do the permission check. - * It's OK because the check does not require catalog access and can't - * fail during an end-of-transaction GUC reversion, but we may someday - * have to push it up into assign_session_authorization. + * The SQL standard says that SET SESSION AUTHORIZATION implies SET ROLE NONE. + * We mechanize that at higher levels not here, because this is the GUC + * assign hook for "session_authorization", and it must be commutative with + * SetCurrentRoleId (the hook for "role") because guc.c provides no guarantees + * which will run first during cases such as transaction rollback. Therefore, + * we update derived state (OuterUserId/CurrentUserId/is_superuser) only if + * !SetRoleIsActive. */ void SetSessionAuthorization(Oid userid, bool is_superuser) { - /* Must have authenticated already, else can't make permission check */ - Assert(OidIsValid(AuthenticatedUserId)); - - if (userid != AuthenticatedUserId && - !AuthenticatedUserIsSuperuser) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied to set session authorization"))); - SetSessionUserId(userid, is_superuser); - SetConfigOption("is_superuser", - is_superuser ? "on" : "off", - PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); + if (!SetRoleIsActive) + SetOuterUserId(userid, is_superuser); } /* @@ -958,28 +990,25 @@ SetCurrentRoleId(Oid roleid, bool is_superuser) /* * Get correct info if it's SET ROLE NONE * - * If SessionUserId hasn't been set yet, just do nothing --- the eventual - * SetSessionUserId call will fix everything. This is needed since we - * will get called during GUC initialization. + * If SessionUserId hasn't been set yet, do nothing beyond updating + * SetRoleIsActive --- the eventual SetSessionAuthorization call will + * update the derived state. This is needed since we will get called + * during GUC initialization. */ if (!OidIsValid(roleid)) { + SetRoleIsActive = false; + if (!OidIsValid(SessionUserId)) return; roleid = SessionUserId; is_superuser = SessionUserIsSuperuser; - - SetRoleIsActive = false; } else SetRoleIsActive = true; - SetOuterUserId(roleid); - - SetConfigOption("is_superuser", - is_superuser ? "on" : "off", - PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); + SetOuterUserId(roleid, is_superuser); } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 349e194694..6083c095c2 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3915,6 +3915,9 @@ set_config_option_ext(const char *name, const char *value, case PGC_STRING: { struct config_string *conf = (struct config_string *) record; + GucContext orig_context = context; + GucSource orig_source = source; + Oid orig_srole = srole; #define newval (newval_union.stringval) @@ -4000,6 +4003,36 @@ set_config_option_ext(const char *name, const char *value, set_guc_source(&conf->gen, source); conf->gen.scontext = context; conf->gen.srole = srole; + + /* + * Ugly hack: during SET session_authorization, forcibly + * do SET ROLE NONE with the same context/source/etc, so + * that the effects will have identical lifespan. This is + * required by the SQL spec, and it's not possible to do + * it within the variable's check hook or assign hook + * because our APIs for those don't pass enough info. + * However, don't do it if is_reload: in that case we + * expect that if "role" isn't supposed to be default, it + * has been or will be set by a separate reload action. + * + * A fine point: for RESET session_authorization, we do + * "RESET role" not "SET ROLE NONE" (by passing down NULL + * rather than "none" for the value). This would have the + * same effects in typical cases, but if the reset value + * of "role" is not "none" it seems better to revert to + * that. + */ + if (!is_reload && + strcmp(conf->gen.name, "session_authorization") == 0) + (void) set_config_option_ext("role", + value ? "none" : NULL, + orig_context, + orig_source, + orig_srole, + action, + true, + elevel, + false); } if (makeDefault) @@ -5667,12 +5700,6 @@ can_skip_gucvar(struct config_generic *gconf) * mechanisms (if indeed they aren't compile-time constants). So we may * always skip these. * - * Role must be handled specially because its current value can be an - * invalid value (for instance, if someone dropped the role since we set - * it). So if we tried to serialize it normally, we might get a failure. - * We skip it here, and use another mechanism to ensure the worker has the - * right value. - * * For all other GUCs, we skip if the GUC has its compiled-in default * value (i.e., source == PGC_S_DEFAULT). On the leader side, this means * we don't send GUCs that have their default values, which typically @@ -5681,8 +5708,8 @@ can_skip_gucvar(struct config_generic *gconf) * comments in RestoreGUCState for more info. */ return gconf->context == PGC_POSTMASTER || - gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT || - strcmp(gconf->name, "role") == 0; + gconf->context == PGC_INTERNAL || + gconf->source == PGC_S_DEFAULT; } /* diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 14bd574fc2..14b26f8f03 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -356,7 +356,10 @@ extern char *GetUserNameFromId(Oid roleid, bool noerr); extern Oid GetUserId(void); extern Oid GetOuterUserId(void); extern Oid GetSessionUserId(void); +extern bool GetSessionUserIsSuperuser(void); extern Oid GetAuthenticatedUserId(void); +extern bool GetAuthenticatedUserIsSuperuser(void); +extern void SetAuthenticatedUserId(Oid userid, bool is_superuser); extern void GetUserIdAndSecContext(Oid *userid, int *sec_context); extern void SetUserIdAndSecContext(Oid userid, int sec_context); extern bool InLocalUserIdChange(void); diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index fbb0489a4f..5b9dba7b32 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -141,6 +141,73 @@ SET ROLE pg_read_all_stats; -- fail, granted without SET option ERROR: permission denied to set role "pg_read_all_stats" RESET ROLE; RESET SESSION AUTHORIZATION; +-- test interaction of SET SESSION AUTHORIZATION and SET ROLE, +-- as well as propagation of these settings to parallel workers +GRANT regress_priv_user9 TO regress_priv_user8; +SET SESSION AUTHORIZATION regress_priv_user8; +SET ROLE regress_priv_user9; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +--------------------+--------------------+--------------------+-------------------- + regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9 +(1 row) + +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +--------------------+--------------------+--------------------+-------------------- + regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9 +(1 row) + +BEGIN; +SET SESSION AUTHORIZATION regress_priv_user10; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +---------------------+---------------------+---------------------+------ + regress_priv_user10 | regress_priv_user10 | regress_priv_user10 | none +(1 row) + +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +---------------------+---------------------+---------------------+------ + regress_priv_user10 | regress_priv_user10 | regress_priv_user10 | none +(1 row) + +ROLLBACK; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +--------------------+--------------------+--------------------+-------------------- + regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9 +(1 row) + +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; + session_user | current_role | current_user | role +--------------------+--------------------+--------------------+-------------------- + regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9 +(1 row) + +RESET SESSION AUTHORIZATION; +-- session_user at this point is installation-dependent +SET debug_parallel_query = 0; +SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role; + c_r_ok | c_u_ok | role +--------+--------+------ + t | t | none +(1 row) + +SET debug_parallel_query = 1; +SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role; + c_r_ok | c_u_ok | role +--------+--------+------ + t | t | none +(1 row) + +RESET debug_parallel_query; REVOKE pg_read_all_settings FROM regress_priv_user8; DROP USER regress_priv_user10; DROP USER regress_priv_user9; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 3f68cafcd1..249df17a58 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -124,6 +124,39 @@ SET ROLE pg_read_all_stats; -- fail, granted without SET option RESET ROLE; RESET SESSION AUTHORIZATION; + +-- test interaction of SET SESSION AUTHORIZATION and SET ROLE, +-- as well as propagation of these settings to parallel workers +GRANT regress_priv_user9 TO regress_priv_user8; + +SET SESSION AUTHORIZATION regress_priv_user8; +SET ROLE regress_priv_user9; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; + +BEGIN; +SET SESSION AUTHORIZATION regress_priv_user10; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; +ROLLBACK; +SET debug_parallel_query = 0; +SELECT session_user, current_role, current_user, current_setting('role') as role; +SET debug_parallel_query = 1; +SELECT session_user, current_role, current_user, current_setting('role') as role; + +RESET SESSION AUTHORIZATION; +-- session_user at this point is installation-dependent +SET debug_parallel_query = 0; +SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role; +SET debug_parallel_query = 1; +SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role; + +RESET debug_parallel_query; + REVOKE pg_read_all_settings FROM regress_priv_user8; DROP USER regress_priv_user10;