Skip to content

Commit 15b4c46

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 14141bb commit 15b4c46

File tree

11 files changed

+80
-91
lines changed

11 files changed

+80
-91
lines changed

src/backend/access/transam/parallel.c

+8-2
Original file line numberDiff line numberDiff line change
@@ -1419,10 +1419,16 @@ ParallelWorkerMain(Datum main_arg)
14191419
fps->session_user_is_superuser);
14201420
SetCurrentRoleId(fps->outer_user_id, fps->role_is_superuser);
14211421

1422-
/* Restore database connection. */
1422+
/*
1423+
* Restore database connection. We skip connection authorization checks,
1424+
* reasoning that (a) the leader checked these things when it started, and
1425+
* (b) we do not want parallel mode to cause these failures, because that
1426+
* would make use of parallel query plans not transparent to applications.
1427+
*/
14231428
BackgroundWorkerInitializeConnectionByOid(fps->database_id,
14241429
fps->authenticated_user_id,
1425-
0);
1430+
BGWORKER_BYPASS_ALLOWCONN |
1431+
BGWORKER_BYPASS_ROLELOGINCHECK);
14261432

14271433
/*
14281434
* 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
@@ -466,7 +466,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid,
466466
proc->databaseId = databaseid;
467467
proc->roleId = owner;
468468
proc->tempNamespaceId = InvalidOid;
469-
proc->isBackgroundWorker = false;
469+
proc->isBackgroundWorker = true;
470470
proc->lwWaiting = LW_WS_NOT_WAITING;
471471
proc->lwWaitMode = 0;
472472
proc->waitLock = NULL;

src/backend/postmaster/autovacuum.c

+6-2
Original file line numberDiff line numberDiff line change
@@ -1549,12 +1549,16 @@ AutoVacWorkerMain(char *startup_data, size_t startup_data_len)
15491549
pgstat_report_autovac(dbid);
15501550

15511551
/*
1552-
* Connect to the selected database, specifying no particular user
1552+
* Connect to the selected database, specifying no particular user,
1553+
* and ignoring datallowconn. Collect the database's name for
1554+
* display.
15531555
*
15541556
* Note: if we have selected a just-deleted database (due to using
15551557
* stale stats info), we'll fail and exit here.
15561558
*/
1557-
InitPostgres(NULL, dbid, NULL, InvalidOid, 0, dbname);
1559+
InitPostgres(NULL, dbid, NULL, InvalidOid,
1560+
INIT_PG_OVERRIDE_ALLOW_CONNS,
1561+
dbname);
15581562
SetProcessingMode(NormalProcessing);
15591563
set_ps_display(dbname);
15601564
ereport(DEBUG1,

src/backend/postmaster/postmaster.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -4156,7 +4156,7 @@ BackgroundWorkerInitializeConnection(const char *dbname, const char *username, u
41564156
BackgroundWorker *worker = MyBgworkerEntry;
41574157
bits32 init_flags = 0; /* never honor session_preload_libraries */
41584158

4159-
/* ignore datallowconn? */
4159+
/* ignore datallowconn and ACL_CONNECT? */
41604160
if (flags & BGWORKER_BYPASS_ALLOWCONN)
41614161
init_flags |= INIT_PG_OVERRIDE_ALLOW_CONNS;
41624162
/* ignore rolcanlogin? */
@@ -4190,7 +4190,7 @@ BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags)
41904190
BackgroundWorker *worker = MyBgworkerEntry;
41914191
bits32 init_flags = 0; /* never honor session_preload_libraries */
41924192

4193-
/* ignore datallowconn? */
4193+
/* ignore datallowconn and ACL_CONNECT? */
41944194
if (flags & BGWORKER_BYPASS_ALLOWCONN)
41954195
init_flags |= INIT_PG_OVERRIDE_ALLOW_CONNS;
41964196
/* ignore rolcanlogin? */

src/backend/storage/ipc/procarray.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -3621,8 +3621,7 @@ CountDBBackends(Oid databaseid)
36213621
}
36223622

36233623
/*
3624-
* CountDBConnections --- counts database backends ignoring any background
3625-
* worker processes
3624+
* CountDBConnections --- counts database backends (only regular backends)
36263625
*/
36273626
int
36283627
CountDBConnections(Oid databaseid)
@@ -3694,6 +3693,7 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
36943693

