Skip to content

Commit bf34ae9

Browse files
committed
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
1 parent 56b46d3 commit bf34ae9

File tree

1 file changed

+95
-109
lines changed

1 file changed

+95
-109
lines changed

src/backend/commands/policy.c

Lines changed: 95 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -186,150 +186,136 @@ policy_role_list_to_array(List *roles, int *num_roles)
186186
/*
187187
* Load row security policy from the catalog, and store it in
188188
* the relation's relcache entry.
189+
*
190+
* Note that caller should have verified that pg_class.relrowsecurity
191+
* is true for this relation.
189192
*/
190193
void
191194
RelationBuildRowSecurity(Relation relation)
192195
{
193196
MemoryContext rscxt;
194197
MemoryContext oldcxt = CurrentMemoryContext;
195-
RowSecurityDesc *volatile rsdesc = NULL;
198+
RowSecurityDesc *rsdesc;
199+
Relation catalog;
200+
ScanKeyData skey;
201+
SysScanDesc sscan;
202+
HeapTuple tuple;
196203

197204
/*
198205
* Create a memory context to hold everything associated with this
199206
* relation's row security policy. This makes it easy to clean up during
200-
* a relcache flush.
207+
* a relcache flush. However, to cover the possibility of an error
208+
* partway through, we don't make the context long-lived till we're done.
201209
*/
202-
rscxt = AllocSetContextCreate(CacheMemoryContext,
210+
rscxt = AllocSetContextCreate(CurrentMemoryContext,
203211
"row security descriptor",
204212
ALLOCSET_SMALL_MINSIZE,
205213
ALLOCSET_SMALL_INITSIZE,
206214
ALLOCSET_SMALL_MAXSIZE);
207215

216+
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
217+
rsdesc->rscxt = rscxt;
218+
208219
/*
209-
* Since rscxt lives under CacheMemoryContext, it is long-lived. Use a
210-
* PG_TRY block to ensure it'll get freed if we fail partway through.
220+
* Now scan pg_policy for RLS policies associated with this relation.
221+
* Because we use the index on (polrelid, polname), we should consistently
222+
* visit the rel's policies in name order, at least when system indexes
223+
* aren't disabled. This simplifies equalRSDesc().
211224
*/
212-
PG_TRY();
213-
{
214-
Relation catalog;
215-
ScanKeyData skey;
216-
SysScanDesc sscan;
217-
HeapTuple tuple;
225+
catalog = heap_open(PolicyRelationId, AccessShareLock);
218226

219-
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
220-
rsdesc->rscxt = rscxt;
227+
ScanKeyInit(&skey,
228+
Anum_pg_policy_polrelid,
229+
BTEqualStrategyNumber, F_OIDEQ,
230+
ObjectIdGetDatum(RelationGetRelid(relation)));
221231

222-
catalog = heap_open(PolicyRelationId, AccessShareLock);
232+
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
233+
NULL, 1, &skey);
223234

224-
ScanKeyInit(&skey,
225-
Anum_pg_policy_polrelid,
226-
BTEqualStrategyNumber, F_OIDEQ,
227-
ObjectIdGetDatum(RelationGetRelid(relation)));
235+
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
236+
{
237+
Form_pg_policy policy_form = (Form_pg_policy) GETSTRUCT(tuple);
238+
RowSecurityPolicy *policy;
239+
Datum datum;
240+
bool isnull;
241+
char *str_value;
228242

229-
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
230-
NULL, 1, &skey);
243+
policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy));
231244

232245
/*
233-
* Loop through the row level security policies for this relation, if
234-
* any.
246+
* Note: we must be sure that pass-by-reference data gets copied into
247+
* rscxt. We avoid making that context current over wider spans than
248+
* we have to, though.
235249
*/
236-
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
237-
{
238-
Datum value_datum;
239-
char cmd_value;
240-
Datum roles_datum;
241-
char *qual_value;
242-
Expr *qual_expr;
243-
char *with_check_value;
244-
Expr *with_check_qual;
245-
char *policy_name_value;
246-
bool isnull;
247-
RowSecurityPolicy *policy;
248-
249-
/*
250-
* Note: all the pass-by-reference data we collect here is either
251-
* still stored in the tuple, or constructed in the caller's
252-
* short-lived memory context. We must copy it into rscxt
253-
* explicitly below.
254-
*/
255-
256-
/* Get policy command */
257-
value_datum = heap_getattr(tuple, Anum_pg_policy_polcmd,
258-
RelationGetDescr(catalog), &isnull);
259-
Assert(!isnull);
260-
cmd_value = DatumGetChar(value_datum);
261-
262-
/* Get policy name */
263-
value_datum = heap_getattr(tuple, Anum_pg_policy_polname,
264-
RelationGetDescr(catalog), &isnull);
265-
Assert(!isnull);
266-
policy_name_value = NameStr(*(DatumGetName(value_datum)));
267-
268-
/* Get policy roles */
269-
roles_datum = heap_getattr(tuple, Anum_pg_policy_polroles,
270-
RelationGetDescr(catalog), &isnull);
271-
/* shouldn't be null, but initdb doesn't mark it so, so check */
272-
if (isnull)
273-
elog(ERROR, "unexpected null value in pg_policy.polroles");
274-
275-
/* Get policy qual */
276-
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
277-
RelationGetDescr(catalog), &isnull);
278-
if (!isnull)
279-
{
280-
qual_value = TextDatumGetCString(value_datum);
281-
qual_expr = (Expr *) stringToNode(qual_value);
282-
}
283-
else
284-
qual_expr = NULL;
285250

