mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Invalidate plan cache after role changes.
Role membership, role attribute, and database ownership changes may impact the expected behavior of row-level security policies, but currently the plan cache doesn't take notice. To fix, register syscache callbacks on pg_auth_members, pg_authid, and pg_database that invalidate the role-dependent plans. Changes to other databases' pg_database rows are ignored. Reported-by: Ilya Staroverov <i.staroverov@ftdata.ru> Reported-by: Shinya Kato <shinya11.kato@gmail.com> Author: Ilya Staroverov <i.staroverov@ftdata.ru> Author: Shinya Kato <shinya11.kato@gmail.com> Co-authored-by: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Security: CVE-2026-14666 Backpatch-through: 14
This commit is contained in:
committed by
Noah Misch
parent
6713596055
commit
1974acf23c
@@ -77,7 +77,7 @@ enum RoleRecurseType
|
||||
};
|
||||
static Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid};
|
||||
static List *cached_roles[] = {NIL, NIL, NIL};
|
||||
static uint32 cached_db_hash;
|
||||
uint32 cached_db_hash;
|
||||
|
||||
/*
|
||||
* If the list of roles gathered by roles_is_member_of() grows larger than the
|
||||
|
||||
60
src/backend/utils/cache/plancache.c
vendored
60
src/backend/utils/cache/plancache.c
vendored
@@ -36,7 +36,10 @@
|
||||
* certain other system catalogs, such as pg_namespace; but for them, our
|
||||
* response is just to invalidate all plans. We expect updates on those
|
||||
* catalogs to be infrequent enough that more-detailed tracking is not worth
|
||||
* the effort.
|
||||
* the effort. We likewise watch pg_authid, pg_auth_members, and
|
||||
* pg_database, which can change which row-level security policies apply.
|
||||
* Since those are shared catalogs whose inval events reach every backend
|
||||
* in the cluster, we invalidate only the role-dependent plans.
|
||||
*
|
||||
* In addition to full-fledged query plans, we provide a facility for
|
||||
* detecting invalidations of simple scalar expressions. This is fairly
|
||||
@@ -66,6 +69,7 @@
|
||||
#include "storage/lmgr.h"
|
||||
#include "tcop/pquery.h"
|
||||
#include "tcop/utility.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/resowner.h"
|
||||
@@ -116,6 +120,7 @@ static bool ScanQueryWalker(Node *node, bool *acquire);
|
||||
static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
|
||||
static void PlanCacheRelCallback(Datum arg, Oid relid);
|
||||
static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
static void PlanCacheRoleCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
|
||||
/* ResourceOwner callbacks to track plancache references */
|
||||
@@ -162,6 +167,9 @@ InitPlanCache(void)
|
||||
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, PlanCacheRoleCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(AUTHOID, PlanCacheRoleCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(DATABASEOID, PlanCacheRoleCallback, (Datum) 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2168,6 +2176,56 @@ PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PlanCacheRoleCallback
|
||||
* Syscache inval callback function for AUTHMEMROLEMEM, AUTHOID, and
|
||||
* DATABASEOID caches
|
||||
*
|
||||
* Role membership, role attributes, and database ownership (which confers
|
||||
* membership in pg_database_owner) affect planning by way of row-level
|
||||
* security, so invalidate just the role-dependent plans. For DATABASEOID, we
|
||||
* can ignore changes to other databases' pg_database rows.
|
||||
*/
|
||||
static void
|
||||
PlanCacheRoleCallback(Datum arg, int cacheid, uint32 hashvalue)
|
||||
{
|
||||
dlist_iter iter;
|
||||
|
||||
if (cacheid == DATABASEOID &&
|
||||
hashvalue != cached_db_hash &&
|
||||
hashvalue != 0)
|
||||
return; /* ignore pg_database changes for other DBs */
|
||||
|
||||
dlist_foreach(iter, &saved_plan_list)
|
||||
{
|
||||
CachedPlanSource *plansource = dlist_container(CachedPlanSource,
|
||||
node, iter.cur);
|
||||
|
||||
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
|
||||
|
||||
/* No work if it's already invalidated */
|
||||
if (!plansource->is_valid)
|
||||
continue;
|
||||
|
||||
/* Never invalidate if parse/plan would be a no-op anyway */
|
||||
if (!StmtPlanRequiresRevalidation(plansource))
|
||||
continue;
|
||||
|
||||
if (plansource->dependsOnRLS)
|
||||
{
|
||||
/* Invalidate the querytree and generic plan */
|
||||
plansource->is_valid = false;
|
||||
if (plansource->gplan)
|
||||
plansource->gplan->is_valid = false;
|
||||
}
|
||||
else if (plansource->gplan && plansource->gplan->dependsOnRole)
|
||||
{
|
||||
/* Invalidate the generic plan only */
|
||||
plansource->gplan->is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PlanCacheSysCallback
|
||||
* Syscache inval callback function for other caches
|
||||
|
||||
@@ -227,6 +227,9 @@ extern void select_best_grantor(Oid roleId, AclMode privileges,
|
||||
const Acl *acl, Oid ownerId,
|
||||
Oid *grantorId, AclMode *grantOptions);
|
||||
|
||||
/* DATABASEOID syscache hash value for our own database, set by initialize_acl */
|
||||
extern uint32 cached_db_hash;
|
||||
|
||||
extern void initialize_acl(void);
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user