LCOV - code coverage report
Current view: top level - src/test/modules/test_oat_hooks - test_oat_hooks.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 123 155 79.4 %
Date: 2024-05-19 07:11:14 Functions: 11 12 91.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*--------------------------------------------------------------------------
       2             :  *
       3             :  * test_oat_hooks.c
       4             :  *      Code for testing mandatory access control (MAC) using object access hooks.
       5             :  *
       6             :  * Copyright (c) 2015-2024, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *      src/test/modules/test_oat_hooks/test_oat_hooks.c
      10             :  *
      11             :  * -------------------------------------------------------------------------
      12             :  */
      13             : 
      14             : #include "postgres.h"
      15             : 
      16             : #include "access/parallel.h"
      17             : #include "catalog/dependency.h"
      18             : #include "catalog/objectaccess.h"
      19             : #include "catalog/pg_proc.h"
      20             : #include "executor/executor.h"
      21             : #include "fmgr.h"
      22             : #include "miscadmin.h"
      23             : #include "tcop/utility.h"
      24             : 
      25           4 : PG_MODULE_MAGIC;
      26             : 
      27             : /*
      28             :  * GUCs controlling which operations to deny
      29             :  */
      30             : static bool REGRESS_deny_set_variable = false;
      31             : static bool REGRESS_deny_alter_system = false;
      32             : static bool REGRESS_deny_object_access = false;
      33             : static bool REGRESS_deny_exec_perms = false;
      34             : static bool REGRESS_deny_utility_commands = false;
      35             : static bool REGRESS_audit = false;
      36             : 
      37             : /*
      38             :  * GUCs for testing privileges on USERSET and SUSET variables,
      39             :  * with and without privileges granted prior to module load.
      40             :  */
      41             : static bool REGRESS_userset_variable1 = false;
      42             : static bool REGRESS_userset_variable2 = false;
      43             : static bool REGRESS_suset_variable1 = false;
      44             : static bool REGRESS_suset_variable2 = false;
      45             : 
      46             : /* Saved hook values */
      47             : static object_access_hook_type next_object_access_hook = NULL;
      48             : static object_access_hook_type_str next_object_access_hook_str = NULL;
      49             : static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
      50             : static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
      51             : 
      52             : /* Test Object Access Type Hook hooks */
      53             : static void REGRESS_object_access_hook_str(ObjectAccessType access,
      54             :                                            Oid classId, const char *objName,
      55             :                                            int subId, void *arg);
      56             : static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
      57             :                                        Oid objectId, int subId, void *arg);
      58             : static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
      59             : static void REGRESS_utility_command(PlannedStmt *pstmt,
      60             :                                     const char *queryString, bool readOnlyTree,
      61             :                                     ProcessUtilityContext context,
      62             :                                     ParamListInfo params,
      63             :                                     QueryEnvironment *queryEnv,
      64             :                                     DestReceiver *dest, QueryCompletion *qc);
      65             : 
      66             : /* Helper functions */
      67             : static char *accesstype_to_string(ObjectAccessType access, int subId);
      68             : static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
      69             : 
      70             : 
      71             : /*
      72             :  * Module load callback
      73             :  */
      74             : void
      75           4 : _PG_init(void)
      76             : {
      77             :     /*
      78             :      * test_oat_hooks.deny_set_variable = (on|off)
      79             :      */
      80           4 :     DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
      81             :                              "Deny non-superuser set permissions",
      82             :                              NULL,
      83             :                              &REGRESS_deny_set_variable,
      84             :                              false,
      85             :                              PGC_SUSET,
      86             :                              GUC_NOT_IN_SAMPLE,
      87             :                              NULL,
      88             :                              NULL,
      89             :                              NULL);
      90             : 
      91             :     /*
      92             :      * test_oat_hooks.deny_alter_system = (on|off)
      93             :      */
      94           4 :     DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
      95             :                              "Deny non-superuser alter system set permissions",
      96             :                              NULL,
      97             :                              &REGRESS_deny_alter_system,
      98             :                              false,
      99             :                              PGC_SUSET,
     100             :                              GUC_NOT_IN_SAMPLE,
     101             :                              NULL,
     102             :                              NULL,
     103             :                              NULL);
     104             : 
     105             :     /*
     106             :      * test_oat_hooks.deny_object_access = (on|off)
     107             :      */
     108           4 :     DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
     109             :                              "Deny non-superuser object access permissions",
     110             :                              NULL,
     111             :                              &REGRESS_deny_object_access,
     112             :                              false,
     113             :                              PGC_SUSET,
     114             :                              GUC_NOT_IN_SAMPLE,
     115             :                              NULL,
     116             :                              NULL,
     117             :                              NULL);
     118             : 
     119             :     /*
     120             :      * test_oat_hooks.deny_exec_perms = (on|off)
     121             :      */
     122           4 :     DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
     123             :                              "Deny non-superuser exec permissions",
     124             :                              NULL,
     125             :                              &REGRESS_deny_exec_perms,
     126             :                              false,
     127             :                              PGC_SUSET,
     128             :                              GUC_NOT_IN_SAMPLE,
     129             :                              NULL,
     130             :                              NULL,
     131             :                              NULL);
     132             : 
     133             :     /*
     134             :      * test_oat_hooks.deny_utility_commands = (on|off)
     135             :      */
     136           4 :     DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
     137             :                              "Deny non-superuser utility commands",
     138             :                              NULL,
     139             :                              &REGRESS_deny_utility_commands,
     140             :                              false,
     141             :                              PGC_SUSET,
     142             :                              GUC_NOT_IN_SAMPLE,
     143             :                              NULL,
     144             :                              NULL,
     145             :                              NULL);
     146             : 
     147             :     /*
     148             :      * test_oat_hooks.audit = (on|off)
     149             :      */
     150           4 :     DefineCustomBoolVariable("test_oat_hooks.audit",
     151             :                              "Turn on/off debug audit messages",
     152             :                              NULL,
     153             :                              &REGRESS_audit,
     154             :                              false,
     155             :                              PGC_SUSET,
     156             :                              GUC_NOT_IN_SAMPLE,
     157             :                              NULL,
     158             :                              NULL,
     159             :                              NULL);
     160             : 
     161             :     /*
     162             :      * test_oat_hooks.user_var{1,2} = (on|off)
     163             :      */
     164           4 :     DefineCustomBoolVariable("test_oat_hooks.user_var1",
     165             :                              "Dummy parameter settable by public",
     166             :                              NULL,
     167             :                              &REGRESS_userset_variable1,
     168             :                              false,
     169             :                              PGC_USERSET,
     170             :                              GUC_NOT_IN_SAMPLE,
     171             :                              NULL,
     172             :                              NULL,
     173             :                              NULL);
     174             : 
     175           4 :     DefineCustomBoolVariable("test_oat_hooks.user_var2",
     176             :                              "Dummy parameter settable by public",
     177             :                              NULL,
     178             :                              &REGRESS_userset_variable2,
     179             :                              false,
     180             :                              PGC_USERSET,
     181             :                              GUC_NOT_IN_SAMPLE,
     182             :                              NULL,
     183             :                              NULL,
     184             :                              NULL);
     185             : 
     186             :     /*
     187             :      * test_oat_hooks.super_var{1,2} = (on|off)
     188             :      */
     189           4 :     DefineCustomBoolVariable("test_oat_hooks.super_var1",
     190             :                              "Dummy parameter settable by superuser",
     191             :                              NULL,
     192             :                              &REGRESS_suset_variable1,
     193             :                              false,
     194             :                              PGC_SUSET,
     195             :                              GUC_NOT_IN_SAMPLE,
     196             :                              NULL,
     197             :                              NULL,
     198             :                              NULL);
     199             : 
     200           4 :     DefineCustomBoolVariable("test_oat_hooks.super_var2",
     201             :                              "Dummy parameter settable by superuser",
     202             :                              NULL,
     203             :                              &REGRESS_suset_variable2,
     204             :                              false,
     205             :                              PGC_SUSET,
     206             :                              GUC_NOT_IN_SAMPLE,
     207             :                              NULL,
     208             :                              NULL,
     209             :                              NULL);
     210             : 
     211           4 :     MarkGUCPrefixReserved("test_oat_hooks");
     212             : 
     213             :     /* Object access hook */
     214           4 :     next_object_access_hook = object_access_hook;
     215           4 :     object_access_hook = REGRESS_object_access_hook;
     216             : 
     217             :     /* Object access hook str */
     218           4 :     next_object_access_hook_str = object_access_hook_str;
     219           4 :     object_access_hook_str = REGRESS_object_access_hook_str;
     220             : 
     221             :     /* DML permission check */
     222           4 :     next_exec_check_perms_hook = ExecutorCheckPerms_hook;
     223           4 :     ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
     224             : 
     225             :     /* ProcessUtility hook */
     226           4 :     next_ProcessUtility_hook = ProcessUtility_hook;
     227           4 :     ProcessUtility_hook = REGRESS_utility_command;
     228           4 : }
     229             : 
     230             : static void
     231         616 : emit_audit_message(const char *type, const char *hook, char *action, char *objName)
     232             : {
     233             :     /*
     234             :      * Ensure that audit messages are not duplicated by only emitting them
     235             :      * from a leader process, not a worker process. This makes the test
     236             :      * results deterministic even if run with debug_parallel_query = regress.
     237             :      */
     238         616 :     if (REGRESS_audit && !IsParallelWorker())
     239             :     {
     240         580 :         const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
     241             : 
     242         580 :         if (objName)
     243         330 :             ereport(NOTICE,
     244             :                     (errcode(ERRCODE_INTERNAL_ERROR),
     245             :                      errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
     246             :         else
     247         250 :             ereport(NOTICE,
     248             :                     (errcode(ERRCODE_INTERNAL_ERROR),
     249             :                      errmsg("in %s: %s %s %s", hook, who, type, action)));
     250             :     }
     251             : 
     252         616 :     if (action)
     253         616 :         pfree(action);
     254         616 :     if (objName)
     255         346 :         pfree(objName);
     256         616 : }
     257             : 
     258             : static void
     259         320 : audit_attempt(const char *hook, char *action, char *objName)
     260             : {
     261         320 :     emit_audit_message("attempting", hook, action, objName);
     262         320 : }
     263             : 
     264             : static void
     265         296 : audit_success(const char *hook, char *action, char *objName)
     266             : {
     267         296 :     emit_audit_message("finished", hook, action, objName);
     268         296 : }
     269             : 
     270             : static void
     271           0 : audit_failure(const char *hook, char *action, char *objName)
     272             : {
     273           0 :     emit_audit_message("denied", hook, action, objName);
     274           0 : }
     275             : 
     276             : static void
     277          48 : REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
     278             : {
     279          48 :     audit_attempt("object_access_hook_str",
     280             :                   accesstype_to_string(access, subId),
     281             :                   pstrdup(objName));
     282             : 
     283          48 :     if (next_object_access_hook_str)
     284             :     {
     285           0 :         (*next_object_access_hook_str) (access, classId, objName, subId, arg);
     286             :     }
     287             : 
     288          48 :     switch (access)
     289             :     {
     290          48 :         case OAT_POST_ALTER:
     291          48 :             if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
     292             :             {
     293           0 :                 if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
     294           0 :                     ereport(ERROR,
     295             :                             (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     296             :                              errmsg("permission denied: all privileges %s", objName)));
     297             :             }
     298          48 :             else if (subId & ACL_SET)
     299             :             {
     300          40 :                 if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
     301           2 :                     ereport(ERROR,
     302             :                             (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     303             :                              errmsg("permission denied: set %s", objName)));
     304             :             }
     305           8 :             else if (subId & ACL_ALTER_SYSTEM)
     306             :             {
     307           8 :                 if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
     308           0 :                     ereport(ERROR,
     309             :                             (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     310             :                              errmsg("permission denied: alter system set %s", objName)));
     311             :             }
     312             :             else
     313           0 :                 elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
     314          46 :             break;
     315           0 :         default:
     316           0 :             break;
     317             :     }
     318             : 
     319          46 :     audit_success("object_access_hook_str",
     320             :                   accesstype_to_string(access, subId),
     321             :                   pstrdup(objName));
     322          46 : }
     323             : 
     324             : static void
     325         126 : REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
     326             : {
     327         126 :     audit_attempt("object access",
     328             :                   accesstype_to_string(access, 0),
     329             :                   accesstype_arg_to_string(access, arg));
     330             : 
     331         126 :     if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
     332           0 :         ereport(ERROR,
     333             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     334             :                  errmsg("permission denied: %s [%s]",
     335             :                         accesstype_to_string(access, 0),
     336             :                         accesstype_arg_to_string(access, arg))));
     337             : 
     338             :     /* Forward to next hook in the chain */
     339         126 :     if (next_object_access_hook)
     340           0 :         (*next_object_access_hook) (access, classId, objectId, subId, arg);
     341             : 
     342         126 :     audit_success("object access",
     343             :                   accesstype_to_string(access, 0),
     344             :                   accesstype_arg_to_string(access, arg));
     345         126 : }
     346             : 
     347             : static bool
     348          12 : REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
     349             : {
     350          12 :     bool        am_super = superuser_arg(GetUserId());
     351          12 :     bool        allow = true;
     352             : 
     353          12 :     audit_attempt("executor check perms", pstrdup("execute"), NULL);
     354             : 
     355             :     /* Perform our check */
     356          12 :     allow = !REGRESS_deny_exec_perms || am_super;
     357          12 :     if (do_abort && !allow)
     358           0 :         ereport(ERROR,
     359             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     360             :                  errmsg("permission denied: %s", "execute")));
     361             : 
     362             :     /* Forward to next hook in the chain */
     363          12 :     if (next_exec_check_perms_hook &&
     364           0 :         !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
     365           0 :         allow = false;
     366             : 
     367          12 :     if (allow)
     368          12 :         audit_success("executor check perms",
     369             :                       pstrdup("execute"),
     370             :                       NULL);
     371             :     else
     372           0 :         audit_failure("executor check perms",
     373             :                       pstrdup("execute"),
     374             :                       NULL);
     375             : 
     376          12 :     return allow;
     377             : }
     378             : 
     379             : static void
     380         134 : REGRESS_utility_command(PlannedStmt *pstmt,
     381             :                         const char *queryString,
     382             :                         bool readOnlyTree,
     383             :                         ProcessUtilityContext context,
     384             :                         ParamListInfo params,
     385             :                         QueryEnvironment *queryEnv,
     386             :                         DestReceiver *dest,
     387             :                         QueryCompletion *qc)
     388             : {
     389         134 :     Node       *parsetree = pstmt->utilityStmt;
     390         134 :     const char *action = GetCommandTagName(CreateCommandTag(parsetree));
     391             : 
     392         134 :     audit_attempt("process utility",
     393             :                   pstrdup(action),
     394             :                   NULL);
     395             : 
     396             :     /* Check permissions */
     397         134 :     if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
     398           0 :         ereport(ERROR,
     399             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     400             :                  errmsg("permission denied: %s", action)));
     401             : 
     402             :     /* Forward to next hook in the chain */
     403         134 :     if (next_ProcessUtility_hook)
     404           0 :         (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
     405             :                                      context, params, queryEnv,
     406             :                                      dest, qc);
     407             :     else
     408         134 :         standard_ProcessUtility(pstmt, queryString, readOnlyTree,
     409             :                                 context, params, queryEnv,
     410             :                                 dest, qc);
     411             : 
     412             :     /* We're done */
     413         112 :     audit_success("process utility",
     414             :                   pstrdup(action),
     415             :                   NULL);
     416         112 : }
     417             : 
     418             : static char *
     419         346 : accesstype_to_string(ObjectAccessType access, int subId)
     420             : {
     421             :     const char *type;
     422             : 
     423         346 :     switch (access)
     424             :     {
     425          68 :         case OAT_POST_CREATE:
     426          68 :             type = "create";
     427          68 :             break;
     428          48 :         case OAT_DROP:
     429          48 :             type = "drop";
     430          48 :             break;
     431         142 :         case OAT_POST_ALTER:
     432         142 :             type = "alter";
     433         142 :             break;
     434          88 :         case OAT_NAMESPACE_SEARCH:
     435          88 :             type = "namespace search";
     436          88 :             break;
     437           0 :         case OAT_FUNCTION_EXECUTE:
     438           0 :             type = "execute";
     439           0 :             break;
     440           0 :         case OAT_TRUNCATE:
     441           0 :             type = "truncate";
     442           0 :             break;
     443           0 :         default:
     444           0 :             type = "UNRECOGNIZED ObjectAccessType";
     445             :     }
     446             : 
     447         346 :     if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
     448           0 :         return psprintf("%s (subId=0x%x, all privileges)", type, subId);
     449         346 :     if (subId & ACL_SET)
     450          78 :         return psprintf("%s (subId=0x%x, set)", type, subId);
     451         268 :     if (subId & ACL_ALTER_SYSTEM)
     452          16 :         return psprintf("%s (subId=0x%x, alter system)", type, subId);
     453             : 
     454         252 :     return psprintf("%s (subId=0x%x)", type, subId);
     455             : }
     456             : 
     457             : static char *
     458         252 : accesstype_arg_to_string(ObjectAccessType access, void *arg)
     459             : {
     460         252 :     if (arg == NULL)
     461           0 :         return pstrdup("extra info null");
     462             : 
     463         252 :     switch (access)
     464             :     {
     465          68 :         case OAT_POST_CREATE:
     466             :             {
     467          68 :                 ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *) arg;
     468             : 
     469          68 :                 return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
     470             :             }
     471             :             break;
     472          48 :         case OAT_DROP:
     473             :             {
     474          48 :                 ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
     475             : 
     476         288 :                 return psprintf("%s%s%s%s%s%s",
     477          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
     478             :                                  ? "internal action," : ""),
     479          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENTLY)
     480             :                                  ? "concurrent drop," : ""),
     481          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
     482             :                                  ? "suppress notices," : ""),
     483          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_SKIP_ORIGINAL)
     484             :                                  ? "keep original object," : ""),
     485          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_SKIP_EXTENSIONS)
     486             :                                  ? "keep extensions," : ""),
     487          48 :                                 ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENT_LOCK)
     488             :                                  ? "normal concurrent drop," : ""));
     489             :             }
     490             :             break;
     491          48 :         case OAT_POST_ALTER:
     492             :             {
     493          48 :                 ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter *) arg;
     494             : 
     495          96 :                 return psprintf("%s %s auxiliary object",
     496          48 :                                 (pa_arg->is_internal ? "internal" : "explicit"),
     497          48 :                                 (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
     498             :             }
     499             :             break;
     500          88 :         case OAT_NAMESPACE_SEARCH:
     501             :             {
     502          88 :                 ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *) arg;
     503             : 
     504         176 :                 return psprintf("%s, %s",
     505          88 :                                 (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
     506          88 :                                 (ns_arg->result ? "allowed" : "denied"));
     507             :             }
     508             :             break;
     509           0 :         case OAT_TRUNCATE:
     510             :         case OAT_FUNCTION_EXECUTE:
     511             :             /* hook takes no arg. */
     512           0 :             return pstrdup("unexpected extra info pointer received");
     513           0 :         default:
     514           0 :             return pstrdup("cannot parse extra info for unrecognized access type");
     515             :     }
     516             : 
     517             :     return pstrdup("unknown");
     518             : }

Generated by: LCOV version 1.14