Skip to content

Commit a61b1f7

Browse files
committed
Rework query relation permission checking
Currently, information about the permissions to be checked on relations mentioned in a query is stored in their range table entries. So the executor must scan the entire range table looking for relations that need to have permissions checked. This can make the permission checking part of the executor initialization needlessly expensive when many inheritance children are present in the range range. While the permissions need not be checked on the individual child relations, the executor still must visit every range table entry to filter them out. This commit moves the permission checking information out of the range table entries into a new plan node called RTEPermissionInfo. Every top-level (inheritance "root") RTE_RELATION entry in the range table gets one and a list of those is maintained alongside the range table. This new list is initialized by the parser when initializing the range table. The rewriter can add more entries to it as rules/views are expanded. Finally, the planner combines the lists of the individual subqueries into one flat list that is passed to the executor for checking. To make it quick to find the RTEPermissionInfo entry belonging to a given relation, RangeTblEntry gets a new Index field 'perminfoindex' that stores the corresponding RTEPermissionInfo's index in the query's list of the latter. ExecutorCheckPerms_hook has gained another List * argument; the signature is now: typedef bool (*ExecutorCheckPerms_hook_type) (List *rangeTable, List *rtePermInfos, bool ereport_on_violation); The first argument is no longer used by any in-core uses of the hook, but we leave it in place because there may be other implementations that do. Implementations should likely scan the rtePermInfos list to determine which operations to allow or deny. Author: Amit Langote <[email protected]> Discussion: https://postgr.es/m/CA+HiwqGjJDmUhDSfv-U2qhKJjt9ST7Xh9JXC_irsAQ1TAUsJYg@mail.gmail.com
1 parent b5bbaf0 commit a61b1f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+953
-533
lines changed

contrib/postgres_fdw/postgres_fdw.c

