Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e89bd02

Browse files
committed
Perform RLS WITH CHECK before constraints, etc
The RLS capability is built on top of the WITH CHECK OPTION system which was added for auto-updatable views, however, unlike WCOs on views (which are mandated by the SQL spec to not fire until after all other constraints and checks are done), it makes much more sense for RLS checks to happen earlier than constraint and uniqueness checks. This patch reworks the structure which holds the WCOs a bit to be explicitly either VIEW or RLS checks and the RLS-related checks are done prior to the constraint and uniqueness checks. This also allows better error reporting as we are now reporting when a violation is due to a WITH CHECK OPTION and when it's due to an RLS policy violation, which was independently noted by Craig Ringer as being confusing. The documentation is also updated to include a paragraph about when RLS WITH CHECK handling is performed, as there have been a number of questions regarding that and the documentation was previously silent on the matter. Author: Dean Rasheed, with some kabitzing and comment changes by me.
1 parent c8aa893 commit e89bd02

File tree

15 files changed

+175
-56
lines changed

15 files changed

+175
-56
lines changed

doc/src/sgml/ref/create_policy.sgml

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
6060
expressions, as they are assumed to be trustworthy.
6161
</para>
6262

63+
<para>
64+
For INSERT and UPDATE queries, WITH CHECK expressions are enforced after
65+
BEFORE triggers are fired, and before any data modifications are made.
66+
Thus a BEFORE ROW trigger may modify the data to be inserted, affecting
67+
the result of the security policy check. WITH CHECK expressions are
68+
enforced before any other constraints.
69+
</para>
70+
6371
<para>
6472
Policy names are per-table, therefore one policy name can be used for many
6573
different tables and have a definition for each table which is appropriate to

src/backend/executor/execMain.c

+50-14
Original file line numberDiff line numberDiff line change
@@ -1673,9 +1673,15 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
16731673

16741674
/*
16751675
* ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
1676+
* of the specified kind.
1677+
*
1678+
* Note that this needs to be called multiple times to ensure that all kinds of
1679+
* WITH CHECK OPTIONs are handled (both those from views which have the WITH
1680+
* CHECK OPTION set and from row level security policies). See ExecInsert()
1681+
* and ExecUpdate().
16761682
*/
16771683
void
1678-
ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
1684+
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
16791685
TupleTableSlot *slot, EState *estate)
16801686
{
16811687
Relation rel = resultRelInfo->ri_RelationDesc;
@@ -1700,6 +1706,13 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
17001706
WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
17011707
ExprState *wcoExpr = (ExprState *) lfirst(l2);
17021708

1709+
/*
1710+
* Skip any WCOs which are not the kind we are looking for at this
1711+
* time.
1712+
*/
1713+
if (wco->kind != kind)
1714+
continue;
1715+
17031716
/*
17041717
* WITH CHECK OPTION checks are intended to ensure that the new tuple
17051718
* is visible (in the case of a view) or that it passes the
@@ -1714,19 +1727,42 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
17141727
char *val_desc;
17151728
Bitmapset *modifiedCols;
17161729

1717-
modifiedCols = GetModifiedColumns(resultRelInfo, estate);
1718-
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
1719-
slot,
1720-
tupdesc,
1721-
modifiedCols,
1722-
64);
1723-
1724-
ereport(ERROR,
1725-
(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
1726-
errmsg("new row violates WITH CHECK OPTION for \"%s\"",
1727-
wco->viewname),
1728-
val_desc ? errdetail("Failing row contains %s.", val_desc) :
1729-
0));
1730+
switch (wco->kind)
1731+
{
1732+
/*
1733+
* For WITH CHECK OPTIONs coming from views, we might be able to
1734+
* provide the details on the row, depending on the permissions
1735+
* on the relation (that is, if the user could view it directly
1736+
* anyway). For RLS violations, we don't include the data since
1737+
* we don't know if the user should be able to view the tuple as
1738+
* as that depends on the USING policy.
1739+
*/
1740+
case WCO_VIEW_CHECK:
1741+
modifiedCols = GetModifiedColumns(resultRelInfo, estate);
1742+
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
1743+
slot,
1744+
tupdesc,
1745+
modifiedCols,
1746+
64);
1747+
1748+
ereport(ERROR,
1749+
(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
1750+
errmsg("new row violates WITH CHECK OPTION for \"%s\"",
1751+
wco->relname),
1752+
val_desc ? errdetail("Failing row contains %s.",
1753+
val_desc) : 0));
1754+
break;
1755+
case WCO_RLS_INSERT_CHECK:
1756+
case WCO_RLS_UPDATE_CHECK:
1757+
ereport(ERROR,
1758+
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1759+
errmsg("new row violates row level security policy for \"%s\"",
1760+
wco->relname)));
1761+
break;
1762+
default:
1763+
elog(ERROR, "unrecognized WCO kind: %u", wco->kind);
1764+
break;
1765+
}
17301766
}
17311767
}
17321768
}

