LCOV - code coverage report
Current view: top level - src/test/modules/test_oat_hooks - test_oat_hooks.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 170 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 12 0
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-2026, 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            0 : 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            0 : _PG_init(void)
      75              : {
      76              :         /*
      77              :          * test_oat_hooks.deny_set_variable = (on|off)
      78              :          */
      79            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         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            0 :         MarkGUCPrefixReserved("test_oat_hooks");
     211              : 
     212              :         /* Object access hook */
     213            0 :         next_object_access_hook = object_access_hook;
     214            0 :         object_access_hook = REGRESS_object_access_hook;
     215              : 
     216              :         /* Object access hook str */
     217            0 :         next_object_access_hook_str = object_access_hook_str;
     218            0 :         object_access_hook_str = REGRESS_object_access_hook_str;
     219              : 
     220              :         /* DML permission check */
     221            0 :         next_exec_check_perms_hook = ExecutorCheckPerms_hook;
     222            0 :         ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
     223              : 
     224              :         /* ProcessUtility hook */
     225            0 :         next_ProcessUtility_hook = ProcessUtility_hook;
     226            0 :         ProcessUtility_hook = REGRESS_utility_command;
     227            0 : }
     228              : 
     229              : static void
     230            0 : 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            0 :         if (REGRESS_audit && !IsParallelWorker())
     238              :         {
     239            0 :                 const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
     240              : 
     241            0 :                 if (objName)
     242            0 :                         ereport(NOTICE,
     243              :                                         (errcode(ERRCODE_INTERNAL_ERROR),
     244              :                                          errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
     245              :                 else
     246            0 :                         ereport(NOTICE,
     247              :                                         (errcode(ERRCODE_INTERNAL_ERROR),
     248              :                                          errmsg("in %s: %s %s %s", hook, who, type, action)));
     249            0 :         }
     250              : 
     251            0 :         if (action)
     252            0 :                 pfree(action);
     253            0 :         if (objName)
     254            0 :                 pfree(objName);
     255            0 : }
     256              : 
     257              : static void
     258            0 : audit_attempt(const char *hook, char *action, char *objName)
     259              : {
     260            0 :         emit_audit_message("attempting", hook, action, objName);
     261            0 : }
     262              : 
     263              : static void
     264            0 : audit_success(const char *hook, char *action, char *objName)
     265              : {
     266            0 :         emit_audit_message("finished", hook, action, objName);
     267            0 : }
     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            0 : REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
     277              : {
     278            0 :         audit_attempt("object_access_hook_str",
     279            0 :                                   accesstype_to_string(access, subId),
     280            0 :                                   pstrdup(objName));
     281              : 
     282            0 :         if (next_object_access_hook_str)
     283              :         {
     284            0 :                 (*next_object_access_hook_str) (access, classId, objName, subId, arg);
     285            0 :         }
     286              : 
     287            0 :         switch (access)
     288              :         {
     289              :                 case OAT_POST_ALTER:
     290            0 :                         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            0 :                         }
     297            0 :                         else if (subId & ACL_SET)
     298              :                         {
     299            0 :                                 if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
     300            0 :                                         ereport(ERROR,
     301              :                                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     302              :                                                          errmsg("permission denied: set %s", objName)));
     303            0 :                         }
     304            0 :                         else if (subId & ACL_ALTER_SYSTEM)
     305              :                         {
     306            0 :                                 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            0 :                         }
     311              :                         else
     312            0 :                                 elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
     313            0 :                         break;
     314              :                 default:
     315            0 :                         break;
     316              :         }
     317              : 
     318            0 :         audit_success("object_access_hook_str",
     319            0 :                                   accesstype_to_string(access, subId),
     320            0 :                                   pstrdup(objName));
     321            0 : }
     322              : 
     323              : static void
     324            0 : REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
     325              : {
     326            0 :         audit_attempt("object access",
     327            0 :                                   accesstype_to_string(access, 0),
     328            0 :                                   accesstype_arg_to_string(access, arg));
     329              : 
     330            0 :         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            0 :         if (next_object_access_hook)
     339            0 :                 (*next_object_access_hook) (access, classId, objectId, subId, arg);
     340              : 
     341            0 :         audit_success("object access",
     342            0 :                                   accesstype_to_string(access, 0),
     343            0 :                                   accesstype_arg_to_string(access, arg));
     344            0 : }
     345              : 
     346              : static bool
     347            0 : REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
     348              : {
     349            0 :         bool            am_super = superuser_arg(GetUserId());
     350            0 :         bool            allow = true;
     351              : 
     352            0 :         audit_attempt("executor check perms", pstrdup("execute"), NULL);
     353              : 
     354              :         /* Perform our check */
     355            0 :         allow = !REGRESS_deny_exec_perms || am_super;
     356            0 :         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            0 :         if (next_exec_check_perms_hook &&
     363            0 :                 !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
     364            0 :                 allow = false;
     365              : 
     366            0 :         if (allow)
     367            0 :                 audit_success("executor check perms",
     368            0 :                                           pstrdup("execute"),
     369              :                                           NULL);
     370              :         else
     371            0 :                 audit_failure("executor check perms",
     372            0 :                                           pstrdup("execute"),
     373              :                                           NULL);
     374              : 
     375            0 :         return allow;
     376            0 : }
     377              : 
     378              : static void
     379            0 : 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            0 :         Node       *parsetree = pstmt->utilityStmt;
     389            0 :         const char *action = GetCommandTagName(CreateCommandTag(parsetree));
     390              : 
     391            0 :         audit_attempt("process utility",
     392            0 :                                   pstrdup(action),
     393              :                                   NULL);
     394              : 
     395              :         /* Check permissions */
     396            0 :         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            0 :         if (next_ProcessUtility_hook)
     403            0 :                 (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
     404            0 :                                                                          context, params, queryEnv,
     405            0 :                                                                          dest, qc);
     406              :         else
     407            0 :                 standard_ProcessUtility(pstmt, queryString, readOnlyTree,
     408            0 :                                                                 context, params, queryEnv,
     409            0 :                                                                 dest, qc);
     410              : 
     411              :         /* We're done */
     412            0 :         audit_success("process utility",
     413            0 :                                   pstrdup(action),
     414              :                                   NULL);
     415            0 : }
     416              : 
     417              : static char *
     418            0 : accesstype_to_string(ObjectAccessType access, int subId)
     419              : {
     420            0 :         const char *type;
     421              : 
     422            0 :         switch (access)
     423              :         {
     424              :                 case OAT_POST_CREATE:
     425            0 :                         type = "create";
     426            0 :                         break;
     427              :                 case OAT_DROP:
     428            0 :                         type = "drop";
     429            0 :                         break;
     430              :                 case OAT_POST_ALTER:
     431            0 :                         type = "alter";
     432            0 :                         break;
     433              :                 case OAT_NAMESPACE_SEARCH:
     434            0 :                         type = "namespace search";
     435            0 :                         break;
     436              :                 case OAT_FUNCTION_EXECUTE:
     437            0 :                         type = "execute";
     438            0 :                         break;
     439              :                 case OAT_TRUNCATE:
     440            0 :                         type = "truncate";
     441            0 :                         break;
     442              :                 default:
     443            0 :                         type = "UNRECOGNIZED ObjectAccessType";
     444            0 :         }
     445              : 
     446            0 :         if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
     447            0 :                 return psprintf("%s (subId=0x%x, all privileges)", type, subId);
     448            0 :         if (subId & ACL_SET)
     449            0 :                 return psprintf("%s (subId=0x%x, set)", type, subId);
     450            0 :         if (subId & ACL_ALTER_SYSTEM)
     451            0 :                 return psprintf("%s (subId=0x%x, alter system)", type, subId);
     452              : 
     453            0 :         return psprintf("%s (subId=0x%x)", type, subId);
     454            0 : }
     455              : 
     456              : static char *
     457            0 : accesstype_arg_to_string(ObjectAccessType access, void *arg)
     458              : {
     459            0 :         if (arg == NULL)
     460            0 :                 return pstrdup("extra info null");
     461              : 
     462            0 :         switch (access)
     463              :         {
     464              :                 case OAT_POST_CREATE:
     465              :                         {
     466            0 :                                 ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *) arg;
     467              : 
     468            0 :                                 return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
     469            0 :                         }
     470              :                         break;
     471              :                 case OAT_DROP:
     472              :                         {
     473            0 :                                 ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
     474              : 
     475            0 :                                 return psprintf("%s%s%s%s%s%s",
     476            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
     477              :                                                                  ? "internal action," : ""),
     478            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENTLY)
     479              :                                                                  ? "concurrent drop," : ""),
     480            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
     481              :                                                                  ? "suppress notices," : ""),
     482            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_SKIP_ORIGINAL)
     483              :                                                                  ? "keep original object," : ""),
     484            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_SKIP_EXTENSIONS)
     485              :                                                                  ? "keep extensions," : ""),
     486            0 :                                                                 ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENT_LOCK)
     487              :                                                                  ? "normal concurrent drop," : ""));
     488            0 :                         }
     489              :                         break;
     490              :                 case OAT_POST_ALTER:
     491              :                         {
     492            0 :                                 ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter *) arg;
     493              : 
     494            0 :                                 return psprintf("%s %s auxiliary object",
     495            0 :                                                                 (pa_arg->is_internal ? "internal" : "explicit"),
     496            0 :                                                                 (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
     497            0 :                         }
     498              :                         break;
     499              :                 case OAT_NAMESPACE_SEARCH:
     500              :                         {
     501            0 :                                 ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *) arg;
     502              : 
     503            0 :                                 return psprintf("%s, %s",
     504            0 :                                                                 (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
     505            0 :                                                                 (ns_arg->result ? "allowed" : "denied"));
     506            0 :                         }
     507              :                         break;
     508              :                 case OAT_TRUNCATE:
     509              :                 case OAT_FUNCTION_EXECUTE:
     510              :                         /* hook takes no arg. */
     511            0 :                         return pstrdup("unexpected extra info pointer received");
     512              :                 default:
     513            0 :                         return pstrdup("cannot parse extra info for unrecognized access type");
     514              :         }
     515              : 
     516              :         return pstrdup("unknown");
     517            0 : }
        

Generated by: LCOV version 2.3.2-1