Skip to content

Commit c58b0c4

Browse files
committed
Exclude parallel workers from connection privilege/limit checks.
Cause parallel workers to not check datallowconn, rolcanlogin, and ACL_CONNECT privileges. The leader already checked these things (except for rolcanlogin which might have been checked for a different role). Re-checking can accomplish little except to induce unexpected failures in applications that might not even be aware that their query has been parallelized. We already had the principle that parallel workers rely on their leader to pass a valid set of authorization information, so this change just extends that a bit further. Also, modify the ReservedConnections, datconnlimit and rolconnlimit logic so that these limits are only enforced against regular backends, and only regular backends are counted while checking if the limits were already reached. Previously, background processes that had an assigned database or role were subject to these limits (with rather random exclusions for autovac workers and walsenders), and the set of existing processes that counted against each limit was quite haphazard as well. The point of these limits, AFAICS, is to ensure the availability of PGPROC slots for regular backends. Since all other types of processes have their own separate pools of PGPROC slots, it makes no sense either to enforce these limits against them or to count them while enforcing the limit. While edge-case failures of these sorts have been possible for a long time, the problem got a good deal worse with commit 5a2fed9 (CVE-2024-10978), which caused parallel workers to make some of these checks using the leader's current role where before we had used its AuthenticatedUserId, thus allowing parallel queries to fail after SET ROLE. The previous behavior was fairly accidental and I have no desire to return to it. This patch includes reverting 73c9f91, which was an emergency hack to suppress these same checks in some cases. It wasn't complete, as shown by a recent bug report from Laurenz Albe. We can also revert fd4d93d and 4922173, which hacked around the same problems in one regression test. In passing, remove the special case for autovac workers in CheckMyDatabase; it seems cleaner to have AutoVacWorkerMain pass the INIT_PG_OVERRIDE_ALLOW_CONNS flag, now that that does what's needed. Like 5a2fed9, back-patch to supported branches (which sadly no longer includes v12). Discussion: https://postgr.es/m/1808397.1735156190@sss.pgh.pa.us
1 parent 536acda commit c58b0c4

File tree

9 files changed

+68
-76
lines changed

9 files changed

+68
-76
lines changed

src/backend/access/transam/parallel.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -1414,10 +1414,15 @@ ParallelWorkerMain(Datum main_arg)
14141414
fps->session_user_is_superuser);
14151415
SetCurrentRoleId(fps->outer_user_id, fps->role_is_superuser);
14161416

1417-
/* Restore database connection. */
1417+
/*
1418+
* Restore database connection. We skip connection authorization checks,
1419+
* reasoning that (a) the leader checked these things when it started, and
1420+
* (b) we do not want parallel mode to cause these failures, because that
1421+
* would make use of parallel query plans not transparent to applications.
1422+
*/
14181423
BackgroundWorkerInitializeConnectionByOid(fps->database_id,
14191424
fps->authenticated_user_id,
1420-
0);
1425+
BGWORKER_BYPASS_ALLOWCONN);
14211426

14221427
/*
14231428
* Set the client encoding to the database encoding, since that is what

src/backend/access/transam/twophase.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid,
481481
proc->databaseId = databaseid;
482482
proc->roleId = owner;
483483
proc->tempNamespaceId = InvalidOid;
484-
proc->isBackgroundWorker = false;
484+
proc->isBackgroundWorker = true;
485485
proc->lwWaiting = LW_WS_NOT_WAITING;
486486
proc->lwWaitMode = 0;
487487
proc->waitLock = NULL;

src/backend/postmaster/autovacuum.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -1699,12 +1699,14 @@ AutoVacWorkerMain(int argc, char *argv[])
16991699
pgstat_report_autovac(dbid);
17001700

17011701
/*
1702-
* Connect to the selected database
1702+
* Connect to the selected database, specifying no particular user,
1703+
* and ignoring datallowconn. Collect the database's name for
1704+
* display.
17031705
*
17041706
* Note: if we have selected a just-deleted database (due to using
17051707
* stale stats info), we'll fail and exit here.
17061708
*/
1707-
InitPostgres(NULL, dbid, NULL, InvalidOid, dbname, false);
1709+
InitPostgres(NULL, dbid, NULL, InvalidOid, dbname, true);
17081710
SetProcessingMode(NormalProcessing);
17091711
set_ps_display(dbname);
17101712
ereport(DEBUG1,

src/backend/storage/ipc/procarray.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -3668,8 +3668,7 @@ CountDBBackends(Oid databaseid)
36683668
}
36693669

