Skip to content

Commit b59c03f

Browse files
committed
Make view/rule permission checking behave properly with
subqueries in the rule.
1 parent 7c57890 commit b59c03f

File tree

1 file changed

+126
-56
lines changed

1 file changed

+126
-56
lines changed

src/backend/rewrite/locks.c

Lines changed: 126 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
*
99
* IDENTIFICATION
10-
* $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.29 2000/05/30 00:49:51 momjian Exp $
10+
* $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.30 2000/07/09 04:56:32 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -17,9 +17,9 @@
1717
#include "catalog/pg_shadow.h"
1818
#include "optimizer/clauses.h"
1919
#include "rewrite/locks.h"
20+
#include "parser/parsetree.h"
2021
#include "utils/acl.h"
2122
#include "utils/syscache.h"
22-
#include "utils/syscache.h"
2323

2424

2525
/*
@@ -152,93 +152,163 @@ matchLocks(CmdType event,
152152
}
153153

154154

155+
/*
156+
* Check the access permissions of tables that are referred to by a rule.
157+
* We want to check the access permissions using the userid of the rule's
158+
* owner, *not* of the current user (the one accessing the rule). So, we
159+
* do the permission check here and set skipAcl = TRUE in each of the rule's
160+
* RTEs, to prevent the executor from running another check with the current
161+
* user's ID.
162+
*
163+
* XXX This routine is called before the rule's query tree has been copied
164+
* out of the relcache entry where it is kept. Therefore, when we set
165+
* skipAcl = TRUE, we are destructively modifying the relcache entry for
166+
* the event relation! This seems fairly harmless because the relcache
167+
* querytree is only used as a source for the rewriter, but it's a tad
168+
* unclean anyway.
169+
*
170+
* Note that we must check permissions every time, even if skipAcl was
171+
* already set TRUE by a prior call. This ensures that we enforce the
172+
* current permission settings for each referenced table, even if they
173+
* have changed since the relcache entry was loaded.
174+
*/
175+
176+
typedef struct
177+
{
178+
char *evowner;
179+
} checkLockPerms_context;
180+
181+
static bool
182+
checkLockPerms_walker(Node *node,
183+
checkLockPerms_context *context)
184+
{
185+
if (node == NULL)
186+
return false;
187+
if (IsA(node, SubLink))
188+
{
189+
/*
190+
* Standard expression_tree_walker will not recurse into
191+
* subselect, but here we must do so.
192+
*/
193+
SubLink *sub = (SubLink *) node;
194+
195+
if (checkLockPerms_walker((Node *) (sub->lefthand), context))
196+
return true;
197+
if (checkLockPerms_walker((Node *) (sub->subselect), context))
198+
return true;
199+
return false;
200+
}
201+
if (IsA(node, Query))
202+
{
203+
/* Reach here after recursing down into subselect above... */
204+
Query *qry = (Query *) node;
205+
int rtablength = length(qry->rtable);
206+
int i;
207+
208+
/* Check all the RTEs in this query node, except OLD and NEW */
209+
for (i = 1; i <= rtablength; i++)
210+
{
211+
RangeTblEntry *rte = rt_fetch(i, qry->rtable);
212+
int32 reqperm;
213+
int32 aclcheck_res;
214+
215+
if (rte->ref != NULL)
216+
{
217+
if (strcmp(rte->ref->relname, "*NEW*") == 0)
218+
continue;
219+
if (strcmp(rte->ref->relname, "*OLD*") == 0)
220+
continue;
221+
}
222+
223+
if (i == qry->resultRelation)
224+
switch (qry->commandType)
225+
{
226+
case CMD_INSERT:
227+
reqperm = ACL_AP;
228+
break;
229+
default:
230+
reqperm = ACL_WR;
231+
break;
232+
}
233+
else
234+
reqperm = ACL_RD;
235+
236+
aclcheck_res = pg_aclcheck(rte->relname,
237+
context->evowner,
238+
reqperm);
239+
if (aclcheck_res != ACLCHECK_OK)
240+
elog(ERROR, "%s: %s",
241+
rte->relname,
242+
aclcheck_error_strings[aclcheck_res]);
243+
244+
/*
245+
* Mark RTE to prevent executor from checking again with the
246+
* current user's ID...
247+
*/
248+
rte->skipAcl = true;
249+
}
250+
251+
/* If there are sublinks, search for them and check their RTEs */
252+
if (qry->hasSubLinks)
253+
{
254+
if (checkLockPerms_walker((Node *) (qry->targetList), context))
255+
return true;
256+
if (checkLockPerms_walker((Node *) (qry->qual), context))
257+
return true;
258+
if (checkLockPerms_walker((Node *) (qry->havingQual), context))
259+
return true;
260+
}
261+
return false;
262+
}
263+
return expression_tree_walker(node, checkLockPerms_walker,
264+
(void *) context);
265+
}
266+
155267
void
156268
checkLockPerms(List *locks, Query *parsetree, int rt_index)
157269
{
270+
RangeTblEntry *rte;
158271
Relation ev_rel;
159272
HeapTuple usertup;
160-
char *evowner;
161-
RangeTblEntry *rte;
162-
int32 reqperm;
163-
int32 aclcheck_res;
164-
int i;
273+
Form_pg_shadow userform;
274+
checkLockPerms_context context;
165275
List *l;
166276

167277
if (locks == NIL)
168-
return;
278+
return; /* nothing to check */
169279

170280
/*
171-
* Get the usename of the rules event relation owner
281+
* Get the usename of the rule's event relation owner
172282
*/
173-
rte = (RangeTblEntry *) nth(rt_index - 1, parsetree->rtable);
283+
rte = rt_fetch(rt_index, parsetree->rtable);
174284
ev_rel = heap_openr(rte->relname, AccessShareLock);
175285
usertup = SearchSysCacheTuple(SHADOWSYSID,
176286
ObjectIdGetDatum(ev_rel->rd_rel->relowner),
177287
0, 0, 0);
178288
if (!HeapTupleIsValid(usertup))
179-
{
180289
elog(ERROR, "cache lookup for userid %d failed",
181290
ev_rel->rd_rel->relowner);
182-
}
291+
userform = (Form_pg_shadow) GETSTRUCT(usertup);
292+
context.evowner = pstrdup(NameStr(userform->usename));
183293
heap_close(ev_rel, AccessShareLock);
184-
evowner = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(usertup))->usename));
185294