src/backend/executor/nodeModifyTable.c

+49-9
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ ExecInsert(TupleTableSlot *slot,
252252
*/
253253
tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
254254

255+
/*
256+
* Check any RLS INSERT WITH CHECK policies
257+
*
258+
* ExecWithCheckOptions() will skip any WCOs which are not of
259+
* the kind we are looking for at this point.
260+
*/
261+
if (resultRelInfo->ri_WithCheckOptions != NIL)
262+
ExecWithCheckOptions(WCO_RLS_INSERT_CHECK,
263+
resultRelInfo, slot, estate);
264+
255265
/*
256266
* Check the constraints of the tuple
257267
*/
@@ -287,9 +297,21 @@ ExecInsert(TupleTableSlot *slot,
287297

288298
list_free(recheckIndexes);
289299

290-
/* Check any WITH CHECK OPTION constraints */
300+
/*
301+
* Check any WITH CHECK OPTION constraints from parent views. We
302+
* are required to do this after testing all constraints and
303+
* uniqueness violations per the SQL spec, so we do it after actually
304+
* inserting the record into the heap and all indexes.
305+
*
306+
* ExecWithCheckOptions will elog(ERROR) if a violation is found, so
307+
* the tuple will never be seen, if it violates the the WITH CHECK
308+
* OPTION.
309+
*
310+
* ExecWithCheckOptions() will skip any WCOs which are not of
311+
* the kind we are looking for at this point.
312+
*/
291313
if (resultRelInfo->ri_WithCheckOptions != NIL)
292-
ExecWithCheckOptions(resultRelInfo, slot, estate);
314+
ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
293315

294316
/* Process RETURNING if present */
295317
if (resultRelInfo->ri_projectReturning)
@@ -653,15 +675,25 @@ ExecUpdate(ItemPointer tupleid,
653675
tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
654676

655677
/*
656-
* Check the constraints of the tuple
678+
* Check any RLS UPDATE WITH CHECK policies
657679
*
658680
* If we generate a new candidate tuple after EvalPlanQual testing, we
659-
* must loop back here and recheck constraints. (We don't need to
660-
* redo triggers, however. If there are any BEFORE triggers then
661-
* trigger.c will have done heap_lock_tuple to lock the correct tuple,
662-
* so there's no need to do them again.)
681+
* must loop back here and recheck any RLS policies and constraints.
682+
* (We don't need to redo triggers, however. If there are any BEFORE
683+
* triggers then trigger.c will have done heap_lock_tuple to lock the
684+
* correct tuple, so there's no need to do them again.)
685+
*
686+
* ExecWithCheckOptions() will skip any WCOs which are not of
687+
* the kind we are looking for at this point.
663688
*/
664689
lreplace:;
690+
if (resultRelInfo->ri_WithCheckOptions != NIL)
691+
ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
692+
resultRelInfo, slot, estate);
693+
694+
/*
695+
* Check the constraints of the tuple
696+
*/
665697
if (resultRelationDesc->rd_att->constr)
666698
ExecConstraints(resultRelInfo, slot, estate);
667699

@@ -780,9 +812,17 @@ lreplace:;
780812

781813
list_free(recheckIndexes);
782814

783-
/* Check any WITH CHECK OPTION constraints */
815+
/*
816+
* Check any WITH CHECK OPTION constraints from parent views. We
817+
* are required to do this after testing all constraints and
818+
* uniqueness violations per the SQL spec, so we do it after actually
819+
* updating the record in the heap and all indexes.
820+
*
821+
* ExecWithCheckOptions() will skip any WCOs which are not of
822+
* the kind we are looking for at this point.
823+
*/
784824
if (resultRelInfo->ri_WithCheckOptions != NIL)
785-
ExecWithCheckOptions(resultRelInfo, slot, estate);
825+
ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
786826

787827
/* Process RETURNING if present */
788828
if (resultRelInfo->ri_projectReturning)

src/backend/nodes/copyfuncs.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -2064,7 +2064,8 @@ _copyWithCheckOption(const WithCheckOption *from)
20642064
{
20652065
WithCheckOption *newnode = makeNode(WithCheckOption);
20662066

2067-
COPY_STRING_FIELD(viewname);
2067+
COPY_SCALAR_FIELD(kind);
2068+
COPY_STRING_FIELD(relname);
20682069
COPY_NODE_FIELD(qual);
20692070
COPY_SCALAR_FIELD(cascaded);
20702071

src/backend/nodes/equalfuncs.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -2363,7 +2363,8 @@ _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
23632363
static bool
23642364
_equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
23652365
{
2366-
COMPARE_STRING_FIELD(viewname);
2366+
COMPARE_SCALAR_FIELD(kind);
2367+
COMPARE_STRING_FIELD(relname);
23672368
COMPARE_NODE_FIELD(qual);
23682369
COMPARE_SCALAR_FIELD(cascaded);
23692370

src/backend/nodes/outfuncs.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -2332,7 +2332,8 @@ _outWithCheckOption(StringInfo str, const WithCheckOption *node)
23322332
{
23332333
WRITE_NODE_TYPE("WITHCHECKOPTION");
23342334

2335-
WRITE_STRING_FIELD(viewname);
2335+
WRITE_ENUM_FIELD(kind, WCOKind);
2336+
WRITE_STRING_FIELD(relname);
23362337
WRITE_NODE_FIELD(qual);
23372338
WRITE_BOOL_FIELD(cascaded);
23382339
}

src/backend/nodes/readfuncs.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ _readWithCheckOption(void)
266266
{
267267
READ_LOCALS(WithCheckOption);
268268

269-
READ_STRING_FIELD(viewname);
269+
READ_ENUM_FIELD(kind, WCOKind);
270+
READ_STRING_FIELD(relname);
270271
READ_NODE_FIELD(qual);
271272
READ_BOOL_FIELD(cascaded);
272273

src/backend/rewrite/rewriteHandler.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -2947,7 +2947,8 @@ rewriteTargetView(Query *parsetree, Relation view)
29472947
WithCheckOption *wco;
29482948

29492949
wco = makeNode(WithCheckOption);
2950-
wco->viewname = pstrdup(RelationGetRelationName(view));
2950+
wco->kind = WCO_VIEW_CHECK;
2951+
wco->relname = pstrdup(RelationGetRelationName(view));
29512952
wco->qual = NULL;
29522953
wco->cascaded = cascaded;
29532954

src/backend/rewrite/rowsecurity.c

+19-7
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
259259
WithCheckOption *wco;
260260

261261
wco = (WithCheckOption *) makeNode(WithCheckOption);
262-
wco->viewname = pstrdup(RelationGetRelationName(rel));
262+
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
263+
WCO_RLS_UPDATE_CHECK;
264+
wco->relname = pstrdup(RelationGetRelationName(rel));
263265
wco->qual = (Node *) hook_with_check_expr_restrictive;
264266
wco->cascaded = false;
265267
*withCheckOptions = lappend(*withCheckOptions, wco);
@@ -274,7 +276,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
274276
WithCheckOption *wco;
275277

276278
wco = (WithCheckOption *) makeNode(WithCheckOption);
277-
wco->viewname = pstrdup(RelationGetRelationName(rel));
279+
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
280+
WCO_RLS_UPDATE_CHECK;
281+
wco->relname = pstrdup(RelationGetRelationName(rel));
278282
wco->qual = (Node *) rowsec_with_check_expr;
279283
wco->cascaded = false;
280284
*withCheckOptions = lappend(*withCheckOptions, wco);
@@ -285,7 +289,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
285289
WithCheckOption *wco;
286290

287291
wco = (WithCheckOption *) makeNode(WithCheckOption);
288-
wco->viewname = pstrdup(RelationGetRelationName(rel));
292+
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
293+
WCO_RLS_UPDATE_CHECK;
294+
wco->relname = pstrdup(RelationGetRelationName(rel));
289295
wco->qual = (Node *) hook_with_check_expr_permissive;
290296
wco->cascaded = false;
291297
*withCheckOptions = lappend(*withCheckOptions, wco);
@@ -297,13 +303,18 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
297303
List *combined_quals = NIL;
298304
Expr *combined_qual_eval;
299305

300-
combined_quals = lcons(copyObject(rowsec_with_check_expr), combined_quals);
301-
combined_quals = lcons(copyObject(hook_with_check_expr_permissive), combined_quals);
306+
combined_quals = lcons(copyObject(rowsec_with_check_expr),
307+
combined_quals);
308+
309+
combined_quals = lcons(copyObject(hook_with_check_expr_permissive),
310+
combined_quals);
302311

303312
combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
304313

305314
wco = (WithCheckOption *) makeNode(WithCheckOption);
306-
wco->viewname = pstrdup(RelationGetRelationName(rel));
315+
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
316+
WCO_RLS_UPDATE_CHECK;
317+
wco->relname = pstrdup(RelationGetRelationName(rel));
307318
wco->qual = (Node *) combined_qual_eval;
308319
wco->cascaded = false;
309320
*withCheckOptions = lappend(*withCheckOptions, wco);
@@ -332,7 +343,8 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
332343
Expr *combined_qual_eval;
333344

334345
combined_quals = lcons(copyObject(rowsec_expr), combined_quals);
335-
combined_quals = lcons(copyObject(hook_expr_permissive), combined_quals);
346+
combined_quals = lcons(copyObject(hook_expr_permissive),
347+
combined_quals);
336348

337349
combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
338350

src/include/executor/executor.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
193193
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
194194
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
195195
TupleTableSlot *slot, EState *estate);
196-
extern void ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
196+
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
197197
TupleTableSlot *slot, EState *estate);
198198
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
199199
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);

src/include/nodes/execnodes.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ typedef struct JunkFilter
303303
* TrigInstrument optional runtime measurements for triggers
304304
* FdwRoutine FDW callback functions, if foreign table
305305
* FdwState available to save private state of FDW
306-
* WithCheckOptions list of WithCheckOption's for views
306+
* WithCheckOptions list of WithCheckOption's to be checked
307307
* WithCheckOptionExprs list of WithCheckOption expr states
308308
* ConstraintExprs array of constraint-checking expr states
309309
* junkFilter for removing junk attributes from tuples

src/include/nodes/parsenodes.h

+12-3
Original file line numberDiff line numberDiff line change
@@ -872,14 +872,23 @@ typedef struct RangeTblFunction
872872
/*
873873
* WithCheckOption -
874874
* representation of WITH CHECK OPTION checks to be applied to new tuples
875-
* when inserting/updating an auto-updatable view.
875+
* when inserting/updating an auto-updatable view, or RLS WITH CHECK
876+
* policies to be applied when inserting/updating a relation with RLS.
876877
*/
878+
typedef enum WCOKind
879+
{
880+
WCO_VIEW_CHECK, /* WCO on an auto-updatable view */
881+
WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */
882+
WCO_RLS_UPDATE_CHECK /* RLS UPDATE WITH CHECK policy */
883+
} WCOKind;
884+
877885
typedef struct WithCheckOption
878886
{
879887
NodeTag type;
880-
char *viewname; /* name of view that specified the WCO */
888+
WCOKind kind; /* kind of WCO */
889+
char *relname; /* name of relation that specified the WCO */
881890
Node *qual; /* constraint qual to check */
882-
bool cascaded; /* true = WITH CASCADED CHECK OPTION */
891+
bool cascaded; /* true for a cascaded WCO on a view */
883892
} WithCheckOption;
884893

885894
/*

0 commit comments

Comments
 (0)