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