186295
/*
187-
* Check all the locks, that should get fired on this query
296+
* Check all the locks that should get fired on this query
188297
*/
189298
foreach(l, locks)
190299
{
191300
RewriteRule *onelock = (RewriteRule *) lfirst(l);
192301
List *action;
193302

194303
/*
195-
* In each lock check every action
304+
* In each lock check every action. We must scan the action
305+
* recursively in case there are any sub-queries within it.
196306
*/
197307
foreach(action, onelock->actions)
198308
{
199309
Query *query = (Query *) lfirst(action);
200310

201-
/*
202-
* In each action check every rangetable entry for read/write
203-
* permission of the event relations owner depending on if
204-
* it's the result relation (write) or not (read)
205-
*/
206-
for (i = 2; i < length(query->rtable); i++)
207-
{
208-
if (i + 1 == query->resultRelation)
209-
switch (query->resultRelation)
210-
{
211-
case CMD_INSERT:
212-
reqperm = ACL_AP;
213-
break;
214-
default:
215-
reqperm = ACL_WR;
216-
break;
217-
}
218-
else
219-
reqperm = ACL_RD;
220-
221-
rte = (RangeTblEntry *) nth(i, query->rtable);
222-
aclcheck_res = pg_aclcheck(rte->relname,
223-
evowner, reqperm);
224-
if (aclcheck_res != ACLCHECK_OK)
225-
{
226-
elog(ERROR, "%s: %s",
227-
rte->relname,
228-
aclcheck_error_strings[aclcheck_res]);
229-
}
230-
231-
/*
232-
* So this is allowed due to the permissions of the rules
233-
* event relation owner. But let's see if the next one too
234-
*/
235-
rte->skipAcl = TRUE;
236-
}
311+
checkLockPerms_walker((Node *) query, &context);
237312
}
238313
}
239-
240-
/*
241-
* Phew, that was close
242-
*/
243-
return;
244314
}

0 commit comments

Comments
 (0)