1
0
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:
Nathan Bossart
2026-08-10 06:38:25 -07:00
committed by Noah Misch
parent 7a761fe408
commit f5f2da795b
3 changed files with 63 additions and 2 deletions

View File

@@ -78,7 +78,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;
static const char *getid(const char *s, char *n, Node *escontext);

View File

@@ -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
@@ -67,6 +70,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_private.h"
@@ -117,6 +121,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);
/* GUC parameter */
@@ -138,6 +143,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);
}
/*
@@ -2145,6 +2153,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

View File

@@ -226,6 +226,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);
/*