36953694
/*
36963695
* CountUserBackends --- count backends that are used by specified user
3696+
* (only regular backends, not any type of background worker)
36973697
*/
36983698
int
36993699
CountUserBackends(Oid roleid)

src/backend/storage/lmgr/proc.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ InitProcess(void)
394394
MyProc->databaseId = InvalidOid;
395395
MyProc->roleId = InvalidOid;
396396
MyProc->tempNamespaceId = InvalidOid;
397-
MyProc->isBackgroundWorker = AmBackgroundWorkerProcess();
397+
MyProc->isBackgroundWorker = !AmRegularBackendProcess();
398398
MyProc->delayChkptFlags = 0;
399399
MyProc->statusFlags = 0;
400400
/* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */
@@ -590,7 +590,7 @@ InitAuxiliaryProcess(void)
590590
MyProc->databaseId = InvalidOid;
591591
MyProc->roleId = InvalidOid;
592592
MyProc->tempNamespaceId = InvalidOid;
593-
MyProc->isBackgroundWorker = AmBackgroundWorkerProcess();
593+
MyProc->isBackgroundWorker = true;
594594
MyProc->delayChkptFlags = 0;
595595
MyProc->statusFlags = 0;
596596
MyProc->lwWaiting = LW_WS_NOT_WAITING;

src/backend/utils/init/miscinit.c

+43-41
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,27 @@ has_rolreplication(Oid roleid)
753753
* Initialize user identity during normal backend startup
754754
*/
755755
void
756-
InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_check)
756+
InitializeSessionUserId(const char *rolename, Oid roleid,
757+
bool bypass_login_check)
757758
{
758759
HeapTuple roleTup;
759760
Form_pg_authid rform;
760761
char *rname;
761762
bool is_superuser;
762763

764+
/*
765+
* In a parallel worker, we don't have to do anything here.
766+
* ParallelWorkerMain already set our output variables, and we aren't
767+
* going to enforce either rolcanlogin or rolconnlimit. Furthermore, we
768+
* don't really want to perform a catalog lookup for the role: we don't
769+
* want to fail if it's been dropped.
770+
*/
771+
if (InitializingParallelWorker)
772+
{
773+
Assert(bypass_login_check);
774+
return;
775+
}
776+
763777
/*
764778
* Don't do scans if we're bootstrapping, none of the system catalogs
765779
* exist yet, and they should be owned by postgres anyway.
@@ -775,68 +789,52 @@ InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_chec
775789

776790
/*
777791
* Look up the role, either by name if that's given or by OID if not.
778-
* Normally we have to fail if we don't find it, but in parallel workers
779-
* just return without doing anything: all the critical work has been done
780-
* already. The upshot of that is that if the role has been deleted, we
781-
* will not enforce its rolconnlimit against parallel workers anymore.
782792
*/
783793
if (rolename != NULL)
784794
{
785795
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
786796
if (!HeapTupleIsValid(roleTup))
787-
{
788-
if (InitializingParallelWorker)
789-
return;
790797
ereport(FATAL,
791798
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
792799
errmsg("role \"%s\" does not exist", rolename)));
793-
}
794800
}
795801
else
796802
{
797803
roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
798804
if (!HeapTupleIsValid(roleTup))
799-
{
800-
if (InitializingParallelWorker)
801-
return;
802805
ereport(FATAL,
803806
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
804807
errmsg("role with OID %u does not exist", roleid)));
805-
}
806808
}
807809

808810
rform = (Form_pg_authid) GETSTRUCT(roleTup);
809811
roleid = rform->oid;
810812
rname = NameStr(rform->rolname);
811813
is_superuser = rform->rolsuper;
812814

