/src/postgres/src/backend/rewrite/rowsecurity.c
Line | Count | Source |
1 | | /* |
2 | | * rewrite/rowsecurity.c |
3 | | * Routines to support policies for row-level security (aka RLS). |
4 | | * |
5 | | * Policies in PostgreSQL provide a mechanism to limit what records are |
6 | | * returned to a user and what records a user is permitted to add to a table. |
7 | | * |
8 | | * Policies can be defined for specific roles, specific commands, or provided |
9 | | * by an extension. Row security can also be enabled for a table without any |
10 | | * policies being explicitly defined, in which case a default-deny policy is |
11 | | * applied. |
12 | | * |
13 | | * Any part of the system which is returning records back to the user, or |
14 | | * which is accepting records from the user to add to a table, needs to |
15 | | * consider the policies associated with the table (if any). For normal |
16 | | * queries, this is handled by calling get_row_security_policies() during |
17 | | * rewrite, for each RTE in the query. This returns the expressions defined |
18 | | * by the table's policies as a list that is prepended to the securityQuals |
19 | | * list for the RTE. For queries which modify the table, any WITH CHECK |
20 | | * clauses from the table's policies are also returned and prepended to the |
21 | | * list of WithCheckOptions for the Query to check each row that is being |
22 | | * added to the table. Other parts of the system (eg: COPY) simply construct |
23 | | * a normal query and use that, if RLS is to be applied. |
24 | | * |
25 | | * The check to see if RLS should be enabled is provided through |
26 | | * check_enable_rls(), which returns an enum (defined in rowsecurity.h) to |
27 | | * indicate if RLS should be enabled (RLS_ENABLED), or bypassed (RLS_NONE or |
28 | | * RLS_NONE_ENV). RLS_NONE_ENV indicates that RLS should be bypassed |
29 | | * in the current environment, but that may change if the row_security GUC or |
30 | | * the current role changes. |
31 | | * |
32 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
33 | | * Portions Copyright (c) 1994, Regents of the University of California |
34 | | */ |
35 | | #include "postgres.h" |
36 | | |
37 | | #include "access/table.h" |
38 | | #include "catalog/pg_class.h" |
39 | | #include "catalog/pg_type.h" |
40 | | #include "miscadmin.h" |
41 | | #include "nodes/makefuncs.h" |
42 | | #include "nodes/pg_list.h" |
43 | | #include "parser/parse_relation.h" |
44 | | #include "rewrite/rewriteDefine.h" |
45 | | #include "rewrite/rewriteManip.h" |
46 | | #include "rewrite/rowsecurity.h" |
47 | | #include "utils/acl.h" |
48 | | #include "utils/rel.h" |
49 | | #include "utils/rls.h" |
50 | | |
51 | | static void get_policies_for_relation(Relation relation, |
52 | | CmdType cmd, Oid user_id, |
53 | | List **permissive_policies, |
54 | | List **restrictive_policies); |
55 | | |
56 | | static void sort_policies_by_name(List *policies); |
57 | | |
58 | | static int row_security_policy_cmp(const ListCell *a, const ListCell *b); |
59 | | |
60 | | static void add_security_quals(int rt_index, |
61 | | List *permissive_policies, |
62 | | List *restrictive_policies, |
63 | | List **securityQuals, |
64 | | bool *hasSubLinks); |
65 | | |
66 | | static void add_with_check_options(Relation rel, |
67 | | int rt_index, |
68 | | WCOKind kind, |
69 | | List *permissive_policies, |
70 | | List *restrictive_policies, |
71 | | List **withCheckOptions, |
72 | | bool *hasSubLinks, |
73 | | bool force_using); |
74 | | |
75 | | static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id); |
76 | | |
77 | | /* |
78 | | * hooks to allow extensions to add their own security policies |
79 | | * |
80 | | * row_security_policy_hook_permissive can be used to add policies which |
81 | | * are combined with the other permissive policies, using OR. |
82 | | * |
83 | | * row_security_policy_hook_restrictive can be used to add policies which |
84 | | * are enforced, regardless of other policies (they are combined using AND). |
85 | | */ |
86 | | row_security_policy_hook_type row_security_policy_hook_permissive = NULL; |
87 | | row_security_policy_hook_type row_security_policy_hook_restrictive = NULL; |
88 | | |
89 | | /* |
90 | | * Get any row security quals and WithCheckOption checks that should be |
91 | | * applied to the specified RTE. |
92 | | * |
93 | | * In addition, hasRowSecurity is set to true if row-level security is enabled |
94 | | * (even if this RTE doesn't have any row security quals), and hasSubLinks is |
95 | | * set to true if any of the quals returned contain sublinks. |
96 | | */ |
97 | | void |
98 | | get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, |
99 | | List **securityQuals, List **withCheckOptions, |
100 | | bool *hasRowSecurity, bool *hasSubLinks) |
101 | 0 | { |
102 | 0 | Oid user_id; |
103 | 0 | int rls_status; |
104 | 0 | Relation rel; |
105 | 0 | CmdType commandType; |
106 | 0 | List *permissive_policies; |
107 | 0 | List *restrictive_policies; |
108 | 0 | RTEPermissionInfo *perminfo; |
109 | | |
110 | | /* Defaults for the return values */ |
111 | 0 | *securityQuals = NIL; |
112 | 0 | *withCheckOptions = NIL; |
113 | 0 | *hasRowSecurity = false; |
114 | 0 | *hasSubLinks = false; |
115 | |
|
116 | 0 | Assert(rte->rtekind == RTE_RELATION); |
117 | | |
118 | | /* If this is not a normal relation, just return immediately */ |
119 | 0 | if (rte->relkind != RELKIND_RELATION && |
120 | 0 | rte->relkind != RELKIND_PARTITIONED_TABLE) |
121 | 0 | return; |
122 | | |
123 | 0 | perminfo = getRTEPermissionInfo(root->rteperminfos, rte); |
124 | | |
125 | | /* Switch to checkAsUser if it's set */ |
126 | 0 | user_id = OidIsValid(perminfo->checkAsUser) ? |
127 | 0 | perminfo->checkAsUser : GetUserId(); |
128 | | |
129 | | /* Determine the state of RLS for this, pass checkAsUser explicitly */ |
130 | 0 | rls_status = check_enable_rls(rte->relid, perminfo->checkAsUser, false); |
131 | | |
132 | | /* If there is no RLS on this table at all, nothing to do */ |
133 | 0 | if (rls_status == RLS_NONE) |
134 | 0 | return; |
135 | | |
136 | | /* |
137 | | * RLS_NONE_ENV means we are not doing any RLS now, but that may change |
138 | | * with changes to the environment, so we mark it as hasRowSecurity to |
139 | | * force a re-plan when the environment changes. |
140 | | */ |
141 | 0 | if (rls_status == RLS_NONE_ENV) |
142 | 0 | { |
143 | | /* |
144 | | * Indicate that this query may involve RLS and must therefore be |
145 | | * replanned if the environment changes (GUCs, role), but we are not |
146 | | * adding anything here. |
147 | | */ |
148 | 0 | *hasRowSecurity = true; |
149 | |
|
150 | 0 | return; |
151 | 0 | } |
152 | | |
153 | | /* |
154 | | * RLS is enabled for this relation. |
155 | | * |
156 | | * Get the security policies that should be applied, based on the command |
157 | | * type. Note that if this isn't the target relation, we actually want |
158 | | * the relation's SELECT policies, regardless of the query command type, |
159 | | * for example in UPDATE t1 ... FROM t2 we need to apply t1's UPDATE |
160 | | * policies and t2's SELECT policies. |
161 | | */ |
162 | 0 | rel = table_open(rte->relid, NoLock); |
163 | |
|
164 | 0 | commandType = rt_index == root->resultRelation ? |
165 | 0 | root->commandType : CMD_SELECT; |
166 | | |
167 | | /* |
168 | | * In some cases, we need to apply USING policies (which control the |
169 | | * visibility of records) associated with multiple command types (see |
170 | | * specific cases below). |
171 | | * |
172 | | * When considering the order in which to apply these USING policies, we |
173 | | * prefer to apply higher privileged policies, those which allow the user |
174 | | * to lock records (UPDATE and DELETE), first, followed by policies which |
175 | | * don't (SELECT). |
176 | | * |
177 | | * Note that the optimizer is free to push down and reorder quals which |
178 | | * use leakproof functions. |
179 | | * |
180 | | * In all cases, if there are no policy clauses allowing access to rows in |
181 | | * the table for the specific type of operation, then a single |
182 | | * always-false clause (a default-deny policy) will be added (see |
183 | | * add_security_quals). |
184 | | */ |
185 | | |
186 | | /* |
187 | | * For a SELECT, if UPDATE privileges are required (eg: the user has |
188 | | * specified FOR [KEY] UPDATE/SHARE), then add the UPDATE USING quals |
189 | | * first. |
190 | | * |
191 | | * This way, we filter out any records from the SELECT FOR SHARE/UPDATE |
192 | | * which the user does not have access to via the UPDATE USING policies, |
193 | | * similar to how we require normal UPDATE rights for these queries. |
194 | | */ |
195 | 0 | if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE) |
196 | 0 | { |
197 | 0 | List *update_permissive_policies; |
198 | 0 | List *update_restrictive_policies; |
199 | |
|
200 | 0 | get_policies_for_relation(rel, CMD_UPDATE, user_id, |
201 | 0 | &update_permissive_policies, |
202 | 0 | &update_restrictive_policies); |
203 | |
|
204 | 0 | add_security_quals(rt_index, |
205 | 0 | update_permissive_policies, |
206 | 0 | update_restrictive_policies, |
207 | 0 | securityQuals, |
208 | 0 | hasSubLinks); |
209 | 0 | } |
210 | | |
211 | | /* |
212 | | * For SELECT, UPDATE and DELETE, add security quals to enforce the USING |
213 | | * policies. These security quals control access to existing table rows. |
214 | | * Restrictive policies are combined together using AND, and permissive |
215 | | * policies are combined together using OR. |
216 | | */ |
217 | |
|
218 | 0 | get_policies_for_relation(rel, commandType, user_id, &permissive_policies, |
219 | 0 | &restrictive_policies); |
220 | |
|
221 | 0 | if (commandType == CMD_SELECT || |
222 | 0 | commandType == CMD_UPDATE || |
223 | 0 | commandType == CMD_DELETE) |
224 | 0 | add_security_quals(rt_index, |
225 | 0 | permissive_policies, |
226 | 0 | restrictive_policies, |
227 | 0 | securityQuals, |
228 | 0 | hasSubLinks); |
229 | | |
230 | | /* |
231 | | * Similar to above, during an UPDATE, DELETE, or MERGE, if SELECT rights |
232 | | * are also required (eg: when a RETURNING clause exists, or the user has |
233 | | * provided a WHERE clause which involves columns from the relation), we |
234 | | * collect up CMD_SELECT policies and add them via add_security_quals |
235 | | * first. |
236 | | * |
237 | | * This way, we filter out any records which are not visible through an |
238 | | * ALL or SELECT USING policy. |
239 | | */ |
240 | 0 | if ((commandType == CMD_UPDATE || commandType == CMD_DELETE || |
241 | 0 | commandType == CMD_MERGE) && |
242 | 0 | perminfo->requiredPerms & ACL_SELECT) |
243 | 0 | { |
244 | 0 | List *select_permissive_policies; |
245 | 0 | List *select_restrictive_policies; |
246 | |
|
247 | 0 | get_policies_for_relation(rel, CMD_SELECT, user_id, |
248 | 0 | &select_permissive_policies, |
249 | 0 | &select_restrictive_policies); |
250 | |
|
251 | 0 | add_security_quals(rt_index, |
252 | 0 | select_permissive_policies, |
253 | 0 | select_restrictive_policies, |
254 | 0 | securityQuals, |
255 | 0 | hasSubLinks); |
256 | 0 | } |
257 | | |
258 | | /* |
259 | | * For INSERT and UPDATE, add withCheckOptions to verify that any new |
260 | | * records added are consistent with the security policies. This will use |
261 | | * each policy's WITH CHECK clause, or its USING clause if no explicit |
262 | | * WITH CHECK clause is defined. |
263 | | */ |
264 | 0 | if (commandType == CMD_INSERT || commandType == CMD_UPDATE) |
265 | 0 | { |
266 | | /* This should be the target relation */ |
267 | 0 | Assert(rt_index == root->resultRelation); |
268 | |
|
269 | 0 | add_with_check_options(rel, rt_index, |
270 | 0 | commandType == CMD_INSERT ? |
271 | 0 | WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, |
272 | 0 | permissive_policies, |
273 | 0 | restrictive_policies, |
274 | 0 | withCheckOptions, |
275 | 0 | hasSubLinks, |
276 | 0 | false); |
277 | | |
278 | | /* |
279 | | * Get and add ALL/SELECT policies, if SELECT rights are required for |
280 | | * this relation (eg: when RETURNING is used). These are added as WCO |
281 | | * policies rather than security quals to ensure that an error is |
282 | | * raised if a policy is violated; otherwise, we might end up silently |
283 | | * dropping rows to be added. |
284 | | */ |
285 | 0 | if (perminfo->requiredPerms & ACL_SELECT) |
286 | 0 | { |
287 | 0 | List *select_permissive_policies = NIL; |
288 | 0 | List *select_restrictive_policies = NIL; |
289 | |
|
290 | 0 | get_policies_for_relation(rel, CMD_SELECT, user_id, |
291 | 0 | &select_permissive_policies, |
292 | 0 | &select_restrictive_policies); |
293 | 0 | add_with_check_options(rel, rt_index, |
294 | 0 | commandType == CMD_INSERT ? |
295 | 0 | WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, |
296 | 0 | select_permissive_policies, |
297 | 0 | select_restrictive_policies, |
298 | 0 | withCheckOptions, |
299 | 0 | hasSubLinks, |
300 | 0 | true); |
301 | 0 | } |
302 | | |
303 | | /* |
304 | | * For INSERT ... ON CONFLICT DO SELECT/UPDATE we need additional |
305 | | * policy checks for the SELECT/UPDATE which may be applied to the |
306 | | * same RTE. |
307 | | */ |
308 | 0 | if (commandType == CMD_INSERT && root->onConflict && |
309 | 0 | (root->onConflict->action == ONCONFLICT_UPDATE || |
310 | 0 | root->onConflict->action == ONCONFLICT_SELECT)) |
311 | 0 | { |
312 | 0 | List *conflict_permissive_policies = NIL; |
313 | 0 | List *conflict_restrictive_policies = NIL; |
314 | 0 | List *conflict_select_permissive_policies = NIL; |
315 | 0 | List *conflict_select_restrictive_policies = NIL; |
316 | |
|
317 | 0 | if (perminfo->requiredPerms & ACL_UPDATE) |
318 | 0 | { |
319 | | /* |
320 | | * Get the policies that apply to the auxiliary UPDATE or |
321 | | * SELECT FOR UPDATE/SHARE. |
322 | | */ |
323 | 0 | get_policies_for_relation(rel, CMD_UPDATE, user_id, |
324 | 0 | &conflict_permissive_policies, |
325 | 0 | &conflict_restrictive_policies); |
326 | | |
327 | | /* |
328 | | * Enforce the USING clauses of the UPDATE policies using WCOs |
329 | | * rather than security quals. This ensures that an error is |
330 | | * raised if the conflicting row cannot be updated/locked due |
331 | | * to RLS, rather than the change being silently dropped. |
332 | | */ |
333 | 0 | add_with_check_options(rel, rt_index, |
334 | 0 | WCO_RLS_CONFLICT_CHECK, |
335 | 0 | conflict_permissive_policies, |
336 | 0 | conflict_restrictive_policies, |
337 | 0 | withCheckOptions, |
338 | 0 | hasSubLinks, |
339 | 0 | true); |
340 | 0 | } |
341 | | |
342 | | /* |
343 | | * Get and add ALL/SELECT policies, as WCO_RLS_CONFLICT_CHECK WCOs |
344 | | * to ensure they are considered when taking the SELECT/UPDATE |
345 | | * path of an INSERT .. ON CONFLICT, if SELECT rights are required |
346 | | * for this relation, also as WCO policies, again, to avoid |
347 | | * silently dropping data. See above. |
348 | | */ |
349 | 0 | if (perminfo->requiredPerms & ACL_SELECT) |
350 | 0 | { |
351 | 0 | get_policies_for_relation(rel, CMD_SELECT, user_id, |
352 | 0 | &conflict_select_permissive_policies, |
353 | 0 | &conflict_select_restrictive_policies); |
354 | 0 | add_with_check_options(rel, rt_index, |
355 | 0 | WCO_RLS_CONFLICT_CHECK, |
356 | 0 | conflict_select_permissive_policies, |
357 | 0 | conflict_select_restrictive_policies, |
358 | 0 | withCheckOptions, |
359 | 0 | hasSubLinks, |
360 | 0 | true); |
361 | 0 | } |
362 | | |
363 | | /* |
364 | | * For INSERT .. ON CONFLICT DO UPDATE, add additional policies to |
365 | | * be checked when the auxiliary UPDATE is executed. |
366 | | */ |
367 | 0 | if (root->onConflict->action == ONCONFLICT_UPDATE) |
368 | 0 | { |
369 | | /* Enforce the WITH CHECK clauses of the UPDATE policies */ |
370 | 0 | add_with_check_options(rel, rt_index, |
371 | 0 | WCO_RLS_UPDATE_CHECK, |
372 | 0 | conflict_permissive_policies, |
373 | 0 | conflict_restrictive_policies, |
374 | 0 | withCheckOptions, |
375 | 0 | hasSubLinks, |
376 | 0 | false); |
377 | | |
378 | | /* |
379 | | * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to |
380 | | * ensure that the final updated row is visible when taking |
381 | | * the UPDATE path of an INSERT .. ON CONFLICT, if SELECT |
382 | | * rights are required for this relation. |
383 | | */ |
384 | 0 | if (perminfo->requiredPerms & ACL_SELECT) |
385 | 0 | add_with_check_options(rel, rt_index, |
386 | 0 | WCO_RLS_UPDATE_CHECK, |
387 | 0 | conflict_select_permissive_policies, |
388 | 0 | conflict_select_restrictive_policies, |
389 | 0 | withCheckOptions, |
390 | 0 | hasSubLinks, |
391 | 0 | true); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | /* |
397 | | * UPDATE/DELETE FOR PORTION OF may insert leftover rows to preserve the |
398 | | * portions of the old row not covered by the target range. Those hidden |
399 | | * inserts go through ExecInsert(), so they need the same INSERT RLS WITH |
400 | | * CHECK options as ordinary INSERTs. SELECT rights are never needed for |
401 | | * the leftover rows, because they are not considered by RETURNING. |
402 | | */ |
403 | 0 | if (root->forPortionOf != NULL && rt_index == root->resultRelation && |
404 | 0 | (commandType == CMD_UPDATE || commandType == CMD_DELETE)) |
405 | 0 | { |
406 | 0 | List *insert_permissive_policies; |
407 | 0 | List *insert_restrictive_policies; |
408 | |
|
409 | 0 | get_policies_for_relation(rel, CMD_INSERT, user_id, |
410 | 0 | &insert_permissive_policies, |
411 | 0 | &insert_restrictive_policies); |
412 | 0 | add_with_check_options(rel, rt_index, |
413 | 0 | WCO_RLS_INSERT_CHECK, |
414 | 0 | insert_permissive_policies, |
415 | 0 | insert_restrictive_policies, |
416 | 0 | withCheckOptions, |
417 | 0 | hasSubLinks, |
418 | 0 | false); |
419 | 0 | } |
420 | | |
421 | | /* |
422 | | * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL) |
423 | | * and set them up so that we can enforce the appropriate policy depending |
424 | | * on the final action we take. |
425 | | * |
426 | | * We already fetched the SELECT policies above, to check existing rows, |
427 | | * but we must also check that new rows created by INSERT/UPDATE actions |
428 | | * are visible, if SELECT rights are required. For INSERT actions, we only |
429 | | * do this if RETURNING is specified, to be consistent with a plain INSERT |
430 | | * command, which can only require SELECT rights when RETURNING is used. |
431 | | * |
432 | | * We don't push the UPDATE/DELETE USING quals to the RTE because we don't |
433 | | * really want to apply them while scanning the relation since we don't |
434 | | * know whether we will be doing an UPDATE or a DELETE at the end. We |
435 | | * apply the respective policy once we decide the final action on the |
436 | | * target tuple. |
437 | | * |
438 | | * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits |
439 | | * UPDATE/DELETE on the target row, we shall throw an error instead of |
440 | | * silently ignoring the row. This is different than how normal |
441 | | * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO |
442 | | * SELECT/UPDATE handling. |
443 | | */ |
444 | 0 | if (commandType == CMD_MERGE) |
445 | 0 | { |
446 | 0 | List *merge_update_permissive_policies; |
447 | 0 | List *merge_update_restrictive_policies; |
448 | 0 | List *merge_delete_permissive_policies; |
449 | 0 | List *merge_delete_restrictive_policies; |
450 | 0 | List *merge_insert_permissive_policies; |
451 | 0 | List *merge_insert_restrictive_policies; |
452 | 0 | List *merge_select_permissive_policies = NIL; |
453 | 0 | List *merge_select_restrictive_policies = NIL; |
454 | | |
455 | | /* |
456 | | * Fetch the UPDATE policies and set them up to execute on the |
457 | | * existing target row before doing UPDATE. |
458 | | */ |
459 | 0 | get_policies_for_relation(rel, CMD_UPDATE, user_id, |
460 | 0 | &merge_update_permissive_policies, |
461 | 0 | &merge_update_restrictive_policies); |
462 | | |
463 | | /* |
464 | | * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on |
465 | | * the existing target row. |
466 | | */ |
467 | 0 | add_with_check_options(rel, rt_index, |
468 | 0 | WCO_RLS_MERGE_UPDATE_CHECK, |
469 | 0 | merge_update_permissive_policies, |
470 | 0 | merge_update_restrictive_policies, |
471 | 0 | withCheckOptions, |
472 | 0 | hasSubLinks, |
473 | 0 | true); |
474 | | |
475 | | /* Enforce the WITH CHECK clauses of the UPDATE policies */ |
476 | 0 | add_with_check_options(rel, rt_index, |
477 | 0 | WCO_RLS_UPDATE_CHECK, |
478 | 0 | merge_update_permissive_policies, |
479 | 0 | merge_update_restrictive_policies, |
480 | 0 | withCheckOptions, |
481 | 0 | hasSubLinks, |
482 | 0 | false); |
483 | | |
484 | | /* |
485 | | * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure |
486 | | * that the updated row is visible when executing an UPDATE action, if |
487 | | * SELECT rights are required for this relation. |
488 | | */ |
489 | 0 | if (perminfo->requiredPerms & ACL_SELECT) |
490 | 0 | { |
491 | 0 | get_policies_for_relation(rel, CMD_SELECT, user_id, |
492 | 0 | &merge_select_permissive_policies, |
493 | 0 | &merge_select_restrictive_policies); |
494 | 0 | add_with_check_options(rel, rt_index, |
495 | 0 | WCO_RLS_UPDATE_CHECK, |
496 | 0 | merge_select_permissive_policies, |
497 | 0 | merge_select_restrictive_policies, |
498 | 0 | withCheckOptions, |
499 | 0 | hasSubLinks, |
500 | 0 | true); |
501 | 0 | } |
502 | | |
503 | | /* |
504 | | * Fetch the DELETE policies and set them up to execute on the |
505 | | * existing target row before doing DELETE. |
506 | | */ |
507 | 0 | get_policies_for_relation(rel, CMD_DELETE, user_id, |
508 | 0 | &merge_delete_permissive_policies, |
509 | 0 | &merge_delete_restrictive_policies); |
510 | | |
511 | | /* |
512 | | * WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on |
513 | | * the existing target row. |
514 | | */ |
515 | 0 | add_with_check_options(rel, rt_index, |
516 | 0 | WCO_RLS_MERGE_DELETE_CHECK, |
517 | 0 | merge_delete_permissive_policies, |
518 | 0 | merge_delete_restrictive_policies, |
519 | 0 | withCheckOptions, |
520 | 0 | hasSubLinks, |
521 | 0 | true); |
522 | | |
523 | | /* |
524 | | * No special handling is required for INSERT policies. They will be |
525 | | * checked and enforced during ExecInsert(). But we must add them to |
526 | | * withCheckOptions. |
527 | | */ |
528 | 0 | get_policies_for_relation(rel, CMD_INSERT, user_id, |
529 | 0 | &merge_insert_permissive_policies, |
530 | 0 | &merge_insert_restrictive_policies); |
531 | |
|
532 | 0 | add_with_check_options(rel, rt_index, |
533 | 0 | WCO_RLS_INSERT_CHECK, |
534 | 0 | merge_insert_permissive_policies, |
535 | 0 | merge_insert_restrictive_policies, |
536 | 0 | withCheckOptions, |
537 | 0 | hasSubLinks, |
538 | 0 | false); |
539 | | |
540 | | /* |
541 | | * Add ALL/SELECT policies as WCO_RLS_INSERT_CHECK WCOs, to ensure |
542 | | * that the inserted row is visible when executing an INSERT action, |
543 | | * if RETURNING is specified and SELECT rights are required for this |
544 | | * relation. |
545 | | */ |
546 | 0 | if (perminfo->requiredPerms & ACL_SELECT && root->returningList) |
547 | 0 | add_with_check_options(rel, rt_index, |
548 | 0 | WCO_RLS_INSERT_CHECK, |
549 | 0 | merge_select_permissive_policies, |
550 | 0 | merge_select_restrictive_policies, |
551 | 0 | withCheckOptions, |
552 | 0 | hasSubLinks, |
553 | 0 | true); |
554 | 0 | } |
555 | |
|
556 | 0 | table_close(rel, NoLock); |
557 | | |
558 | | /* |
559 | | * Copy checkAsUser to the row security quals and WithCheckOption checks, |
560 | | * in case they contain any subqueries referring to other relations. |
561 | | */ |
562 | 0 | setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser); |
563 | 0 | setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser); |
564 | | |
565 | | /* |
566 | | * Mark this query as having row security, so plancache can invalidate it |
567 | | * when necessary (eg: role changes) |
568 | | */ |
569 | 0 | *hasRowSecurity = true; |
570 | 0 | } |
571 | | |
572 | | /* |
573 | | * get_policies_for_relation |
574 | | * |
575 | | * Returns lists of permissive and restrictive policies to be applied to the |
576 | | * specified relation, based on the command type and role. |
577 | | * |
578 | | * This includes any policies added by extensions. |
579 | | */ |
580 | | static void |
581 | | get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, |
582 | | List **permissive_policies, |
583 | | List **restrictive_policies) |
584 | 0 | { |
585 | 0 | ListCell *item; |
586 | |
|
587 | 0 | *permissive_policies = NIL; |
588 | 0 | *restrictive_policies = NIL; |
589 | | |
590 | | /* First find all internal policies for the relation. */ |
591 | 0 | foreach(item, relation->rd_rsdesc->policies) |
592 | 0 | { |
593 | 0 | bool cmd_matches = false; |
594 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
595 | | |
596 | | /* Always add ALL policies, if they exist. */ |
597 | 0 | if (policy->polcmd == '*') |
598 | 0 | cmd_matches = true; |
599 | 0 | else |
600 | 0 | { |
601 | | /* Check whether the policy applies to the specified command type */ |
602 | 0 | switch (cmd) |
603 | 0 | { |
604 | 0 | case CMD_SELECT: |
605 | 0 | if (policy->polcmd == ACL_SELECT_CHR) |
606 | 0 | cmd_matches = true; |
607 | 0 | break; |
608 | 0 | case CMD_INSERT: |
609 | 0 | if (policy->polcmd == ACL_INSERT_CHR) |
610 | 0 | cmd_matches = true; |
611 | 0 | break; |
612 | 0 | case CMD_UPDATE: |
613 | 0 | if (policy->polcmd == ACL_UPDATE_CHR) |
614 | 0 | cmd_matches = true; |
615 | 0 | break; |
616 | 0 | case CMD_DELETE: |
617 | 0 | if (policy->polcmd == ACL_DELETE_CHR) |
618 | 0 | cmd_matches = true; |
619 | 0 | break; |
620 | 0 | case CMD_MERGE: |
621 | | |
622 | | /* |
623 | | * We do not support a separate policy for MERGE command. |
624 | | * Instead it derives from the policies defined for other |
625 | | * commands. |
626 | | */ |
627 | 0 | break; |
628 | 0 | default: |
629 | 0 | elog(ERROR, "unrecognized policy command type %d", |
630 | 0 | (int) cmd); |
631 | 0 | break; |
632 | 0 | } |
633 | 0 | } |
634 | | |
635 | | /* |
636 | | * Add this policy to the relevant list of policies if it applies to |
637 | | * the specified role. |
638 | | */ |
639 | 0 | if (cmd_matches && check_role_for_policy(policy->roles, user_id)) |
640 | 0 | { |
641 | 0 | if (policy->permissive) |
642 | 0 | *permissive_policies = lappend(*permissive_policies, policy); |
643 | 0 | else |
644 | 0 | *restrictive_policies = lappend(*restrictive_policies, policy); |
645 | 0 | } |
646 | 0 | } |
647 | | |
648 | | /* |
649 | | * We sort restrictive policies by name so that any WCOs they generate are |
650 | | * checked in a well-defined order. |
651 | | */ |
652 | 0 | sort_policies_by_name(*restrictive_policies); |
653 | | |
654 | | /* |
655 | | * Then add any permissive or restrictive policies defined by extensions. |
656 | | * These are simply appended to the lists of internal policies, if they |
657 | | * apply to the specified role. |
658 | | */ |
659 | 0 | if (row_security_policy_hook_restrictive) |
660 | 0 | { |
661 | 0 | List *hook_policies = |
662 | 0 | (*row_security_policy_hook_restrictive) (cmd, relation); |
663 | | |
664 | | /* |
665 | | * As with built-in restrictive policies, we sort any hook-provided |
666 | | * restrictive policies by name also. Note that we also intentionally |
667 | | * always check all built-in restrictive policies, in name order, |
668 | | * before checking restrictive policies added by hooks, in name order. |
669 | | */ |
670 | 0 | sort_policies_by_name(hook_policies); |
671 | |
|
672 | 0 | foreach(item, hook_policies) |
673 | 0 | { |
674 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
675 | |
|
676 | 0 | if (check_role_for_policy(policy->roles, user_id)) |
677 | 0 | *restrictive_policies = lappend(*restrictive_policies, policy); |
678 | 0 | } |
679 | 0 | } |
680 | |
|
681 | 0 | if (row_security_policy_hook_permissive) |
682 | 0 | { |
683 | 0 | List *hook_policies = |
684 | 0 | (*row_security_policy_hook_permissive) (cmd, relation); |
685 | |
|
686 | 0 | foreach(item, hook_policies) |
687 | 0 | { |
688 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
689 | |
|
690 | 0 | if (check_role_for_policy(policy->roles, user_id)) |
691 | 0 | *permissive_policies = lappend(*permissive_policies, policy); |
692 | 0 | } |
693 | 0 | } |
694 | 0 | } |
695 | | |
696 | | /* |
697 | | * sort_policies_by_name |
698 | | * |
699 | | * This is only used for restrictive policies, ensuring that any |
700 | | * WithCheckOptions they generate are applied in a well-defined order. |
701 | | * This is not necessary for permissive policies, since they are all combined |
702 | | * together using OR into a single WithCheckOption check. |
703 | | */ |
704 | | static void |
705 | | sort_policies_by_name(List *policies) |
706 | 0 | { |
707 | 0 | list_sort(policies, row_security_policy_cmp); |
708 | 0 | } |
709 | | |
710 | | /* |
711 | | * list_sort comparator to sort RowSecurityPolicy entries by name |
712 | | */ |
713 | | static int |
714 | | row_security_policy_cmp(const ListCell *a, const ListCell *b) |
715 | 0 | { |
716 | 0 | const RowSecurityPolicy *pa = (const RowSecurityPolicy *) lfirst(a); |
717 | 0 | const RowSecurityPolicy *pb = (const RowSecurityPolicy *) lfirst(b); |
718 | | |
719 | | /* Guard against NULL policy names from extensions */ |
720 | 0 | if (pa->policy_name == NULL) |
721 | 0 | return pb->policy_name == NULL ? 0 : 1; |
722 | 0 | if (pb->policy_name == NULL) |
723 | 0 | return -1; |
724 | | |
725 | 0 | return strcmp(pa->policy_name, pb->policy_name); |
726 | 0 | } |
727 | | |
728 | | /* |
729 | | * add_security_quals |
730 | | * |
731 | | * Add security quals to enforce the specified RLS policies, restricting |
732 | | * access to existing data in a table. If there are no policies controlling |
733 | | * access to the table, then all access is prohibited --- i.e., an implicit |
734 | | * default-deny policy is used. |
735 | | * |
736 | | * New security quals are added to securityQuals, and hasSubLinks is set to |
737 | | * true if any of the quals added contain sublink subqueries. |
738 | | */ |
739 | | static void |
740 | | add_security_quals(int rt_index, |
741 | | List *permissive_policies, |
742 | | List *restrictive_policies, |
743 | | List **securityQuals, |
744 | | bool *hasSubLinks) |
745 | 0 | { |
746 | 0 | ListCell *item; |
747 | 0 | List *permissive_quals = NIL; |
748 | 0 | Expr *rowsec_expr; |
749 | | |
750 | | /* |
751 | | * First collect up the permissive quals. If we do not find any |
752 | | * permissive policies then no rows are visible (this is handled below). |
753 | | */ |
754 | 0 | foreach(item, permissive_policies) |
755 | 0 | { |
756 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
757 | |
|
758 | 0 | if (policy->qual != NULL) |
759 | 0 | { |
760 | 0 | permissive_quals = lappend(permissive_quals, |
761 | 0 | copyObject(policy->qual)); |
762 | 0 | *hasSubLinks |= policy->hassublinks; |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | /* |
767 | | * We must have permissive quals, always, or no rows are visible. |
768 | | * |
769 | | * If we do not, then we simply return a single 'false' qual which results |
770 | | * in no rows being visible. |
771 | | */ |
772 | 0 | if (permissive_quals != NIL) |
773 | 0 | { |
774 | | /* |
775 | | * We now know that permissive policies exist, so we can now add |
776 | | * security quals based on the USING clauses from the restrictive |
777 | | * policies. Since these need to be combined together using AND, we |
778 | | * can just add them one at a time. |
779 | | */ |
780 | 0 | foreach(item, restrictive_policies) |
781 | 0 | { |
782 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
783 | 0 | Expr *qual; |
784 | |
|
785 | 0 | if (policy->qual != NULL) |
786 | 0 | { |
787 | 0 | qual = copyObject(policy->qual); |
788 | 0 | ChangeVarNodes((Node *) qual, 1, rt_index, 0); |
789 | |
|
790 | 0 | *securityQuals = list_append_unique(*securityQuals, qual); |
791 | 0 | *hasSubLinks |= policy->hassublinks; |
792 | 0 | } |
793 | 0 | } |
794 | | |
795 | | /* |
796 | | * Then add a single security qual combining together the USING |
797 | | * clauses from all the permissive policies using OR. |
798 | | */ |
799 | 0 | if (list_length(permissive_quals) == 1) |
800 | 0 | rowsec_expr = (Expr *) linitial(permissive_quals); |
801 | 0 | else |
802 | 0 | rowsec_expr = makeBoolExpr(OR_EXPR, permissive_quals, -1); |
803 | |
|
804 | 0 | ChangeVarNodes((Node *) rowsec_expr, 1, rt_index, 0); |
805 | 0 | *securityQuals = list_append_unique(*securityQuals, rowsec_expr); |
806 | 0 | } |
807 | 0 | else |
808 | | |
809 | | /* |
810 | | * A permissive policy must exist for rows to be visible at all. |
811 | | * Therefore, if there were no permissive policies found, return a |
812 | | * single always-false clause. |
813 | | */ |
814 | 0 | *securityQuals = lappend(*securityQuals, |
815 | 0 | makeConst(BOOLOID, -1, InvalidOid, |
816 | 0 | sizeof(bool), BoolGetDatum(false), |
817 | 0 | false, true)); |
818 | 0 | } |
819 | | |
820 | | /* |
821 | | * add_with_check_options |
822 | | * |
823 | | * Add WithCheckOptions of the specified kind to check that new records |
824 | | * added by an INSERT or UPDATE are consistent with the specified RLS |
825 | | * policies. Normally new data must satisfy the WITH CHECK clauses from the |
826 | | * policies. If a policy has no explicit WITH CHECK clause, its USING clause |
827 | | * is used instead. In the special case of a SELECT or UPDATE arising from an |
828 | | * INSERT ... ON CONFLICT DO SELECT/UPDATE, existing records are first checked |
829 | | * using a WCO_RLS_CONFLICT_CHECK WithCheckOption, which always uses the USING |
830 | | * clauses from RLS policies. |
831 | | * |
832 | | * New WCOs are added to withCheckOptions, and hasSubLinks is set to true if |
833 | | * any of the check clauses added contain sublink subqueries. |
834 | | */ |
835 | | static void |
836 | | add_with_check_options(Relation rel, |
837 | | int rt_index, |
838 | | WCOKind kind, |
839 | | List *permissive_policies, |
840 | | List *restrictive_policies, |
841 | | List **withCheckOptions, |
842 | | bool *hasSubLinks, |
843 | | bool force_using) |
844 | 0 | { |
845 | 0 | ListCell *item; |
846 | 0 | List *permissive_quals = NIL; |
847 | |
|
848 | 0 | #define QUAL_FOR_WCO(policy) \ |
849 | 0 | ( !force_using && \ |
850 | 0 | (policy)->with_check_qual != NULL ? \ |
851 | 0 | (policy)->with_check_qual : (policy)->qual ) |
852 | | |
853 | | /* |
854 | | * First collect up the permissive policy clauses, similar to |
855 | | * add_security_quals. |
856 | | */ |
857 | 0 | foreach(item, permissive_policies) |
858 | 0 | { |
859 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
860 | 0 | Expr *qual = QUAL_FOR_WCO(policy); |
861 | |
|
862 | 0 | if (qual != NULL) |
863 | 0 | { |
864 | 0 | permissive_quals = lappend(permissive_quals, copyObject(qual)); |
865 | 0 | *hasSubLinks |= policy->hassublinks; |
866 | 0 | } |
867 | 0 | } |
868 | | |
869 | | /* |
870 | | * There must be at least one permissive qual found or no rows are allowed |
871 | | * to be added. This is the same as in add_security_quals. |
872 | | * |
873 | | * If there are no permissive_quals then we fall through and return a |
874 | | * single 'false' WCO, preventing all new rows. |
875 | | */ |
876 | 0 | if (permissive_quals != NIL) |
877 | 0 | { |
878 | | /* |
879 | | * Add a single WithCheckOption for all the permissive policy clauses, |
880 | | * combining them together using OR. This check has no policy name, |
881 | | * since if the check fails it means that no policy granted permission |
882 | | * to perform the update, rather than any particular policy being |
883 | | * violated. |
884 | | */ |
885 | 0 | WithCheckOption *wco; |
886 | |
|
887 | 0 | wco = makeNode(WithCheckOption); |
888 | 0 | wco->kind = kind; |
889 | 0 | wco->relname = pstrdup(RelationGetRelationName(rel)); |
890 | 0 | wco->polname = NULL; |
891 | 0 | wco->cascaded = false; |
892 | |
|
893 | 0 | if (list_length(permissive_quals) == 1) |
894 | 0 | wco->qual = (Node *) linitial(permissive_quals); |
895 | 0 | else |
896 | 0 | wco->qual = (Node *) makeBoolExpr(OR_EXPR, permissive_quals, -1); |
897 | |
|
898 | 0 | ChangeVarNodes(wco->qual, 1, rt_index, 0); |
899 | |
|
900 | 0 | *withCheckOptions = list_append_unique(*withCheckOptions, wco); |
901 | | |
902 | | /* |
903 | | * Now add WithCheckOptions for each of the restrictive policy clauses |
904 | | * (which will be combined together using AND). We use a separate |
905 | | * WithCheckOption for each restrictive policy to allow the policy |
906 | | * name to be included in error reports if the policy is violated. |
907 | | */ |
908 | 0 | foreach(item, restrictive_policies) |
909 | 0 | { |
910 | 0 | RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
911 | 0 | Expr *qual = QUAL_FOR_WCO(policy); |
912 | |
|
913 | 0 | if (qual != NULL) |
914 | 0 | { |
915 | 0 | qual = copyObject(qual); |
916 | 0 | ChangeVarNodes((Node *) qual, 1, rt_index, 0); |
917 | |
|
918 | 0 | wco = makeNode(WithCheckOption); |
919 | 0 | wco->kind = kind; |
920 | 0 | wco->relname = pstrdup(RelationGetRelationName(rel)); |
921 | 0 | wco->polname = pstrdup(policy->policy_name); |
922 | 0 | wco->qual = (Node *) qual; |
923 | 0 | wco->cascaded = false; |
924 | |
|
925 | 0 | *withCheckOptions = list_append_unique(*withCheckOptions, wco); |
926 | 0 | *hasSubLinks |= policy->hassublinks; |
927 | 0 | } |
928 | 0 | } |
929 | 0 | } |
930 | 0 | else |
931 | 0 | { |
932 | | /* |
933 | | * If there were no policy clauses to check new data, add a single |
934 | | * always-false WCO (a default-deny policy). |
935 | | */ |
936 | 0 | WithCheckOption *wco; |
937 | |
|
938 | 0 | wco = makeNode(WithCheckOption); |
939 | 0 | wco->kind = kind; |
940 | 0 | wco->relname = pstrdup(RelationGetRelationName(rel)); |
941 | 0 | wco->polname = NULL; |
942 | 0 | wco->qual = (Node *) makeConst(BOOLOID, -1, InvalidOid, |
943 | 0 | sizeof(bool), BoolGetDatum(false), |
944 | 0 | false, true); |
945 | 0 | wco->cascaded = false; |
946 | |
|
947 | 0 | *withCheckOptions = lappend(*withCheckOptions, wco); |
948 | 0 | } |
949 | 0 | } |
950 | | |
951 | | /* |
952 | | * check_role_for_policy - |
953 | | * determines if the policy should be applied for the current role |
954 | | */ |
955 | | static bool |
956 | | check_role_for_policy(ArrayType *policy_roles, Oid user_id) |
957 | 0 | { |
958 | 0 | int i; |
959 | 0 | Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles); |
960 | | |
961 | | /* Quick fall-thru for policies applied to all roles */ |
962 | 0 | if (roles[0] == ACL_ID_PUBLIC) |
963 | 0 | return true; |
964 | | |
965 | 0 | for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++) |
966 | 0 | { |
967 | 0 | if (has_privs_of_role(user_id, roles[i])) |
968 | 0 | return true; |
969 | 0 | } |
970 | | |
971 | 0 | return false; |
972 | 0 | } |