LCOV - code coverage report
Current view: top level - src/backend/rewrite - rowsecurity.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 198 202 98.0 %
Date: 2024-03-28 13:11:05 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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-2024, 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      396700 : get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
      99             :                           List **securityQuals, List **withCheckOptions,
     100             :                           bool *hasRowSecurity, bool *hasSubLinks)
     101             : {
     102             :     Oid         user_id;
     103             :     int         rls_status;
     104             :     Relation    rel;
     105             :     CmdType     commandType;
     106             :     List       *permissive_policies;
     107             :     List       *restrictive_policies;
     108             :     RTEPermissionInfo *perminfo;
     109             : 
     110             :     /* Defaults for the return values */
     111      396700 :     *securityQuals = NIL;
     112      396700 :     *withCheckOptions = NIL;
     113      396700 :     *hasRowSecurity = false;
     114      396700 :     *hasSubLinks = false;
     115             : 
     116             :     Assert(rte->rtekind == RTE_RELATION);
     117             : 
     118             :     /* If this is not a normal relation, just return immediately */
     119      396700 :     if (rte->relkind != RELKIND_RELATION &&
     120       16366 :         rte->relkind != RELKIND_PARTITIONED_TABLE)
     121      394060 :         return;
     122             : 
     123      396700 :     perminfo = getRTEPermissionInfo(root->rteperminfos, rte);
     124             : 
     125             :     /* Switch to checkAsUser if it's set */
     126      793400 :     user_id = OidIsValid(perminfo->checkAsUser) ?
     127      396700 :         perminfo->checkAsUser : GetUserId();
     128             : 
     129             :     /* Determine the state of RLS for this, pass checkAsUser explicitly */
     130      396700 :     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      396652 :     if (rls_status == RLS_NONE)
     134      393514 :         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        3138 :     if (rls_status == RLS_NONE_ENV)
     142             :     {
     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         546 :         *hasRowSecurity = true;
     149             : 
     150         546 :         return;
     151             :     }
     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        2592 :     rel = table_open(rte->relid, NoLock);
     163             : 
     164        5184 :     commandType = rt_index == root->resultRelation ?
     165        2592 :         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        2592 :     if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE)
     196             :     {
     197             :         List       *update_permissive_policies;
     198             :         List       *update_restrictive_policies;
     199             : 
     200          24 :         get_policies_for_relation(rel, CMD_UPDATE, user_id,
     201             :                                   &update_permissive_policies,
     202             :                                   &update_restrictive_policies);
     203             : 
     204          24 :         add_security_quals(rt_index,
     205             :                            update_permissive_policies,
     206             :                            update_restrictive_policies,
     207             :                            securityQuals,
     208             :                            hasSubLinks);
     209             :     }
     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        2592 :     get_policies_for_relation(rel, commandType, user_id, &permissive_policies,
     219             :                               &restrictive_policies);
     220             : 
     221        2592 :     if (commandType == CMD_SELECT ||
     222         480 :         commandType == CMD_UPDATE ||
     223             :         commandType == CMD_DELETE)
     224        2196 :         add_security_quals(rt_index,
     225             :                            permissive_policies,
     226             :                            restrictive_policies,
     227             :                            securityQuals,
     228             :                            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        2592 :     if ((commandType == CMD_UPDATE || commandType == CMD_DELETE ||
     241         438 :          commandType == CMD_MERGE) &&
     242         438 :         perminfo->requiredPerms & ACL_SELECT)
     243             :     {
     244             :         List       *select_permissive_policies;
     245             :         List       *select_restrictive_policies;
     246             : 
     247         420 :         get_policies_for_relation(rel, CMD_SELECT, user_id,
     248             :                                   &select_permissive_policies,
     249             :                                   &select_restrictive_policies);
     250             : 
     251         420 :         add_security_quals(rt_index,
     252             :                            select_permissive_policies,
     253             :                            select_restrictive_policies,
     254             :                            securityQuals,
     255             :                            hasSubLinks);
     256             :     }
     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        2592 :     if (commandType == CMD_INSERT || commandType == CMD_UPDATE)
     265             :     {
     266             :         /* This should be the target relation */
     267             :         Assert(rt_index == root->resultRelation);
     268             : 
     269         522 :         add_with_check_options(rel, rt_index,
     270             :                                commandType == CMD_INSERT ?
     271             :                                WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK,
     272             :                                permissive_policies,
     273             :                                restrictive_policies,
     274             :                                withCheckOptions,
     275             :                                hasSubLinks,
     276             :                                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         522 :         if (perminfo->requiredPerms & ACL_SELECT)
     286             :         {
     287         348 :             List       *select_permissive_policies = NIL;
     288         348 :             List       *select_restrictive_policies = NIL;
     289             : 
     290         348 :             get_policies_for_relation(rel, CMD_SELECT, user_id,
     291             :                                       &select_permissive_policies,
     292             :                                       &select_restrictive_policies);
     293         348 :             add_with_check_options(rel, rt_index,
     294             :                                    commandType == CMD_INSERT ?
     295             :                                    WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK,
     296             :                                    select_permissive_policies,
     297             :                                    select_restrictive_policies,
     298             :                                    withCheckOptions,
     299             :                                    hasSubLinks,
     300             :                                    true);
     301             :         }
     302             : 
     303             :         /*
     304             :          * For INSERT ... ON CONFLICT DO UPDATE we need additional policy
     305             :          * checks for the UPDATE which may be applied to the same RTE.
     306             :          */
     307         522 :         if (commandType == CMD_INSERT &&
     308         282 :             root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE)
     309             :         {
     310             :             List       *conflict_permissive_policies;
     311             :             List       *conflict_restrictive_policies;
     312          96 :             List       *conflict_select_permissive_policies = NIL;
     313          96 :             List       *conflict_select_restrictive_policies = NIL;
     314             : 
     315             :             /* Get the policies that apply to the auxiliary UPDATE */
     316          96 :             get_policies_for_relation(rel, CMD_UPDATE, user_id,
     317             :                                       &conflict_permissive_policies,
     318             :                                       &conflict_restrictive_policies);
     319             : 
     320             :             /*
     321             :              * Enforce the USING clauses of the UPDATE policies using WCOs
     322             :              * rather than security quals.  This ensures that an error is
     323             :              * raised if the conflicting row cannot be updated due to RLS,
     324             :              * rather than the change being silently dropped.
     325             :              */
     326          96 :             add_with_check_options(rel, rt_index,
     327             :                                    WCO_RLS_CONFLICT_CHECK,
     328             :                                    conflict_permissive_policies,
     329             :                                    conflict_restrictive_policies,
     330             :                                    withCheckOptions,
     331             :                                    hasSubLinks,
     332             :                                    true);
     333             : 
     334             :             /*
     335             :              * Get and add ALL/SELECT policies, as WCO_RLS_CONFLICT_CHECK WCOs
     336             :              * to ensure they are considered when taking the UPDATE path of an
     337             :              * INSERT .. ON CONFLICT DO UPDATE, if SELECT rights are required
     338             :              * for this relation, also as WCO policies, again, to avoid
     339             :              * silently dropping data.  See above.
     340             :              */
     341          96 :             if (perminfo->requiredPerms & ACL_SELECT)
     342             :             {
     343          96 :                 get_policies_for_relation(rel, CMD_SELECT, user_id,
     344             :                                           &conflict_select_permissive_policies,
     345             :                                           &conflict_select_restrictive_policies);
     346          96 :                 add_with_check_options(rel, rt_index,
     347             :                                        WCO_RLS_CONFLICT_CHECK,
     348             :                                        conflict_select_permissive_policies,
     349             :                                        conflict_select_restrictive_policies,
     350             :                                        withCheckOptions,
     351             :                                        hasSubLinks,
     352             :                                        true);
     353             :             }
     354             : 
     355             :             /* Enforce the WITH CHECK clauses of the UPDATE policies */
     356          96 :             add_with_check_options(rel, rt_index,
     357             :                                    WCO_RLS_UPDATE_CHECK,
     358             :                                    conflict_permissive_policies,
     359             :                                    conflict_restrictive_policies,
     360             :                                    withCheckOptions,
     361             :                                    hasSubLinks,
     362             :                                    false);
     363             : 
     364             :             /*
     365             :              * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure
     366             :              * that the final updated row is visible when taking the UPDATE
     367             :              * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights
     368             :              * are required for this relation.
     369             :              */
     370          96 :             if (perminfo->requiredPerms & ACL_SELECT)
     371          96 :                 add_with_check_options(rel, rt_index,
     372             :                                        WCO_RLS_UPDATE_CHECK,
     373             :                                        conflict_select_permissive_policies,
     374             :                                        conflict_select_restrictive_policies,
     375             :                                        withCheckOptions,
     376             :                                        hasSubLinks,
     377             :                                        true);
     378             :         }
     379             :     }
     380             : 
     381             :     /*
     382             :      * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
     383             :      * and set them up so that we can enforce the appropriate policy depending
     384             :      * on the final action we take.
     385             :      *
     386             :      * We already fetched the SELECT policies above, to check existing rows,
     387             :      * but we must also check that new rows created by INSERT/UPDATE actions
     388             :      * are visible, if SELECT rights are required. For INSERT actions, we only
     389             :      * do this if RETURNING is specified, to be consistent with a plain INSERT
     390             :      * command, which can only require SELECT rights when RETURNING is used.
     391             :      *
     392             :      * We don't push the UPDATE/DELETE USING quals to the RTE because we don't
     393             :      * really want to apply them while scanning the relation since we don't
     394             :      * know whether we will be doing an UPDATE or a DELETE at the end. We
     395             :      * apply the respective policy once we decide the final action on the
     396             :      * target tuple.
     397             :      *
     398             :      * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits
     399             :      * UPDATE/DELETE on the target row, we shall throw an error instead of
     400             :      * silently ignoring the row. This is different than how normal
     401             :      * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE
     402             :      * handling.
     403             :      */
     404        2592 :     if (commandType == CMD_MERGE)
     405             :     {
     406             :         List       *merge_update_permissive_policies;
     407             :         List       *merge_update_restrictive_policies;
     408             :         List       *merge_delete_permissive_policies;
     409             :         List       *merge_delete_restrictive_policies;
     410             :         List       *merge_insert_permissive_policies;
     411             :         List       *merge_insert_restrictive_policies;
     412         114 :         List       *merge_select_permissive_policies = NIL;
     413         114 :         List       *merge_select_restrictive_policies = NIL;
     414             : 
     415             :         /*
     416             :          * Fetch the UPDATE policies and set them up to execute on the
     417             :          * existing target row before doing UPDATE.
     418             :          */
     419         114 :         get_policies_for_relation(rel, CMD_UPDATE, user_id,
     420             :                                   &merge_update_permissive_policies,
     421             :                                   &merge_update_restrictive_policies);
     422             : 
     423             :         /*
     424             :          * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
     425             :          * the existing target row.
     426             :          */
     427         114 :         add_with_check_options(rel, rt_index,
     428             :                                WCO_RLS_MERGE_UPDATE_CHECK,
     429             :                                merge_update_permissive_policies,
     430             :                                merge_update_restrictive_policies,
     431             :                                withCheckOptions,
     432             :                                hasSubLinks,
     433             :                                true);
     434             : 
     435             :         /* Enforce the WITH CHECK clauses of the UPDATE policies */
     436         114 :         add_with_check_options(rel, rt_index,
     437             :                                WCO_RLS_UPDATE_CHECK,
     438             :                                merge_update_permissive_policies,
     439             :                                merge_update_restrictive_policies,
     440             :                                withCheckOptions,
     441             :                                hasSubLinks,
     442             :                                false);
     443             : 
     444             :         /*
     445             :          * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure
     446             :          * that the updated row is visible when executing an UPDATE action, if
     447             :          * SELECT rights are required for this relation.
     448             :          */
     449         114 :         if (perminfo->requiredPerms & ACL_SELECT)
     450             :         {
     451         114 :             get_policies_for_relation(rel, CMD_SELECT, user_id,
     452             :                                       &merge_select_permissive_policies,
     453             :                                       &merge_select_restrictive_policies);
     454         114 :             add_with_check_options(rel, rt_index,
     455             :                                    WCO_RLS_UPDATE_CHECK,
     456             :                                    merge_select_permissive_policies,
     457             :                                    merge_select_restrictive_policies,
     458             :                                    withCheckOptions,
     459             :                                    hasSubLinks,
     460             :                                    true);
     461             :         }
     462             : 
     463             :         /*
     464             :          * Fetch the DELETE policies and set them up to execute on the
     465             :          * existing target row before doing DELETE.
     466             :          */
     467         114 :         get_policies_for_relation(rel, CMD_DELETE, user_id,
     468             :                                   &merge_delete_permissive_policies,
     469             :                                   &merge_delete_restrictive_policies);
     470             : 
     471             :         /*
     472             :          * WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on
     473             :          * the existing target row.
     474             :          */
     475         114 :         add_with_check_options(rel, rt_index,
     476             :                                WCO_RLS_MERGE_DELETE_CHECK,
     477             :                                merge_delete_permissive_policies,
     478             :                                merge_delete_restrictive_policies,
     479             :                                withCheckOptions,
     480             :                                hasSubLinks,
     481             :                                true);
     482             : 
     483             :         /*
     484             :          * No special handling is required for INSERT policies. They will be
     485             :          * checked and enforced during ExecInsert(). But we must add them to
     486             :          * withCheckOptions.
     487             :          */
     488         114 :         get_policies_for_relation(rel, CMD_INSERT, user_id,
     489             :                                   &merge_insert_permissive_policies,
     490             :                                   &merge_insert_restrictive_policies);
     491             : 
     492         114 :         add_with_check_options(rel, rt_index,
     493             :                                WCO_RLS_INSERT_CHECK,
     494             :                                merge_insert_permissive_policies,
     495             :                                merge_insert_restrictive_policies,
     496             :                                withCheckOptions,
     497             :                                hasSubLinks,
     498             :                                false);
     499             : 
     500             :         /*
     501             :          * Add ALL/SELECT policies as WCO_RLS_INSERT_CHECK WCOs, to ensure
     502             :          * that the inserted row is visible when executing an INSERT action,
     503             :          * if RETURNING is specified and SELECT rights are required for this
     504             :          * relation.
     505             :          */
     506         114 :         if (perminfo->requiredPerms & ACL_SELECT && root->returningList)
     507          12 :             add_with_check_options(rel, rt_index,
     508             :                                    WCO_RLS_INSERT_CHECK,
     509             :                                    merge_select_permissive_policies,
     510             :                                    merge_select_restrictive_policies,
     511             :                                    withCheckOptions,
     512             :                                    hasSubLinks,
     513             :                                    true);
     514             :     }
     515             : 
     516        2592 :     table_close(rel, NoLock);
     517             : 
     518             :     /*
     519             :      * Copy checkAsUser to the row security quals and WithCheckOption checks,
     520             :      * in case they contain any subqueries referring to other relations.
     521             :      */
     522        2592 :     setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser);
     523        2592 :     setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser);
     524             : 
     525             :     /*
     526             :      * Mark this query as having row security, so plancache can invalidate it
     527             :      * when necessary (eg: role changes)
     528             :      */
     529        2592 :     *hasRowSecurity = true;
     530             : }
     531             : 
     532             : /*
     533             :  * get_policies_for_relation
     534             :  *
     535             :  * Returns lists of permissive and restrictive policies to be applied to the
     536             :  * specified relation, based on the command type and role.
     537             :  *
     538             :  * This includes any policies added by extensions.
     539             :  */
     540             : static void
     541        4032 : get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
     542             :                           List **permissive_policies,
     543             :                           List **restrictive_policies)
     544             : {
     545             :     ListCell   *item;
     546             : 
     547        4032 :     *permissive_policies = NIL;
     548        4032 :     *restrictive_policies = NIL;
     549             : 
     550             :     /* First find all internal policies for the relation. */
     551       13548 :     foreach(item, relation->rd_rsdesc->policies)
     552             :     {
     553        9516 :         bool        cmd_matches = false;
     554        9516 :         RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     555             : 
     556             :         /* Always add ALL policies, if they exist. */
     557        9516 :         if (policy->polcmd == '*')
     558        4560 :             cmd_matches = true;
     559             :         else
     560             :         {
     561             :             /* Check whether the policy applies to the specified command type */
     562        4956 :             switch (cmd)
     563             :             {
     564        2352 :                 case CMD_SELECT:
     565        2352 :                     if (policy->polcmd == ACL_SELECT_CHR)
     566         720 :                         cmd_matches = true;
     567        2352 :                     break;
     568         714 :                 case CMD_INSERT:
     569         714 :                     if (policy->polcmd == ACL_INSERT_CHR)
     570         204 :                         cmd_matches = true;
     571         714 :                     break;
     572         942 :                 case CMD_UPDATE:
     573         942 :                     if (policy->polcmd == ACL_UPDATE_CHR)
     574         318 :                         cmd_matches = true;
     575         942 :                     break;
     576         516 :                 case CMD_DELETE:
     577         516 :                     if (policy->polcmd == ACL_DELETE_CHR)
     578         132 :                         cmd_matches = true;
     579         516 :                     break;
     580         432 :                 case CMD_MERGE:
     581             : 
     582             :                     /*
     583             :                      * We do not support a separate policy for MERGE command.
     584             :                      * Instead it derives from the policies defined for other
     585             :                      * commands.
     586             :                      */
     587         432 :                     break;
     588           0 :                 default:
     589           0 :                     elog(ERROR, "unrecognized policy command type %d",
     590             :                          (int) cmd);
     591             :                     break;
     592             :             }
     593             :         }
     594             : 
     595             :         /*
     596             :          * Add this policy to the relevant list of policies if it applies to
     597             :          * the specified role.
     598             :          */
     599        9516 :         if (cmd_matches && check_role_for_policy(policy->roles, user_id))
     600             :         {
     601        4260 :             if (policy->permissive)
     602        3978 :                 *permissive_policies = lappend(*permissive_policies, policy);
     603             :             else
     604         282 :                 *restrictive_policies = lappend(*restrictive_policies, policy);
     605             :         }
     606             :     }
     607             : 
     608             :     /*
     609             :      * We sort restrictive policies by name so that any WCOs they generate are
     610             :      * checked in a well-defined order.
     611             :      */
     612        4032 :     sort_policies_by_name(*restrictive_policies);
     613             : 
     614             :     /*
     615             :      * Then add any permissive or restrictive policies defined by extensions.
     616             :      * These are simply appended to the lists of internal policies, if they
     617             :      * apply to the specified role.
     618             :      */
     619        4032 :     if (row_security_policy_hook_restrictive)
     620             :     {
     621             :         List       *hook_policies =
     622          60 :             (*row_security_policy_hook_restrictive) (cmd, relation);
     623             : 
     624             :         /*
     625             :          * As with built-in restrictive policies, we sort any hook-provided
     626             :          * restrictive policies by name also.  Note that we also intentionally
     627             :          * always check all built-in restrictive policies, in name order,
     628             :          * before checking restrictive policies added by hooks, in name order.
     629             :          */
     630          60 :         sort_policies_by_name(hook_policies);
     631             : 
     632         102 :         foreach(item, hook_policies)
     633             :         {
     634          42 :             RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     635             : 
     636          42 :             if (check_role_for_policy(policy->roles, user_id))
     637          42 :                 *restrictive_policies = lappend(*restrictive_policies, policy);
     638             :         }
     639             :     }
     640             : 
     641        4032 :     if (row_security_policy_hook_permissive)
     642             :     {
     643             :         List       *hook_policies =
     644          60 :             (*row_security_policy_hook_permissive) (cmd, relation);
     645             : 
     646         100 :         foreach(item, hook_policies)
     647             :         {
     648          40 :             RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     649             : 
     650          40 :             if (check_role_for_policy(policy->roles, user_id))
     651          40 :                 *permissive_policies = lappend(*permissive_policies, policy);
     652             :         }
     653             :     }
     654        4032 : }
     655             : 
     656             : /*
     657             :  * sort_policies_by_name
     658             :  *
     659             :  * This is only used for restrictive policies, ensuring that any
     660             :  * WithCheckOptions they generate are applied in a well-defined order.
     661             :  * This is not necessary for permissive policies, since they are all combined
     662             :  * together using OR into a single WithCheckOption check.
     663             :  */
     664             : static void
     665        4092 : sort_policies_by_name(List *policies)
     666             : {
     667        4092 :     list_sort(policies, row_security_policy_cmp);
     668        4092 : }
     669             : 
     670             : /*
     671             :  * list_sort comparator to sort RowSecurityPolicy entries by name
     672             :  */
     673             : static int
     674          48 : row_security_policy_cmp(const ListCell *a, const ListCell *b)
     675             : {
     676          48 :     const RowSecurityPolicy *pa = (const RowSecurityPolicy *) lfirst(a);
     677          48 :     const RowSecurityPolicy *pb = (const RowSecurityPolicy *) lfirst(b);
     678             : 
     679             :     /* Guard against NULL policy names from extensions */
     680          48 :     if (pa->policy_name == NULL)
     681           0 :         return pb->policy_name == NULL ? 0 : 1;
     682          48 :     if (pb->policy_name == NULL)
     683           0 :         return -1;
     684             : 
     685          48 :     return strcmp(pa->policy_name, pb->policy_name);
     686             : }
     687             : 
     688             : /*
     689             :  * add_security_quals
     690             :  *
     691             :  * Add security quals to enforce the specified RLS policies, restricting
     692             :  * access to existing data in a table.  If there are no policies controlling
     693             :  * access to the table, then all access is prohibited --- i.e., an implicit
     694             :  * default-deny policy is used.
     695             :  *
     696             :  * New security quals are added to securityQuals, and hasSubLinks is set to
     697             :  * true if any of the quals added contain sublink subqueries.
     698             :  */
     699             : static void
     700        2640 : add_security_quals(int rt_index,
     701             :                    List *permissive_policies,
     702             :                    List *restrictive_policies,
     703             :                    List **securityQuals,
     704             :                    bool *hasSubLinks)
     705             : {
     706             :     ListCell   *item;
     707        2640 :     List       *permissive_quals = NIL;
     708             :     Expr       *rowsec_expr;
     709             : 
     710             :     /*
     711             :      * First collect up the permissive quals.  If we do not find any
     712             :      * permissive policies then no rows are visible (this is handled below).
     713             :      */
     714        5384 :     foreach(item, permissive_policies)
     715             :     {
     716        2744 :         RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     717             : 
     718        2744 :         if (policy->qual != NULL)
     719             :         {
     720        2744 :             permissive_quals = lappend(permissive_quals,
     721        2744 :                                        copyObject(policy->qual));
     722        2744 :             *hasSubLinks |= policy->hassublinks;
     723             :         }
     724             :     }
     725             : 
     726             :     /*
     727             :      * We must have permissive quals, always, or no rows are visible.
     728             :      *
     729             :      * If we do not, then we simply return a single 'false' qual which results
     730             :      * in no rows being visible.
     731             :      */
     732        2640 :     if (permissive_quals != NIL)
     733             :     {
     734             :         /*
     735             :          * We now know that permissive policies exist, so we can now add
     736             :          * security quals based on the USING clauses from the restrictive
     737             :          * policies.  Since these need to be combined together using AND, we
     738             :          * can just add them one at a time.
     739             :          */
     740        2824 :         foreach(item, restrictive_policies)
     741             :         {
     742         238 :             RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     743             :             Expr       *qual;
     744             : 
     745         238 :             if (policy->qual != NULL)
     746             :             {
     747         238 :                 qual = copyObject(policy->qual);
     748         238 :                 ChangeVarNodes((Node *) qual, 1, rt_index, 0);
     749             : 
     750         238 :                 *securityQuals = list_append_unique(*securityQuals, qual);
     751         238 :                 *hasSubLinks |= policy->hassublinks;
     752             :             }
     753             :         }
     754             : 
     755             :         /*
     756             :          * Then add a single security qual combining together the USING
     757             :          * clauses from all the permissive policies using OR.
     758             :          */
     759        2586 :         if (list_length(permissive_quals) == 1)
     760        2476 :             rowsec_expr = (Expr *) linitial(permissive_quals);
     761             :         else
     762         110 :             rowsec_expr = makeBoolExpr(OR_EXPR, permissive_quals, -1);
     763             : 
     764        2586 :         ChangeVarNodes((Node *) rowsec_expr, 1, rt_index, 0);
     765        2586 :         *securityQuals = list_append_unique(*securityQuals, rowsec_expr);
     766             :     }
     767             :     else
     768             : 
     769             :         /*
     770             :          * A permissive policy must exist for rows to be visible at all.
     771             :          * Therefore, if there were no permissive policies found, return a
     772             :          * single always-false clause.
     773             :          */
     774          54 :         *securityQuals = lappend(*securityQuals,
     775          54 :                                  makeConst(BOOLOID, -1, InvalidOid,
     776             :                                            sizeof(bool), BoolGetDatum(false),
     777             :                                            false, true));
     778        2640 : }
     779             : 
     780             : /*
     781             :  * add_with_check_options
     782             :  *
     783             :  * Add WithCheckOptions of the specified kind to check that new records
     784             :  * added by an INSERT or UPDATE are consistent with the specified RLS
     785             :  * policies.  Normally new data must satisfy the WITH CHECK clauses from the
     786             :  * policies.  If a policy has no explicit WITH CHECK clause, its USING clause
     787             :  * is used instead.  In the special case of an UPDATE arising from an
     788             :  * INSERT ... ON CONFLICT DO UPDATE, existing records are first checked using
     789             :  * a WCO_RLS_CONFLICT_CHECK WithCheckOption, which always uses the USING
     790             :  * clauses from RLS policies.
     791             :  *
     792             :  * New WCOs are added to withCheckOptions, and hasSubLinks is set to true if
     793             :  * any of the check clauses added contain sublink subqueries.
     794             :  */
     795             : static void
     796        1836 : add_with_check_options(Relation rel,
     797             :                        int rt_index,
     798             :                        WCOKind kind,
     799             :                        List *permissive_policies,
     800             :                        List *restrictive_policies,
     801             :                        List **withCheckOptions,
     802             :                        bool *hasSubLinks,
     803             :                        bool force_using)
     804             : {
     805             :     ListCell   *item;
     806        1836 :     List       *permissive_quals = NIL;
     807             : 
     808             : #define QUAL_FOR_WCO(policy) \
     809             :     ( !force_using && \
     810             :       (policy)->with_check_qual != NULL ? \
     811             :       (policy)->with_check_qual : (policy)->qual )
     812             : 
     813             :     /*
     814             :      * First collect up the permissive policy clauses, similar to
     815             :      * add_security_quals.
     816             :      */
     817        3662 :     foreach(item, permissive_policies)
     818             :     {
     819        1826 :         RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     820        1826 :         Expr       *qual = QUAL_FOR_WCO(policy);
     821             : 
     822        1826 :         if (qual != NULL)
     823             :         {
     824        1826 :             permissive_quals = lappend(permissive_quals, copyObject(qual));
     825        1826 :             *hasSubLinks |= policy->hassublinks;
     826             :         }
     827             :     }
     828             : 
     829             :     /*
     830             :      * There must be at least one permissive qual found or no rows are allowed
     831             :      * to be added.  This is the same as in add_security_quals.
     832             :      *
     833             :      * If there are no permissive_quals then we fall through and return a
     834             :      * single 'false' WCO, preventing all new rows.
     835             :      */
     836        1836 :     if (permissive_quals != NIL)
     837             :     {
     838             :         /*
     839             :          * Add a single WithCheckOption for all the permissive policy clauses,
     840             :          * combining them together using OR.  This check has no policy name,
     841             :          * since if the check fails it means that no policy granted permission
     842             :          * to perform the update, rather than any particular policy being
     843             :          * violated.
     844             :          */
     845             :         WithCheckOption *wco;
     846             : 
     847        1782 :         wco = makeNode(WithCheckOption);
     848        1782 :         wco->kind = kind;
     849        1782 :         wco->relname = pstrdup(RelationGetRelationName(rel));
     850        1782 :         wco->polname = NULL;
     851        1782 :         wco->cascaded = false;
     852             : 
     853        1782 :         if (list_length(permissive_quals) == 1)
     854        1738 :             wco->qual = (Node *) linitial(permissive_quals);
     855             :         else
     856          44 :             wco->qual = (Node *) makeBoolExpr(OR_EXPR, permissive_quals, -1);
     857             : 
     858        1782 :         ChangeVarNodes(wco->qual, 1, rt_index, 0);
     859             : 
     860        1782 :         *withCheckOptions = list_append_unique(*withCheckOptions, wco);
     861             : 
     862             :         /*
     863             :          * Now add WithCheckOptions for each of the restrictive policy clauses
     864             :          * (which will be combined together using AND).  We use a separate
     865             :          * WithCheckOption for each restrictive policy to allow the policy
     866             :          * name to be included in error reports if the policy is violated.
     867             :          */
     868        1892 :         foreach(item, restrictive_policies)
     869             :         {
     870         110 :             RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
     871         110 :             Expr       *qual = QUAL_FOR_WCO(policy);
     872             : 
     873         110 :             if (qual != NULL)
     874             :             {
     875         110 :                 qual = copyObject(qual);
     876         110 :                 ChangeVarNodes((Node *) qual, 1, rt_index, 0);
     877             : 
     878         110 :                 wco = makeNode(WithCheckOption);
     879         110 :                 wco->kind = kind;
     880         110 :                 wco->relname = pstrdup(RelationGetRelationName(rel));
     881         110 :                 wco->polname = pstrdup(policy->policy_name);
     882         110 :                 wco->qual = (Node *) qual;
     883         110 :                 wco->cascaded = false;
     884             : 
     885         110 :                 *withCheckOptions = list_append_unique(*withCheckOptions, wco);
     886         110 :                 *hasSubLinks |= policy->hassublinks;
     887             :             }
     888             :         }
     889             :     }
     890             :     else
     891             :     {
     892             :         /*
     893             :          * If there were no policy clauses to check new data, add a single
     894             :          * always-false WCO (a default-deny policy).
     895             :          */
     896             :         WithCheckOption *wco;
     897             : 
     898          54 :         wco = makeNode(WithCheckOption);
     899          54 :         wco->kind = kind;
     900          54 :         wco->relname = pstrdup(RelationGetRelationName(rel));
     901          54 :         wco->polname = NULL;
     902          54 :         wco->qual = (Node *) makeConst(BOOLOID, -1, InvalidOid,
     903             :                                        sizeof(bool), BoolGetDatum(false),
     904             :                                        false, true);
     905          54 :         wco->cascaded = false;
     906             : 
     907          54 :         *withCheckOptions = lappend(*withCheckOptions, wco);
     908             :     }
     909        1836 : }
     910             : 
     911             : /*
     912             :  * check_role_for_policy -
     913             :  *   determines if the policy should be applied for the current role
     914             :  */
     915             : static bool
     916        6016 : check_role_for_policy(ArrayType *policy_roles, Oid user_id)
     917             : {
     918             :     int         i;
     919        6016 :     Oid        *roles = (Oid *) ARR_DATA_PTR(policy_roles);
     920             : 
     921             :     /* Quick fall-thru for policies applied to all roles */
     922        6016 :     if (roles[0] == ACL_ID_PUBLIC)
     923        3868 :         return true;
     924             : 
     925        3822 :     for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
     926             :     {
     927        2148 :         if (has_privs_of_role(user_id, roles[i]))
     928         474 :             return true;
     929             :     }
     930             : 
     931        1674 :     return false;
     932             : }

Generated by: LCOV version 1.14