36703670
/*
3671-
* CountDBConnections --- counts database backends ignoring any background
3672-
* worker processes
3671+
* CountDBConnections --- counts database backends (only regular backends)
36733672
*/
36743673
int
36753674
CountDBConnections(Oid databaseid)
@@ -3741,6 +3740,7 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
37413740

37423741
/*
37433742
* CountUserBackends --- count backends that are used by specified user
3743+
* (only regular backends, not any type of background worker)
37443744
*/
37453745
int
37463746
CountUserBackends(Oid roleid)

src/backend/storage/lmgr/proc.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ InitProcess(void)
393393
MyProc->databaseId = InvalidOid;
394394
MyProc->roleId = InvalidOid;
395395
MyProc->tempNamespaceId = InvalidOid;
396-
MyProc->isBackgroundWorker = IsBackgroundWorker;
396+
MyProc->isBackgroundWorker = !AmRegularBackendProcess();
397397
MyProc->delayChkpt = false;
398398
MyProc->delayChkptEnd = false;
399399
MyProc->statusFlags = 0;
@@ -579,7 +579,7 @@ InitAuxiliaryProcess(void)
579579
MyProc->databaseId = InvalidOid;
580580
MyProc->roleId = InvalidOid;
581581
MyProc->tempNamespaceId = InvalidOid;
582-
MyProc->isBackgroundWorker = IsBackgroundWorker;
582+
MyProc->isBackgroundWorker = true;
583583
MyProc->delayChkpt = false;
584584
MyProc->delayChkptEnd = false;
585585
MyProc->statusFlags = 0;

src/backend/utils/init/miscinit.c

+36-39
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,16 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
720720
char *rname;
721721
bool is_superuser;
722722

723+
/*
724+
* In a parallel worker, we don't have to do anything here.
725+
* ParallelWorkerMain already set our output variables, and we aren't
726+
* going to enforce either rolcanlogin or rolconnlimit. Furthermore, we
727+
* don't really want to perform a catalog lookup for the role: we don't
728+
* want to fail if it's been dropped.
729+
*/
730+
if (InitializingParallelWorker)
731+
return;
732+
723733
/*
724734
* Don't do scans if we're bootstrapping, none of the system catalogs
725735
* exist yet, and they should be owned by postgres anyway.
@@ -735,68 +745,52 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
735745

736746
/*
737747
* Look up the role, either by name if that's given or by OID if not.
738-
* Normally we have to fail if we don't find it, but in parallel workers
739-
* just return without doing anything: all the critical work has been done
740-
* already. The upshot of that is that if the role has been deleted, we
741-
* will not enforce its rolconnlimit against parallel workers anymore.
742748
*/
743749
if (rolename != NULL)
744750
{
745751
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
746752
if (!HeapTupleIsValid(roleTup))
747-
{
748-
if (InitializingParallelWorker)
749-
return;
750753
ereport(FATAL,
751754
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
752755
errmsg("role \"%s\" does not exist", rolename)));
753-
}
754756
}
755757
else
756758
{
757759
roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
758760
if (!HeapTupleIsValid(roleTup))
759-
{
760-
if (InitializingParallelWorker)
761-
return;
762761
ereport(FATAL,
763762
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
764763
errmsg("role with OID %u does not exist", roleid)));
765-
}
766764
}
767765