813-
/* In a parallel worker, ParallelWorkerMain already set these variables */
814-
if (!InitializingParallelWorker)
815-
{
816-
SetAuthenticatedUserId(roleid);
815+
SetAuthenticatedUserId(roleid);
817816

818-
/*
819-
* Set SessionUserId and related variables, including "role", via the
820-
* GUC mechanisms.
821-
*
822-
* Note: ideally we would use PGC_S_DYNAMIC_DEFAULT here, so that
823-
* session_authorization could subsequently be changed from
824-
* pg_db_role_setting entries. Instead, session_authorization in
825-
* pg_db_role_setting has no effect. Changing that would require
826-
* solving two problems:
827-
*
828-
* 1. If pg_db_role_setting has values for both session_authorization
829-
* and role, we could not be sure which order those would be applied
830-
* in, and it would matter.
831-
*
832-
* 2. Sites may have years-old session_authorization entries. There's
833-
* not been any particular reason to remove them. Ending the dormancy
834-
* of those entries could seriously change application behavior, so
835-
* only a major release should do that.
836-
*/
837-
SetConfigOption("session_authorization", rname,
838-
PGC_BACKEND, PGC_S_OVERRIDE);
839-
}
817+
/*
818+
* Set SessionUserId and related variables, including "role", via the GUC
819+
* mechanisms.
820+
*
821+
* Note: ideally we would use PGC_S_DYNAMIC_DEFAULT here, so that
822+
* session_authorization could subsequently be changed from
823+
* pg_db_role_setting entries. Instead, session_authorization in
824+
* pg_db_role_setting has no effect. Changing that would require solving
825+
* two problems:
826+
*
827+
* 1. If pg_db_role_setting has values for both session_authorization and
828+
* role, we could not be sure which order those would be applied in, and
829+
* it would matter.
830+
*
831+
* 2. Sites may have years-old session_authorization entries. There's not
832+
* been any particular reason to remove them. Ending the dormancy of
833+
* those entries could seriously change application behavior, so only a
834+
* major release should do that.
835+
*/
836+
SetConfigOption("session_authorization", rname,
837+
PGC_BACKEND, PGC_S_OVERRIDE);
840838

841839
/*
842840
* These next checks are not enforced when in standalone mode, so that
@@ -846,7 +844,8 @@ InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_chec
846844
if (IsUnderPostmaster)
847845
{
848846
/*
849-
* Is role allowed to login at all?
847+
* Is role allowed to login at all? (But background workers can
848+
* override this by setting bypass_login_check.)
850849
*/
851850
if (!bypass_login_check && !rform->rolcanlogin)
852851
ereport(FATAL,
@@ -855,7 +854,9 @@ InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_chec
855854
rname)));
856855