+8-9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "optimizer/appendinfo.h"
3232
#include "optimizer/clauses.h"
3333
#include "optimizer/cost.h"
34+
#include "optimizer/inherit.h"
3435
#include "optimizer/optimizer.h"
3536
#include "optimizer/pathnode.h"
3637
#include "optimizer/paths.h"
@@ -657,8 +658,8 @@ postgresGetForeignRelSize(PlannerInfo *root,
657658
/*
658659
* If the table or the server is configured to use remote estimates,
659660
* identify which user to do remote access as during planning. This
660-
* should match what ExecCheckRTEPerms() does. If we fail due to lack of
661-
* permissions, the query would have failed at runtime anyway.
661+
* should match what ExecCheckPermissions() does. If we fail due to lack
662+
* of permissions, the query would have failed at runtime anyway.
662663
*/
663664
if (fpinfo->use_remote_estimate)
664665
{
@@ -1809,7 +1810,8 @@ postgresPlanForeignModify(PlannerInfo *root,
18091810
else if (operation == CMD_UPDATE)
18101811
{
18111812
int col;
1812-
Bitmapset *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols);
1813+
RelOptInfo *rel = find_base_rel(root, resultRelation);
1814+
Bitmapset *allUpdatedCols = get_rel_all_updated_cols(root, rel);
18131815

18141816
col = -1;
18151817
while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
@@ -2650,7 +2652,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
26502652

26512653
/*
26522654
* Identify which user to do the remote access as. This should match what
2653-
* ExecCheckRTEPerms() does.
2655+
* ExecCheckPermissions() does.
26542656
*/
26552657
userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
26562658

@@ -3975,11 +3977,8 @@ create_foreign_modify(EState *estate,
39753977
fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
39763978
fmstate->rel = rel;
39773979

3978-
/*
3979-
* Identify which user to do the remote access as. This should match what
3980-
* ExecCheckRTEPerms() does.
3981-
*/
3982-
userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
3980+
/* Identify which user to do the remote access as. */
3981+
userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
39833982

39843983
/* Get info about foreign table. */
39853984
table = GetForeignTable(RelationGetRelid(rel));

contrib/sepgsql/dml.c

+19-23
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "commands/tablecmds.h"
2424
#include "executor/executor.h"
2525
#include "nodes/bitmapset.h"
26+
#include "parser/parsetree.h"
2627
#include "sepgsql.h"
2728
#include "utils/lsyscache.h"
2829
#include "utils/syscache.h"
@@ -277,38 +278,33 @@ check_relation_privileges(Oid relOid,
277278
* Entrypoint of the DML permission checks
278279
*/
279280
bool
280-
sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
281+
sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos,
282+
bool abort_on_violation)
281283
{
282284
ListCell *lr;
283285

284-
foreach(lr, rangeTabls)
286+
foreach(lr, rteperminfos)
285287
{
286-
RangeTblEntry *rte = lfirst(lr);
288+
RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
287289
uint32 required = 0;
288290
List *tableIds;
289291
ListCell *li;
290292

291-
/*
292-
* Only regular relations shall be checked
293-
*/
294-
if (rte->rtekind != RTE_RELATION)
295-
continue;
296-
297293
/*
298294
* Find out required permissions
299295
*/
300-
if (rte->requiredPerms & ACL_SELECT)
296+
if (perminfo->requiredPerms & ACL_SELECT)
301297
required |= SEPG_DB_TABLE__SELECT;
302-
if (rte->requiredPerms & ACL_INSERT)
298+
if (perminfo->requiredPerms & ACL_INSERT)
303299
required |= SEPG_DB_TABLE__INSERT;
304-
if (rte->requiredPerms & ACL_UPDATE)
300+
if (perminfo->requiredPerms & ACL_UPDATE)
305301
{
306-
if (!bms_is_empty(rte->updatedCols))
302+
if (!bms_is_empty(perminfo->updatedCols))
307303
required |= SEPG_DB_TABLE__UPDATE;
308304
else
309305
required |= SEPG_DB_TABLE__LOCK;
310306
}
311-
if (rte->requiredPerms & ACL_DELETE)
307+
if (perminfo->requiredPerms & ACL_DELETE)
312308
required |= SEPG_DB_TABLE__DELETE;
313309

314310
/*
@@ -323,10 +319,10 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
323319
* expand rte->relid into list of OIDs of inheritance hierarchy, then
324320
* checker routine will be invoked for each relations.
325321
*/
326-
if (!rte->inh)
327-
tableIds = list_make1_oid(rte->relid);
322+
if (!perminfo->inh)
323+
tableIds = list_make1_oid(perminfo->relid);
328324
else
329-
tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
325+
tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
330326

331327
foreach(li, tableIds)
332328
{
@@ -339,12 +335,12 @@ sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
339335
* child table has different attribute numbers, so we need to fix
340336
* up them.
341337
*/
342-
selectedCols = fixup_inherited_columns(rte->relid, tableOid,
343-
rte->selectedCols);
344-
insertedCols = fixup_inherited_columns(rte->relid, tableOid,
345-
rte->insertedCols);
346-
updatedCols = fixup_inherited_columns(rte->relid, tableOid,
347-
rte->updatedCols);
338+
selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
339+
perminfo->selectedCols);
340+
insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
341+
perminfo->insertedCols);
342+
updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
343+
perminfo->updatedCols);
348344

349345
/*
350346
* check permissions on individual tables

contrib/sepgsql/hooks.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -287,17 +287,17 @@ sepgsql_object_access(ObjectAccessType access,
287287
* Entrypoint of DML permissions
288288
*/
289289
static bool
290-
sepgsql_exec_check_perms(List *rangeTabls, bool abort)
290+
sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort)
291291
{
292292
/*
293293
* If security provider is stacking and one of them replied 'false' at
294294
* least, we don't need to check any more.
295295
*/
296296
if (next_exec_check_perms_hook &&
297-
!(*next_exec_check_perms_hook) (rangeTabls, abort))
297+
!(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort))
298298
return false;
299299

300-
if (!sepgsql_dml_privileges(rangeTabls, abort))
300+
if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort))
301301
return false;
302302

303303
return true;

contrib/sepgsql/sepgsql.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ extern void sepgsql_object_relabel(const ObjectAddress *object,
274274
/*
275275
* dml.c
276276
*/
277-
extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation);
277+
extern bool sepgsql_dml_privileges(List *rangeTabls, List *rteperminfos,
278+
bool abort_on_violation);
278279

279280
/*
280281
* database.c

src/backend/commands/copy.c

+12-11
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
109109
{
110110
LOCKMODE lockmode = is_from ? RowExclusiveLock : AccessShareLock;
111111
ParseNamespaceItem *nsitem;
112-
RangeTblEntry *rte;
112+
RTEPermissionInfo *perminfo;
113113
TupleDesc tupDesc;
114114
List *attnums;
115115
ListCell *cur;
@@ -123,8 +123,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
123123

124124
nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
125125
NULL, false, false);
126-
rte = nsitem->p_rte;
127-
rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
126+
127+
perminfo = nsitem->p_perminfo;
128+
perminfo->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
128129

129130
if (stmt->whereClause)
130131
{
@@ -150,15 +151,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
150151
attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
151152
foreach(cur, attnums)
152153
{
153-
int attno = lfirst_int(cur) -
154-
FirstLowInvalidHeapAttributeNumber;
154+
int attno;
155+
Bitmapset **bms;
155156

156-
if (is_from)
157-
rte->insertedCols = bms_add_member(rte->insertedCols, attno);
158-
else
159-
rte->selectedCols = bms_add_member(rte->selectedCols, attno);
157+
attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
158+
bms = is_from ? &perminfo->insertedCols : &perminfo->selectedCols;
159+
160+
*bms = bms_add_member(*bms, attno);
160161
}
161-
ExecCheckRTPerms(pstate->p_rtable, true);
162+
ExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);
162163

163164
/*
164165
* Permission check for row security policies.
@@ -174,7 +175,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
174175
* If RLS is not enabled for this, then just fall through to the
175176
* normal non-filtering relation handling.
176177
*/
177-
if (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)
178+
if (check_enable_rls(relid, InvalidOid, false) == RLS_ENABLED)
178179
{
179180
SelectStmt *select;
180181
ColumnRef *cr;

src/backend/commands/copyfrom.c

+10-1
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,12 @@ CopyFrom(CopyFromState cstate)
761761
resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
762762
ExecInitResultRelation(estate, resultRelInfo, 1);
763763

764+
/*
765+
* Copy the RTEPermissionInfos into estate as well, so that
766+
* ExecGetInsertedCols() et al will work correctly.
767+
*/
768+
estate->es_rteperminfos = cstate->rteperminfos;
769+
764770
/* Verify the named relation is a valid target for INSERT */
765771
CheckValidResultRel(resultRelInfo, CMD_INSERT);
766772

@@ -1525,9 +1531,12 @@ BeginCopyFrom(ParseState *pstate,
15251531

15261532
initStringInfo(&cstate->attribute_buf);
15271533

1528-
/* Assign range table, we'll need it in CopyFrom. */
1534+
/* Assign range table and rteperminfos, we'll need them in CopyFrom. */
15291535
if (pstate)
1536+
{
15301537
cstate->range_table = pstate->p_rtable;
1538+
cstate->rteperminfos = pstate->p_rteperminfos;
1539+
}
15311540

15321541
tupDesc = RelationGetDescr(cstate->rel);
15331542
num_phys_attrs = tupDesc->natts;

src/backend/commands/view.c

+29-4
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
367367
* by 2...
368368
*
369369
* These extra RT entries are not actually used in the query,
370-
* except for run-time locking and permission checking.
370+
* except for run-time locking.
371371
*---------------------------------------------------------------
372372
*/
373373
static Query *
@@ -378,7 +378,9 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
378378
ParseNamespaceItem *nsitem;
379379
RangeTblEntry *rt_entry1,
380380
*rt_entry2;
381+
RTEPermissionInfo *rte_perminfo1;
381382
ParseState *pstate;
383+
ListCell *lc;
382384

383385
/*
384386
* Make a copy of the given parsetree. It's not so much that we don't
@@ -405,15 +407,38 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
405407
makeAlias("old", NIL),
406408
false, false);
407409
rt_entry1 = nsitem->p_rte;
410+
rte_perminfo1 = nsitem->p_perminfo;
408411
nsitem = addRangeTableEntryForRelation(pstate, viewRel,
409412
AccessShareLock,
410413
makeAlias("new", NIL),
411414
false, false);
412415
rt_entry2 = nsitem->p_rte;
413416

414-
/* Must override addRangeTableEntry's default access-check flags */
415-
rt_entry1->requiredPerms = 0;
416-
rt_entry2->requiredPerms = 0;
417+
/*
418+
* Add only the "old" RTEPermissionInfo at the head of view query's list
419+
* and update the other RTEs' perminfoindex accordingly. When rewriting a
420+
* query on the view, ApplyRetrieveRule() will transfer the view
421+
* relation's permission details into this RTEPermissionInfo. That's
422+
* needed because the view's RTE itself will be transposed into a subquery
423+
* RTE that can't carry the permission details; see the code stanza toward
424+
* the end of ApplyRetrieveRule() for how that's done.
425+
*/
426+
viewParse->rteperminfos = lcons(rte_perminfo1, viewParse->rteperminfos);
427+
foreach(lc, viewParse->rtable)
428+
{
429+
RangeTblEntry *rte = lfirst(lc);
430+
431+
if (rte->perminfoindex > 0)
432+
rte->perminfoindex += 1;
433+
}
434+
435+
/*
436+
* Also make the "new" RTE's RTEPermissionInfo undiscoverable. This is a
437+
* bit of a hack given that all the non-child RTE_RELATION entries really
438+
* should have a RTEPermissionInfo, but this dummy "new" RTE is going to
439+
* go away anyway in the very near future.
440+
*/
441+
rt_entry2->perminfoindex = 0;
417442

418443
new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
419444

0 commit comments

Comments
 (0)