768766
rform = (Form_pg_authid) GETSTRUCT(roleTup);
769767
roleid = rform->oid;
770768
rname = NameStr(rform->rolname);
771769
is_superuser = rform->rolsuper;
772770

773-
/* In a parallel worker, ParallelWorkerMain already set these variables */
774-
if (!InitializingParallelWorker)
775-
{
776-
SetAuthenticatedUserId(roleid, is_superuser);
771+
SetAuthenticatedUserId(roleid, is_superuser);
777772

778-
/*
779-
* Set SessionUserId and related variables, including "role", via the
780-
* GUC mechanisms.
781-
*
782-
* Note: ideally we would use PGC_S_DYNAMIC_DEFAULT here, so that
783-
* session_authorization could subsequently be changed from
784-
* pg_db_role_setting entries. Instead, session_authorization in
785-
* pg_db_role_setting has no effect. Changing that would require
786-
* solving two problems:
787-
*
788-
* 1. If pg_db_role_setting has values for both session_authorization
789-
* and role, we could not be sure which order those would be applied
790-
* in, and it would matter.
791-
*
792-
* 2. Sites may have years-old session_authorization entries. There's
793-
* not been any particular reason to remove them. Ending the dormancy
794-
* of those entries could seriously change application behavior, so
795-
* only a major release should do that.
796-
*/
797-
SetConfigOption("session_authorization", rname,
798-
PGC_BACKEND, PGC_S_OVERRIDE);
799-
}
773+
/*
774+
* Set SessionUserId and related variables, including "role", via the GUC
775+
* mechanisms.
776+
*
777+
* Note: ideally we would use PGC_S_DYNAMIC_DEFAULT here, so that
778+
* session_authorization could subsequently be changed from
779+
* pg_db_role_setting entries. Instead, session_authorization in
780+
* pg_db_role_setting has no effect. Changing that would require solving
781+
* two problems:
782+
*
783+
* 1. If pg_db_role_setting has values for both session_authorization and
784+
* role, we could not be sure which order those would be applied in, and
785+
* it would matter.
786+
*
787+
* 2. Sites may have years-old session_authorization entries. There's not
788+
* been any particular reason to remove them. Ending the dormancy of
789+
* those entries could seriously change application behavior, so only a
790+
* major release should do that.
791+
*/
792+
SetConfigOption("session_authorization", rname,
793+
PGC_BACKEND, PGC_S_OVERRIDE);
800794