857856
/*
858-
* Check connection limit for this role.
857+
* Check connection limit for this role. We enforce the limit only
858+
* for regular backends, since other process types have their own
859+
* PGPROC pools.
859860
*
860861
* There is a race condition here --- we create our PGPROC before
861862
* checking for other PGPROCs. If two backends did this at about the
@@ -865,6 +866,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_chec
865866
* just document that the connection limit is approximate.
866867
*/
867868
if (rform->rolconnlimit >= 0 &&
869+
AmRegularBackendProcess() &&
868870
!is_superuser &&
869871
CountUserBackends(roleid) > rform->rolconnlimit)
870872
ereport(FATAL,

src/backend/utils/init/postinit.c

+14-29
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/tableam.h"
2827
#include "access/xact.h"
@@ -342,13 +341,13 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
342341
* These checks are not enforced when in standalone mode, so that there is
343342
* a way to recover from disabling all access to all databases, for
344343
* example "UPDATE pg_database SET datallowconn = false;".
345-
*
346-
* We do not enforce them for autovacuum worker processes either.
347344
*/
348-
if (IsUnderPostmaster && !AmAutoVacuumWorkerProcess())
345+
if (IsUnderPostmaster)
349346
{
350347
/*
351348
* Check that the database is currently allowing connections.
349+
* (Background processes can override this test and the next one by
350+
* setting override_allow_connections.)
352351
*/
353352
if (!dbform->datallowconn && !override_allow_connections)
354353
ereport(FATAL,
@@ -361,7 +360,7 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
361360
* is redundant, but since we have the flag, might as well check it
362361
* and save a few cycles.)
363362
*/
364-
if (!am_superuser &&
363+
if (!am_superuser && !override_allow_connections &&
365364
object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
366365
ACL_CONNECT) != ACLCHECK_OK)
367366
ereport(FATAL,
@@ -370,7 +369,9 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
370369
errdetail("User does not have CONNECT privilege.")));
371370

372371
/*
373-
* Check connection limit for this database.
372+
* Check connection limit for this database. We enforce the limit
373+
* only for regular backends, since other process types have their own
374+
* PGPROC pools.
374375
*
375376
* There is a race condition here --- we create our PGPROC before
376377
* checking for other PGPROCs. If two backends did this at about the
@@ -380,6 +381,7 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
380381
* just document that the connection limit is approximate.
381382
*/
382383
if (dbform->datconnlimit >= 0 &&
384+
AmRegularBackendProcess() &&
383385
!am_superuser &&
384386
CountDBConnections(MyDatabaseId) > dbform->datconnlimit)
385387
ereport(FATAL,
@@ -914,23 +916,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
914916
{
915917
InitializeSessionUserId(username, useroid,
916918
(flags & INIT_PG_OVERRIDE_ROLE_LOGIN) != 0);
917-
918-
/*
919-
* In a parallel worker, set am_superuser based on the
920-
* authenticated user ID, not the current role. This is pretty
921-
* dubious but it matches our historical behavior. Note that this
922-
* value of am_superuser is used only for connection-privilege
923-
* checks here and in CheckMyDatabase (we won't reach
924-
* process_startup_options in a background worker).
925-
*
926-
* In other cases, there's been no opportunity for the current
927-
* role to diverge from the authenticated user ID yet, so we can
928-
* just rely on superuser() and avoid an extra catalog lookup.
929-
*/
930-
if (InitializingParallelWorker)
931-
am_superuser = superuser_arg(GetAuthenticatedUserId());
932-
else
933-
am_superuser = superuser();
919+
am_superuser = superuser();
934920
}
935921
}
936922
else
@@ -957,17 +943,16 @@ InitPostgres(const char *in_dbname, Oid dboid,
957943
}
958944

959945
/*
960-
* The last few connection slots are reserved for superusers and roles
961-
* with privileges of pg_use_reserved_connections. Replication
962-
* connections are drawn from slots reserved with max_wal_senders and are
963-
* not limited by max_connections, superuser_reserved_connections, or
964-
* reserved_connections.
946+
* The last few regular connection slots are reserved for superusers and
947+
* roles with privileges of pg_use_reserved_connections. We do not apply
948+
* these limits to background processes, since they all have their own
949+
* pools of PGPROC slots.
965950
*
966951
* Note: At this point, the new backend has already claimed a proc struct,
967952
* so we must check whether the number of free slots is strictly less than
968953
* the reserved connection limits.
969954
*/
970-
if (!am_superuser && !am_walsender &&
955+
if (AmRegularBackendProcess() && !am_superuser &&
971956
(SuperuserReservedConnections + ReservedConnections) > 0 &&
972957
!HaveNFreeProcs(SuperuserReservedConnections + ReservedConnections, &nfree))
973958
{

src/include/miscadmin.h

+1
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ typedef enum BackendType
372372

373373
extern PGDLLIMPORT BackendType MyBackendType;
374374

375+
#define AmRegularBackendProcess() (MyBackendType == B_BACKEND)
375376
#define AmAutoVacuumLauncherProcess() (MyBackendType == B_AUTOVAC_LAUNCHER)
376377
#define AmAutoVacuumWorkerProcess() (MyBackendType == B_AUTOVAC_WORKER)
377378
#define AmBackgroundWorkerProcess() (MyBackendType == B_BG_WORKER)

src/include/storage/proc.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ struct PGPROC
212212
Oid tempNamespaceId; /* OID of temp schema this backend is
213213
* using */
214214

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

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

src/test/modules/worker_spi/worker_spi.c

-9
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,6 @@ worker_spi_main(Datum main_arg)
173173
BackgroundWorkerInitializeConnection(worker_spi_database,
174174
worker_spi_role, flags);
175175

176-
/*
177-
* Disable parallel query for workers started with BYPASS_ALLOWCONN or
178-
* BGWORKER_BYPASS_ALLOWCONN so as these don't attempt connections using a
179-
* database or a role that may not allow that.
180-
*/
181-
if ((flags & (BGWORKER_BYPASS_ALLOWCONN | BGWORKER_BYPASS_ROLELOGINCHECK)))
182-
SetConfigOption("max_parallel_workers_per_gather", "0",
183-
PGC_USERSET, PGC_S_OVERRIDE);
184-
185176
elog(LOG, "%s initialized with %s.%s",
186177
MyBgworkerEntry->bgw_name, table->schema, table->name);
187178
initialize_worker_spi(table);

0 commit comments

Comments
 (0)