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

Generated by: LCOV version 1.14