286-
/* Get WITH CHECK qual */
287-
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
288-
RelationGetDescr(catalog), &isnull);
289-
if (!isnull)
290-
{
291-
with_check_value = TextDatumGetCString(value_datum);
292-
with_check_qual = (Expr *) stringToNode(with_check_value);
293-
}
294-
else
295-
with_check_qual = NULL;
251+
/* Get policy command */
252+
policy->polcmd = policy_form->polcmd;
253+
254+
/* Get policy name */
255+
policy->policy_name =
256+
MemoryContextStrdup(rscxt, NameStr(policy_form->polname));
257+
258+
/* Get policy roles */
259+
datum = heap_getattr(tuple, Anum_pg_policy_polroles,
260+
RelationGetDescr(catalog), &isnull);
261+
/* shouldn't be null, but let's check for luck */
262+
if (isnull)
263+
elog(ERROR, "unexpected null value in pg_policy.polroles");
264+
MemoryContextSwitchTo(rscxt);
265+
policy->roles = DatumGetArrayTypePCopy(datum);
266+
MemoryContextSwitchTo(oldcxt);
296267

297-
/* Now copy everything into the cache context */
268+
/* Get policy qual */
269+
datum = heap_getattr(tuple, Anum_pg_policy_polqual,
270+
RelationGetDescr(catalog), &isnull);
271+
if (!isnull)
272+
{
273+
str_value = TextDatumGetCString(datum);
298274
MemoryContextSwitchTo(rscxt);
299-
300-
policy = palloc0(sizeof(RowSecurityPolicy));
301-
policy->policy_name = pstrdup(policy_name_value);
302-
policy->polcmd = cmd_value;
303-
policy->roles = DatumGetArrayTypePCopy(roles_datum);
304-
policy->qual = copyObject(qual_expr);
305-
policy->with_check_qual = copyObject(with_check_qual);
306-
policy->hassublinks = checkExprHasSubLink((Node *) qual_expr) ||
307-
checkExprHasSubLink((Node *) with_check_qual);
308-
309-
rsdesc->policies = lcons(policy, rsdesc->policies);
310-
275+
policy->qual = (Expr *) stringToNode(str_value);
311276
MemoryContextSwitchTo(oldcxt);
277+
pfree(str_value);
278+
}
279+
else
280+
policy->qual = NULL;
312281

313-
/* clean up some (not all) of the junk ... */
314-
if (qual_expr != NULL)
315-
pfree(qual_expr);
316-
if (with_check_qual != NULL)
317-
pfree(with_check_qual);
282+
/* Get WITH CHECK qual */
283+
datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
284+
RelationGetDescr(catalog), &isnull);
285+
if (!isnull)
286+
{
287+
str_value = TextDatumGetCString(datum);
288+
MemoryContextSwitchTo(rscxt);
289+
policy->with_check_qual = (Expr *) stringToNode(str_value);
290+
MemoryContextSwitchTo(oldcxt);
291+
pfree(str_value);
318292
}
293+
else
294+
policy->with_check_qual = NULL;
319295

320-
systable_endscan(sscan);
321-
heap_close(catalog, AccessShareLock);
322-
}
323-
PG_CATCH();
324-
{
325-
/* Delete rscxt, first making sure it isn't active */
296+
/* We want to cache whether there are SubLinks in these expressions */
297+
policy->hassublinks = checkExprHasSubLink((Node *) policy->qual) ||
298+
checkExprHasSubLink((Node *) policy->with_check_qual);
299+
300+
/*
301+
* Add this object to list. For historical reasons, the list is built
302+
* in reverse order.
303+
*/
304+
MemoryContextSwitchTo(rscxt);
305+
rsdesc->policies = lcons(policy, rsdesc->policies);
326306
MemoryContextSwitchTo(oldcxt);
327-
MemoryContextDelete(rscxt);
328-
PG_RE_THROW();
329307
}
330-
PG_END_TRY();
331308

332-
/* Success --- attach the policy descriptor to the relcache entry */
309+
systable_endscan(sscan);
310+
heap_close(catalog, AccessShareLock);
311+
312+
/*
313+
* Success. Reparent the descriptor's memory context under
314+
* CacheMemoryContext so that it will live indefinitely, then attach the
315+
* policy descriptor to the relcache entry.
316+
*/
317+
MemoryContextSetParent(rscxt, CacheMemoryContext);
318+
333319
relation->rd_rsdesc = rsdesc;
334320
}
335321

0 commit comments

Comments
 (0)