mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Revise RelationBuildRowSecurity() to avoid memory leaks.
This function leaked some memory while loading qual clauses for an RLS policy. While ordinarily negligible, that could build up in some repeated-reload cases, as reported by Konstantin Knizhnik. We can improve matters by borrowing the coding long used in RelationBuildRuleLock: build stringToNode's result directly in the target context, and remember to explicitly pfree the input string. This patch by no means completely guarantees zero leaks within this function, since we have no real guarantee that the catalog- reading subroutines it calls don't leak anything. However, practical tests suggest that this is enough to resolve the issue. In any case, any remaining leaks are similar to those risked by RelationBuildRuleLock and other relcache-loading subroutines. If we need to fix them, we should adopt a more global approach such as that used by the RECOVER_RELATION_BUILD_MEMORY hack. While here, let's remove the need for an expensive PG_TRY block by using MemoryContextSetParent to reparent an initially-short-lived context for the RLS data. Back-patch to all supported branches. Discussion: https://postgr.es/m/21356c12-8917-8249-b35f-1c447231922b@postgrespro.ru
This commit is contained in:
parent
079d0cacf4
commit
e55f718fc4
@ -187,40 +187,42 @@ policy_role_list_to_array(List *roles, int *num_roles)
|
||||
/*
|
||||
* Load row security policy from the catalog, and store it in
|
||||
* the relation's relcache entry.
|
||||
*
|
||||
* Note that caller should have verified that pg_class.relrowsecurity
|
||||
* is true for this relation.
|
||||
*/
|
||||
void
|
||||
RelationBuildRowSecurity(Relation relation)
|
||||
{
|
||||
MemoryContext rscxt;
|
||||
MemoryContext oldcxt = CurrentMemoryContext;
|
||||
RowSecurityDesc *volatile rsdesc = NULL;
|
||||
|
||||
/*
|
||||
* Create a memory context to hold everything associated with this
|
||||
* relation's row security policy. This makes it easy to clean up during
|
||||
* a relcache flush.
|
||||
*/
|
||||
rscxt = AllocSetContextCreate(CacheMemoryContext,
|
||||
"row security descriptor",
|
||||
ALLOCSET_SMALL_SIZES);
|
||||
|
||||
/*
|
||||
* Since rscxt lives under CacheMemoryContext, it is long-lived. Use a
|
||||
* PG_TRY block to ensure it'll get freed if we fail partway through.
|
||||
*/
|
||||
PG_TRY();
|
||||
{
|
||||
RowSecurityDesc *rsdesc;
|
||||
Relation catalog;
|
||||
ScanKeyData skey;
|
||||
SysScanDesc sscan;
|
||||
HeapTuple tuple;
|
||||
|
||||
/*
|
||||
* Create a memory context to hold everything associated with this
|
||||
* relation's row security policy. This makes it easy to clean up during
|
||||
* a relcache flush. However, to cover the possibility of an error
|
||||
* partway through, we don't make the context long-lived till we're done.
|
||||
*/
|
||||
rscxt = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"row security descriptor",
|
||||
ALLOCSET_SMALL_SIZES);
|
||||
MemoryContextCopyAndSetIdentifier(rscxt,
|
||||
RelationGetRelationName(relation));
|
||||
|
||||
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
|
||||
rsdesc->rscxt = rscxt;
|
||||
|
||||
/*
|
||||
* Now scan pg_policy for RLS policies associated with this relation.
|
||||
* Because we use the index on (polrelid, polname), we should consistently
|
||||
* visit the rel's policies in name order, at least when system indexes
|
||||
* aren't disabled. This simplifies equalRSDesc().
|
||||
*/
|
||||
catalog = table_open(PolicyRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&skey,
|
||||
@ -231,115 +233,93 @@ RelationBuildRowSecurity(Relation relation)
|
||||
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
|
||||
NULL, 1, &skey);
|
||||
|
||||
/*
|
||||
* Loop through the row level security policies for this relation, if
|
||||
* any.
|
||||
*/
|
||||
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
|
||||
{
|
||||
Datum value_datum;
|
||||
char cmd_value;
|
||||
bool permissive_value;
|
||||
Datum roles_datum;
|
||||
char *qual_value;
|
||||
Expr *qual_expr;
|
||||
char *with_check_value;
|
||||
Expr *with_check_qual;
|
||||
char *policy_name_value;
|
||||
bool isnull;
|
||||
Form_pg_policy policy_form = (Form_pg_policy) GETSTRUCT(tuple);
|
||||
RowSecurityPolicy *policy;
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
char *str_value;
|
||||
|
||||
policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy));
|
||||
|
||||
/*
|
||||
* Note: all the pass-by-reference data we collect here is either
|
||||
* still stored in the tuple, or constructed in the caller's
|
||||
* short-lived memory context. We must copy it into rscxt
|
||||
* explicitly below.
|
||||
* Note: we must be sure that pass-by-reference data gets copied into
|
||||
* rscxt. We avoid making that context current over wider spans than
|
||||
* we have to, though.
|
||||
*/
|
||||
|
||||
/* Get policy command */
|
||||
value_datum = heap_getattr(tuple, Anum_pg_policy_polcmd,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
Assert(!isnull);
|
||||
cmd_value = DatumGetChar(value_datum);
|
||||
policy->polcmd = policy_form->polcmd;
|
||||
|
||||
/* Get policy permissive or restrictive */
|
||||
value_datum = heap_getattr(tuple, Anum_pg_policy_polpermissive,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
Assert(!isnull);
|
||||
permissive_value = DatumGetBool(value_datum);
|
||||
/* Get policy, permissive or restrictive */
|
||||
policy->permissive = policy_form->polpermissive;
|
||||
|
||||
/* Get policy name */
|
||||
value_datum = heap_getattr(tuple, Anum_pg_policy_polname,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
Assert(!isnull);
|
||||
policy_name_value = NameStr(*(DatumGetName(value_datum)));
|
||||
policy->policy_name =
|
||||
MemoryContextStrdup(rscxt, NameStr(policy_form->polname));
|
||||
|
||||
/* Get policy roles */
|
||||
roles_datum = heap_getattr(tuple, Anum_pg_policy_polroles,
|
||||
datum = heap_getattr(tuple, Anum_pg_policy_polroles,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
/* shouldn't be null, but initdb doesn't mark it so, so check */
|
||||
/* shouldn't be null, but let's check for luck */
|
||||
if (isnull)
|
||||
elog(ERROR, "unexpected null value in pg_policy.polroles");
|
||||
|
||||
/* Get policy qual */
|
||||
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
qual_value = TextDatumGetCString(value_datum);
|
||||
qual_expr = (Expr *) stringToNode(qual_value);
|
||||
}
|
||||
else
|
||||
qual_expr = NULL;
|
||||
|
||||
/* Get WITH CHECK qual */
|
||||
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
with_check_value = TextDatumGetCString(value_datum);
|
||||
with_check_qual = (Expr *) stringToNode(with_check_value);
|
||||
}
|
||||
else
|
||||
with_check_qual = NULL;
|
||||
|
||||
/* Now copy everything into the cache context */
|
||||
MemoryContextSwitchTo(rscxt);
|
||||
|
||||
policy = palloc0(sizeof(RowSecurityPolicy));
|
||||
policy->policy_name = pstrdup(policy_name_value);
|
||||
policy->polcmd = cmd_value;
|
||||
policy->permissive = permissive_value;
|
||||
policy->roles = DatumGetArrayTypePCopy(roles_datum);
|
||||
policy->qual = copyObject(qual_expr);
|
||||
policy->with_check_qual = copyObject(with_check_qual);
|
||||
policy->hassublinks = checkExprHasSubLink((Node *) qual_expr) ||
|
||||
checkExprHasSubLink((Node *) with_check_qual);
|
||||
|
||||
rsdesc->policies = lcons(policy, rsdesc->policies);
|
||||
|
||||
policy->roles = DatumGetArrayTypePCopy(datum);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
|
||||
/* clean up some (not all) of the junk ... */
|
||||
if (qual_expr != NULL)
|
||||
pfree(qual_expr);
|
||||
if (with_check_qual != NULL)
|
||||
pfree(with_check_qual);
|
||||
/* Get policy qual */
|
||||
datum = heap_getattr(tuple, Anum_pg_policy_polqual,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
str_value = TextDatumGetCString(datum);
|
||||
MemoryContextSwitchTo(rscxt);
|
||||
policy->qual = (Expr *) stringToNode(str_value);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
pfree(str_value);
|
||||
}
|
||||
else
|
||||
policy->qual = NULL;
|
||||
|
||||
/* Get WITH CHECK qual */
|
||||
datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
|
||||
RelationGetDescr(catalog), &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
str_value = TextDatumGetCString(datum);
|
||||
MemoryContextSwitchTo(rscxt);
|
||||
policy->with_check_qual = (Expr *) stringToNode(str_value);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
pfree(str_value);
|
||||
}
|
||||
else
|
||||
policy->with_check_qual = NULL;
|
||||
|
||||
/* We want to cache whether there are SubLinks in these expressions */
|
||||
policy->hassublinks = checkExprHasSubLink((Node *) policy->qual) ||
|
||||
checkExprHasSubLink((Node *) policy->with_check_qual);
|
||||
|
||||
/*
|
||||
* Add this object to list. For historical reasons, the list is built
|
||||
* in reverse order.
|
||||
*/
|
||||
MemoryContextSwitchTo(rscxt);
|
||||
rsdesc->policies = lcons(policy, rsdesc->policies);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
systable_endscan(sscan);
|
||||
table_close(catalog, AccessShareLock);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
/* Delete rscxt, first making sure it isn't active */
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
MemoryContextDelete(rscxt);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
/* Success --- attach the policy descriptor to the relcache entry */
|
||||
/*
|
||||
* Success. Reparent the descriptor's memory context under
|
||||
* CacheMemoryContext so that it will live indefinitely, then attach the
|
||||
* policy descriptor to the relcache entry.
|
||||
*/
|
||||
MemoryContextSetParent(rscxt, CacheMemoryContext);
|
||||
|
||||
relation->rd_rsdesc = rsdesc;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user