801795
/*
802796
* These next checks are not enforced when in standalone mode, so that
@@ -815,7 +809,9 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
815809
rname)));
816810

817811
/*
818-
* Check connection limit for this role.
812+
* Check connection limit for this role. We enforce the limit only
813+
* for regular backends, since other process types have their own
814+
* PGPROC pools.
819815
*
820816
* There is a race condition here --- we create our PGPROC before
821817
* checking for other PGPROCs. If two backends did this at about the
@@ -825,6 +821,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
825821
* just document that the connection limit is approximate.
826822
*/
827823
if (rform->rolconnlimit >= 0 &&
824+
AmRegularBackendProcess() &&
828825
!is_superuser &&
829826
CountUserBackends(roleid) > rform->rolconnlimit)
830827
ereport(FATAL,

src/backend/utils/init/postinit.c

+13-27
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
#include "access/genam.h"
2323
#include "access/heapam.h"
2424
#include "access/htup_details.h"
25-
#include "access/parallel.h"
2625
#include "access/session.h"
2726
#include "access/sysattr.h"
2827
#include "access/tableam.h"
@@ -332,13 +331,13 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
332331
* These checks are not enforced when in standalone mode, so that there is
333332
* a way to recover from disabling all access to all databases, for
334333
* example "UPDATE pg_database SET datallowconn = false;".
335-
*
336-
* We do not enforce them for autovacuum worker processes either.
337334
*/
338-
if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess())
335+
if (IsUnderPostmaster)
339336
{
340337
/*
341338
* Check that the database is currently allowing connections.
339+
* (Background processes can override this test and the next one by
340+
* setting override_allow_connections.)
342341
*/
343342
if (!dbform->datallowconn && !override_allow_connections)
344343
ereport(FATAL,
@@ -351,7 +350,7 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
351350
* is redundant, but since we have the flag, might as well check it
352351
* and save a few cycles.)
353352
*/
354-
if (!am_superuser &&
353+
if (!am_superuser && !override_allow_connections &&
355354
pg_database_aclcheck(MyDatabaseId, GetUserId(),
356355
ACL_CONNECT) != ACLCHECK_OK)
357356
ereport(FATAL,
@@ -360,7 +359,9 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
360359
errdetail("User does not have CONNECT privilege.")));
361360

362361
/*
363-
* Check connection limit for this database.
362+
* Check connection limit for this database. We enforce the limit
363+
* only for regular backends, since other process types have their own
364+
* PGPROC pools.
364365
*
365366
* There is a race condition here --- we create our PGPROC before
366367
* checking for other PGPROCs. If two backends did this at about the
@@ -370,6 +371,7 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
370371
* just document that the connection limit is approximate.
371372
*/
372373
if (dbform->datconnlimit >= 0 &&
374+
AmRegularBackendProcess() &&
373375
!am_superuser &&
374376
CountDBConnections(MyDatabaseId) > dbform->datconnlimit)
375377
ereport(FATAL,
@@ -773,23 +775,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
773775
else
774776
{
775777
InitializeSessionUserId(username, useroid);
776-
777-
/*
778-
* In a parallel worker, set am_superuser based on the
779-
* authenticated user ID, not the current role. This is pretty
780-
* dubious but it matches our historical behavior. Note that this
781-
* value of am_superuser is used only for connection-privilege
782-
* checks here and in CheckMyDatabase (we won't reach
783-
* process_startup_options in a background worker).
784-
*
785-
* In other cases, there's been no opportunity for the current
786-
* role to diverge from the authenticated user ID yet, so we can
787-
* just rely on superuser() and avoid an extra catalog lookup.
788-
*/
789-
if (InitializingParallelWorker)
790-
am_superuser = superuser_arg(GetAuthenticatedUserId());
791-
else
792-
am_superuser = superuser();
778+
am_superuser = superuser();
793779
}
794780
}
795781
else
@@ -830,11 +816,11 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
830816
}
831817

832818
/*
833-
* The last few connection slots are reserved for superusers. Replication
834-
* connections are drawn from slots reserved with max_wal_senders and not
835-
* limited by max_connections or superuser_reserved_connections.
819+
* The last few regular connection slots are reserved for superusers. We
820+
* do not apply this limit to background processes, since they all have
821+
* their own pools of PGPROC slots.
836822
*/
837-
if (!am_superuser && !am_walsender &&
823+
if (AmRegularBackendProcess() && !am_superuser &&
838824
ReservedBackends > 0 &&
839825
!HaveNFreeProcs(ReservedBackends))
840826
ereport(FATAL,

src/include/miscadmin.h

+2
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ typedef enum BackendType
340340

341341
extern BackendType MyBackendType;
342342

343+
#define AmRegularBackendProcess() (MyBackendType == B_BACKEND)
344+
343345
extern const char *GetBackendTypeDesc(BackendType backendType);
344346

345347
extern void SetDatabasePath(const char *path);

src/include/storage/proc.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ struct PGPROC
170170
Oid tempNamespaceId; /* OID of temp schema this backend is
171171
* using */
172172

173-
bool isBackgroundWorker; /* true if background worker. */
173+
bool isBackgroundWorker; /* true if not a regular backend. */
174174

175175
/*
176176
* While in hot standby mode, shows that a conflict signal has been sent

0 commit comments

Comments
 (0)