aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/extension.c10
-rw-r--r--src/backend/utils/misc/guc.c203
-rw-r--r--src/include/utils/guc.h5
-rw-r--r--src/include/utils/guc_tables.h8
-rw-r--r--src/pl/plperl/expected/plperl_init.out28
-rw-r--r--src/pl/plperl/sql/plperl_init.sql33
-rw-r--r--src/test/modules/unsafe_tests/expected/guc_privs.out36
-rw-r--r--src/test/modules/unsafe_tests/sql/guc_privs.sql16
8 files changed, 280 insertions, 59 deletions
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3db859c3ea3..6b6720c6905 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -912,6 +912,9 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
* We use the equivalent of a function SET option to allow the setting to
* persist for exactly the duration of the script execution. guc.c also
* takes care of undoing the setting on error.
+ *
+ * log_min_messages can't be set by ordinary users, so for that one we
+ * pretend to be superuser.
*/
save_nestlevel = NewGUCNestLevel();
@@ -920,9 +923,10 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
if (log_min_messages < WARNING)
- (void) set_config_option("log_min_messages", "warning",
- PGC_SUSET, PGC_S_SESSION,
- GUC_ACTION_SAVE, true, 0, false);
+ (void) set_config_option_ext("log_min_messages", "warning",
+ PGC_SUSET, PGC_S_SESSION,
+ BOOTSTRAP_SUPERUSERID,
+ GUC_ACTION_SAVE, true, 0, false);
/*
* Similarly disable check_function_bodies, to ensure that SQL functions
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0328029d430..28da02ff6c9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -5172,7 +5172,8 @@ static void reapply_stacked_values(struct config_generic *variable,
struct config_string *pHolder,
GucStack *stack,
const char *curvalue,
- GucContext curscontext, GucSource cursource);
+ GucContext curscontext, GucSource cursource,
+ Oid cursrole);
static void ShowGUCConfigOption(const char *name, DestReceiver *dest);
static void ShowAllGUCConfig(DestReceiver *dest);
static char *_ShowOption(struct config_generic *record, bool use_units);
@@ -5897,10 +5898,10 @@ InitializeWalConsistencyChecking(void)
check_wal_consistency_checking_deferred = false;
- set_config_option("wal_consistency_checking",
- wal_consistency_checking_string,
- PGC_POSTMASTER, guc->source,
- GUC_ACTION_SET, true, ERROR, false);
+ set_config_option_ext("wal_consistency_checking",
+ wal_consistency_checking_string,
+ guc->scontext, guc->source, guc->srole,
+ GUC_ACTION_SET, true, ERROR, false);
/* checking should not be deferred again */
Assert(!check_wal_consistency_checking_deferred);
@@ -5979,6 +5980,8 @@ InitializeOneGUCOption(struct config_generic *gconf)
gconf->reset_source = PGC_S_DEFAULT;
gconf->scontext = PGC_INTERNAL;
gconf->reset_scontext = PGC_INTERNAL;
+ gconf->srole = BOOTSTRAP_SUPERUSERID;
+ gconf->reset_srole = BOOTSTRAP_SUPERUSERID;
gconf->stack = NULL;
gconf->extra = NULL;
gconf->last_reported = NULL;
@@ -6356,6 +6359,7 @@ ResetAllOptions(void)
gconf->source = gconf->reset_source;
gconf->scontext = gconf->reset_scontext;
+ gconf->srole = gconf->reset_srole;
if (gconf->flags & GUC_REPORT)
{
@@ -6401,6 +6405,7 @@ push_old_value(struct config_generic *gconf, GucAction action)
{
/* SET followed by SET LOCAL, remember SET's value */
stack->masked_scontext = gconf->scontext;
+ stack->masked_srole = gconf->srole;
set_stack_value(gconf, &stack->masked);
stack->state = GUC_SET_LOCAL;
}
@@ -6439,6 +6444,7 @@ push_old_value(struct config_generic *gconf, GucAction action)
}
stack->source = gconf->source;
stack->scontext = gconf->scontext;
+ stack->srole = gconf->srole;
set_stack_value(gconf, &stack->prior);
gconf->stack = stack;
@@ -6583,6 +6589,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
{
/* LOCAL migrates down */
prev->masked_scontext = stack->scontext;
+ prev->masked_srole = stack->srole;
prev->masked = stack->prior;
prev->state = GUC_SET_LOCAL;
}
@@ -6598,6 +6605,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
discard_stack_value(gconf, &stack->prior);
/* copy down the masked state */
prev->masked_scontext = stack->masked_scontext;
+ prev->masked_srole = stack->masked_srole;
if (prev->state == GUC_SET_LOCAL)
discard_stack_value(gconf, &prev->masked);
prev->masked = stack->masked;
@@ -6614,18 +6622,21 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
config_var_value newvalue;
GucSource newsource;
GucContext newscontext;
+ Oid newsrole;
if (restoreMasked)
{
newvalue = stack->masked;
newsource = PGC_S_SESSION;
newscontext = stack->masked_scontext;
+ newsrole = stack->masked_srole;
}
else
{
newvalue = stack->prior;
newsource = stack->source;
newscontext = stack->scontext;
+ newsrole = stack->srole;
}
switch (gconf->vartype)
@@ -6740,6 +6751,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
/* And restore source information */
gconf->source = newsource;
gconf->scontext = newscontext;
+ gconf->srole = newsrole;
}
/* Finish popping the state stack */
@@ -7520,7 +7532,7 @@ parse_and_validate_value(struct config_generic *record,
/*
- * Sets option `name' to given value.
+ * set_config_option: sets option `name' to given value.
*
* The value should be a string, which will be parsed and converted to
* the appropriate data type. The context and source parameters indicate
@@ -7564,6 +7576,46 @@ set_config_option(const char *name, const char *value,
GucAction action, bool changeVal, int elevel,
bool is_reload)
{
+ Oid srole;
+
+ /*
+ * Non-interactive sources should be treated as having all privileges,
+ * except for PGC_S_CLIENT. Note in particular that this is true for
+ * pg_db_role_setting sources (PGC_S_GLOBAL etc): we assume a suitable
+ * privilege check was done when the pg_db_role_setting entry was made.
+ */
+ if (source >= PGC_S_INTERACTIVE || source == PGC_S_CLIENT)
+ srole = GetUserId();
+ else
+ srole = BOOTSTRAP_SUPERUSERID;
+
+ return set_config_option_ext(name, value,
+ context, source, srole,
+ action, changeVal, elevel,
+ is_reload);
+}
+
+/*
+ * set_config_option_ext: sets option `name' to given value.
+ *
+ * This API adds the ability to explicitly specify which role OID
+ * is considered to be setting the value. Most external callers can use
+ * set_config_option() and let it determine that based on the GucSource,
+ * but there are a few that are supplying a value that was determined
+ * in some special way and need to override the decision. Also, when
+ * restoring a previously-assigned value, it's important to supply the
+ * same role OID that set the value originally; so all guc.c callers
+ * that are doing that type of thing need to call this directly.
+ *
+ * Generally, srole should be GetUserId() when the source is a SQL operation,
+ * or BOOTSTRAP_SUPERUSERID if the source is a config file or similar.
+ */
+int
+set_config_option_ext(const char *name, const char *value,
+ GucContext context, GucSource source, Oid srole,
+ GucAction action, bool changeVal, int elevel,
+ bool is_reload)
+{
struct config_generic *record;
union config_var_val newval_union;
void *newextra = NULL;
@@ -7667,12 +7719,12 @@ set_config_option(const char *name, const char *value,
if (context == PGC_BACKEND)
{
/*
- * Check whether the current user has been granted privilege
- * to set this GUC.
+ * Check whether the requesting user has been granted
+ * privilege to set this GUC.
*/
AclResult aclresult;
- aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
+ aclresult = pg_parameter_aclcheck(name, srole, ACL_SET);
if (aclresult != ACLCHECK_OK)
{
/* No granted privilege */
@@ -7725,12 +7777,12 @@ set_config_option(const char *name, const char *value,
if (context == PGC_USERSET || context == PGC_BACKEND)
{
/*
- * Check whether the current user has been granted privilege
- * to set this GUC.
+ * Check whether the requesting user has been granted
+ * privilege to set this GUC.
*/
AclResult aclresult;
- aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
+ aclresult = pg_parameter_aclcheck(name, srole, ACL_SET);
if (aclresult != ACLCHECK_OK)
{
/* No granted privilege */
@@ -7847,6 +7899,7 @@ set_config_option(const char *name, const char *value,
newextra = conf->reset_extra;
source = conf->gen.reset_source;
context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
}
if (prohibitValueChange)
@@ -7881,6 +7934,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.source = source;
conf->gen.scontext = context;
+ conf->gen.srole = srole;
}
if (makeDefault)
{
@@ -7893,6 +7947,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.reset_source = source;
conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
}
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
@@ -7903,6 +7958,7 @@ set_config_option(const char *name, const char *value,
newextra);
stack->source = source;
stack->scontext = context;
+ stack->srole = srole;
}
}
}
@@ -7941,6 +7997,7 @@ set_config_option(const char *name, const char *value,
newextra = conf->reset_extra;
source = conf->gen.reset_source;
context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
}
if (prohibitValueChange)
@@ -7975,6 +8032,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.source = source;
conf->gen.scontext = context;
+ conf->gen.srole = srole;
}
if (makeDefault)
{
@@ -7987,6 +8045,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.reset_source = source;
conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
}
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
@@ -7997,6 +8056,7 @@ set_config_option(const char *name, const char *value,
newextra);
stack->source = source;
stack->scontext = context;
+ stack->srole = srole;
}
}
}
@@ -8035,6 +8095,7 @@ set_config_option(const char *name, const char *value,
newextra = conf->reset_extra;
source = conf->gen.reset_source;
context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
}
if (prohibitValueChange)
@@ -8069,6 +8130,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.source = source;
conf->gen.scontext = context;
+ conf->gen.srole = srole;
}
if (makeDefault)
{
@@ -8081,6 +8143,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.reset_source = source;
conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
}
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
@@ -8091,6 +8154,7 @@ set_config_option(const char *name, const char *value,
newextra);
stack->source = source;
stack->scontext = context;
+ stack->srole = srole;
}
}
}
@@ -8145,6 +8209,7 @@ set_config_option(const char *name, const char *value,
newextra = conf->reset_extra;
source = conf->gen.reset_source;
context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
}
if (prohibitValueChange)
@@ -8189,6 +8254,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.source = source;
conf->gen.scontext = context;
+ conf->gen.srole = srole;
}
if (makeDefault)
@@ -8202,6 +8268,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.reset_source = source;
conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
}
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
@@ -8213,6 +8280,7 @@ set_config_option(const char *name, const char *value,
newextra);
stack->source = source;
stack->scontext = context;
+ stack->srole = srole;
}
}
}
@@ -8254,6 +8322,7 @@ set_config_option(const char *name, const char *value,
newextra = conf->reset_extra;
source = conf->gen.reset_source;
context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
}
if (prohibitValueChange)
@@ -8288,6 +8357,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.source = source;
conf->gen.scontext = context;
+ conf->gen.srole = srole;
}
if (makeDefault)
{
@@ -8300,6 +8370,7 @@ set_config_option(const char *name, const char *value,
newextra);
conf->gen.reset_source = source;
conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
}
for (stack = conf->gen.stack; stack; stack = stack->prev)
{
@@ -8310,6 +8381,7 @@ set_config_option(const char *name, const char *value,
newextra);
stack->source = source;
stack->scontext = context;
+ stack->srole = srole;
}
}
}
@@ -9354,17 +9426,19 @@ define_custom_variable(struct config_generic *variable)
/* First, apply the reset value if any */
if (pHolder->reset_val)
- (void) set_config_option(name, pHolder->reset_val,
- pHolder->gen.reset_scontext,
- pHolder->gen.reset_source,
- GUC_ACTION_SET, true, WARNING, false);
+ (void) set_config_option_ext(name, pHolder->reset_val,
+ pHolder->gen.reset_scontext,
+ pHolder->gen.reset_source,
+ pHolder->gen.reset_srole,
+ GUC_ACTION_SET, true, WARNING, false);
/* That should not have resulted in stacking anything */
Assert(variable->stack == NULL);
/* Now, apply current and stacked values, in the order they were stacked */
reapply_stacked_values(variable, pHolder, pHolder->gen.stack,
*(pHolder->variable),
- pHolder->gen.scontext, pHolder->gen.source);
+ pHolder->gen.scontext, pHolder->gen.source,
+ pHolder->gen.srole);
/* Also copy over any saved source-location information */
if (pHolder->gen.sourcefile)
@@ -9395,7 +9469,8 @@ reapply_stacked_values(struct config_generic *variable,
struct config_string *pHolder,
GucStack *stack,
const char *curvalue,
- GucContext curscontext, GucSource cursource)
+ GucContext curscontext, GucSource cursource,
+ Oid cursrole)
{
const char *name = variable->name;
GucStack *oldvarstack = variable->stack;
@@ -9405,43 +9480,45 @@ reapply_stacked_values(struct config_generic *variable,
/* First, recurse, so that stack items are processed bottom to top */
reapply_stacked_values(variable, pHolder, stack->prev,
stack->prior.val.stringval,
- stack->scontext, stack->source);
+ stack->scontext, stack->source, stack->srole);
/* See how to apply the passed-in value */
switch (stack->state)
{
case GUC_SAVE:
- (void) set_config_option(name, curvalue,
- curscontext, cursource,
- GUC_ACTION_SAVE, true,
- WARNING, false);
+ (void) set_config_option_ext(name, curvalue,
+ curscontext, cursource, cursrole,
+ GUC_ACTION_SAVE, true,
+ WARNING, false);
break;
case GUC_SET:
- (void) set_config_option(name, curvalue,
- curscontext, cursource,
- GUC_ACTION_SET, true,
- WARNING, false);
+ (void) set_config_option_ext(name, curvalue,
+ curscontext, cursource, cursrole,
+ GUC_ACTION_SET, true,
+ WARNING, false);
break;
case GUC_LOCAL:
- (void) set_config_option(name, curvalue,
- curscontext, cursource,
- GUC_ACTION_LOCAL, true,
- WARNING, false);
+ (void) set_config_option_ext(name, curvalue,
+ curscontext, cursource, cursrole,
+ GUC_ACTION_LOCAL, true,
+ WARNING, false);
break;
case GUC_SET_LOCAL:
/* first, apply the masked value as SET */
- (void) set_config_option(name, stack->masked.val.stringval,
- stack->masked_scontext, PGC_S_SESSION,
- GUC_ACTION_SET, true,
- WARNING, false);
+ (void) set_config_option_ext(name, stack->masked.val.stringval,
+ stack->masked_scontext,
+ PGC_S_SESSION,
+ stack->masked_srole,
+ GUC_ACTION_SET, true,
+ WARNING, false);
/* then apply the current value as LOCAL */
- (void) set_config_option(name, curvalue,
- curscontext, cursource,
- GUC_ACTION_LOCAL, true,
- WARNING, false);
+ (void) set_config_option_ext(name, curvalue,
+ curscontext, cursource, cursrole,
+ GUC_ACTION_LOCAL, true,
+ WARNING, false);
break;
}
@@ -9461,11 +9538,12 @@ reapply_stacked_values(struct config_generic *variable,
*/
if (curvalue != pHolder->reset_val ||
curscontext != pHolder->gen.reset_scontext ||
- cursource != pHolder->gen.reset_source)
+ cursource != pHolder->gen.reset_source ||
+ cursrole != pHolder->gen.reset_srole)
{
- (void) set_config_option(name, curvalue,
- curscontext, cursource,
- GUC_ACTION_SET, true, WARNING, false);
+ (void) set_config_option_ext(name, curvalue,
+ curscontext, cursource, cursrole,
+ GUC_ACTION_SET, true, WARNING, false);
variable->stack = NULL;
}
}
@@ -10577,6 +10655,7 @@ _ShowOption(struct config_generic *record, bool use_units)
* variable sourceline, integer
* variable source, integer
* variable scontext, integer
+* variable srole, OID
*/
static void
write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
@@ -10643,6 +10722,7 @@ write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
fwrite(&gconf->sourceline, 1, sizeof(gconf->sourceline), fp);
fwrite(&gconf->source, 1, sizeof(gconf->source), fp);
fwrite(&gconf->scontext, 1, sizeof(gconf->scontext), fp);
+ fwrite(&gconf->srole, 1, sizeof(gconf->srole), fp);
}
void
@@ -10738,6 +10818,7 @@ read_nondefault_variables(void)
int varsourceline;
GucSource varsource;
GucContext varscontext;
+ Oid varsrole;
/*
* Open file
@@ -10774,10 +10855,12 @@ read_nondefault_variables(void)
elog(FATAL, "invalid format of exec config params file");
if (fread(&varscontext, 1, sizeof(varscontext), fp) != sizeof(varscontext))
elog(FATAL, "invalid format of exec config params file");
+ if (fread(&varsrole, 1, sizeof(varsrole), fp) != sizeof(varsrole))
+ elog(FATAL, "invalid format of exec config params file");
- (void) set_config_option(varname, varvalue,
- varscontext, varsource,
- GUC_ACTION_SET, true, 0, true);
+ (void) set_config_option_ext(varname, varvalue,
+ varscontext, varsource, varsrole,
+ GUC_ACTION_SET, true, 0, true);
if (varsourcefile[0])
set_config_sourcefile(varname, varsourcefile, varsourceline);
@@ -10931,6 +11014,7 @@ estimate_variable_size(struct config_generic *gconf)
size = add_size(size, sizeof(gconf->source));
size = add_size(size, sizeof(gconf->scontext));
+ size = add_size(size, sizeof(gconf->srole));
return size;
}
@@ -11076,6 +11160,8 @@ serialize_variable(char **destptr, Size *maxbytes,
sizeof(gconf->source));
do_serialize_binary(destptr, maxbytes, &gconf->scontext,
sizeof(gconf->scontext));
+ do_serialize_binary(destptr, maxbytes, &gconf->srole,
+ sizeof(gconf->srole));
}
/*
@@ -11177,6 +11263,7 @@ RestoreGUCState(void *gucstate)
int varsourceline;
GucSource varsource;
GucContext varscontext;
+ Oid varsrole;
char *srcptr = (char *) gucstate;
char *srcend;
Size len;
@@ -11304,12 +11391,15 @@ RestoreGUCState(void *gucstate)
&varsource, sizeof(varsource));
read_gucstate_binary(&srcptr, srcend,
&varscontext, sizeof(varscontext));
+ read_gucstate_binary(&srcptr, srcend,
+ &varsrole, sizeof(varsrole));
error_context_name_and_value[0] = varname;
error_context_name_and_value[1] = varvalue;
error_context_callback.arg = &error_context_name_and_value[0];
- result = set_config_option(varname, varvalue, varscontext, varsource,
- GUC_ACTION_SET, true, ERROR, true);
+ result = set_config_option_ext(varname, varvalue,
+ varscontext, varsource, varsrole,
+ GUC_ACTION_SET, true, ERROR, true);
if (result <= 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
@@ -11578,7 +11668,7 @@ GUCArrayDelete(ArrayType *array, const char *name)
/*
* Given a GUC array, delete all settings from it that our permission
* level allows: if superuser, delete them all; if regular user, only
- * those that are PGC_USERSET
+ * those that are PGC_USERSET or we have permission to set
*/
ArrayType *
GUCArrayReset(ArrayType *array)
@@ -11664,14 +11754,16 @@ validate_option_array_item(const char *name, const char *value,
*
* name is a known GUC variable. Check the value normally, check
* permissions normally (i.e., allow if variable is USERSET, or if it's
- * SUSET and user is superuser).
+ * SUSET and user is superuser or holds ACL_SET permissions).
*
* name is not known, but exists or can be created as a placeholder (i.e.,
* it has a valid custom name). We allow this case if you're a superuser,
* otherwise not. Superusers are assumed to know what they're doing. We
* can't allow it for other users, because when the placeholder is
- * resolved it might turn out to be a SUSET variable;
- * define_custom_variable assumes we checked that.
+ * resolved it might turn out to be a SUSET variable. (With currently
+ * available infrastructure, we can actually handle such cases within the
+ * current session --- but once an entry is made in pg_db_role_setting,
+ * it's assumed to be fully validated.)
*
* name is not known and can't be created as a placeholder. Throw error,
* unless skipIfNoPermissions is true, in which case return false.
@@ -11689,7 +11781,8 @@ validate_option_array_item(const char *name, const char *value,
* We cannot do any meaningful check on the value, so only permissions
* are useful to check.
*/
- if (superuser())
+ if (superuser() ||
+ pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)
return true;
if (skipIfNoPermissions)
return false;
@@ -11701,7 +11794,9 @@ validate_option_array_item(const char *name, const char *value,
/* manual permissions check so we can avoid an error being thrown */
if (gconf->context == PGC_USERSET)
/* ok */ ;
- else if (gconf->context == PGC_SUSET && superuser())
+ else if (gconf->context == PGC_SUSET &&
+ (superuser() ||
+ pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK))
/* ok */ ;
else if (skipIfNoPermissions)
return false;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 4d0920c42e2..e734493a484 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -388,6 +388,11 @@ extern int set_config_option(const char *name, const char *value,
GucContext context, GucSource source,
GucAction action, bool changeVal, int elevel,
bool is_reload);
+extern int set_config_option_ext(const char *name, const char *value,
+ GucContext context, GucSource source,
+ Oid srole,
+ GucAction action, bool changeVal, int elevel,
+ bool is_reload);
extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt);
extern char *GetConfigOptionByName(const char *name, const char **varname,
bool missing_ok);
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index ba44f7437bf..067d82ada95 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -120,6 +120,8 @@ typedef struct guc_stack
/* masked value's source must be PGC_S_SESSION, so no need to store it */
GucContext scontext; /* context that set the prior value */
GucContext masked_scontext; /* context that set the masked value */
+ Oid srole; /* role that set the prior value */
+ Oid masked_srole; /* role that set the masked value */
config_var_value prior; /* previous value of variable */
config_var_value masked; /* SET value in a GUC_SET_LOCAL entry */
} GucStack;
@@ -131,6 +133,10 @@ typedef struct guc_stack
* applications may use the long description as well, and will append
* it to the short description. (separated by a newline or '. ')
*
+ * srole is the role that set the current value, or BOOTSTRAP_SUPERUSERID
+ * if the value came from an internal source or the config file. Similarly
+ * for reset_srole (which is usually BOOTSTRAP_SUPERUSERID, but not always).
+ *
* Note that sourcefile/sourceline are kept here, and not pushed into stacked
* values, although in principle they belong with some stacked value if the
* active value is session- or transaction-local. This is to avoid bloating
@@ -152,6 +158,8 @@ struct config_generic
GucSource reset_source; /* source of the reset_value */
GucContext scontext; /* context that set the current value */
GucContext reset_scontext; /* context that set the reset value */
+ Oid srole; /* role that set the current value */
+ Oid reset_srole; /* role that set the reset value */
GucStack *stack; /* stacked prior values */
void *extra; /* "extra" pointer for current actual value */
char *last_reported; /* if variable is GUC_REPORT, value last sent
diff --git a/src/pl/plperl/expected/plperl_init.out b/src/pl/plperl/expected/plperl_init.out
index 133828e9f38..4b7e9254197 100644
--- a/src/pl/plperl/expected/plperl_init.out
+++ b/src/pl/plperl/expected/plperl_init.out
@@ -1,4 +1,4 @@
--- test plperl.on_plperl_init errors are fatal
+-- test plperl.on_plperl_init
-- This test tests setting on_plperl_init after loading plperl
LOAD 'plperl';
SET SESSION plperl.on_plperl_init = ' system("/nonesuch"); ';
@@ -12,3 +12,29 @@ DO $$ warn 42 $$ language plperl;
ERROR: 'system' trapped by operation mask at line 1.
CONTEXT: while executing plperl.on_plperl_init
PL/Perl anonymous code block
+--
+-- Reconnect (to unload plperl), then test setting on_plperl_init
+-- as an unprivileged user
+--
+\c -
+CREATE ROLE regress_plperl_user;
+SET ROLE regress_plperl_user;
+-- this succeeds, since the GUC isn't known yet
+SET SESSION plperl.on_plperl_init = 'test';
+RESET ROLE;
+LOAD 'plperl';
+WARNING: permission denied to set parameter "plperl.on_plperl_init"
+SHOW plperl.on_plperl_init;
+ plperl.on_plperl_init
+-----------------------
+
+(1 row)
+
+DO $$ warn 42 $$ language plperl;
+WARNING: 42 at line 1.
+-- now we won't be allowed to set it in the first place
+SET ROLE regress_plperl_user;
+SET SESSION plperl.on_plperl_init = 'test';
+ERROR: permission denied to set parameter "plperl.on_plperl_init"
+RESET ROLE;
+DROP ROLE regress_plperl_user;
diff --git a/src/pl/plperl/sql/plperl_init.sql b/src/pl/plperl/sql/plperl_init.sql
index 4ebf3f86eb7..2aa38110334 100644
--- a/src/pl/plperl/sql/plperl_init.sql
+++ b/src/pl/plperl/sql/plperl_init.sql
@@ -1,4 +1,4 @@
--- test plperl.on_plperl_init errors are fatal
+-- test plperl.on_plperl_init
-- This test tests setting on_plperl_init after loading plperl
LOAD 'plperl';
@@ -8,3 +8,34 @@ SET SESSION plperl.on_plperl_init = ' system("/nonesuch"); ';
SHOW plperl.on_plperl_init;
DO $$ warn 42 $$ language plperl;
+
+--
+-- Reconnect (to unload plperl), then test setting on_plperl_init
+-- as an unprivileged user
+--
+
+\c -
+
+CREATE ROLE regress_plperl_user;
+
+SET ROLE regress_plperl_user;
+
+-- this succeeds, since the GUC isn't known yet
+SET SESSION plperl.on_plperl_init = 'test';
+
+RESET ROLE;
+
+LOAD 'plperl';
+
+SHOW plperl.on_plperl_init;
+
+DO $$ warn 42 $$ language plperl;
+
+-- now we won't be allowed to set it in the first place
+SET ROLE regress_plperl_user;
+
+SET SESSION plperl.on_plperl_init = 'test';
+
+RESET ROLE;
+
+DROP ROLE regress_plperl_user;
diff --git a/src/test/modules/unsafe_tests/expected/guc_privs.out b/src/test/modules/unsafe_tests/expected/guc_privs.out
index de4e1b3cdfa..54f95b2334a 100644
--- a/src/test/modules/unsafe_tests/expected/guc_privs.out
+++ b/src/test/modules/unsafe_tests/expected/guc_privs.out
@@ -418,6 +418,8 @@ FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
pg_parameter_acl | work_mem | 0
(1 row)
+-- Make a per-role setting that regress_host_resource_admin can't change
+ALTER ROLE regress_host_resource_admin SET lc_messages = 'C';
-- Perform some operations as user 'regress_host_resource_admin'
SET SESSION AUTHORIZATION regress_host_resource_admin;
ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, privileges have been granted
@@ -457,6 +459,40 @@ SELECT set_config ('temp_buffers', '8192', false); -- ok
ALTER SYSTEM RESET autovacuum_work_mem; -- ok, privileges have been granted
ALTER SYSTEM RESET ALL; -- fail, insufficient privileges
ERROR: permission denied to perform ALTER SYSTEM RESET ALL
+ALTER ROLE regress_host_resource_admin SET lc_messages = 'POSIX'; -- fail
+ERROR: permission denied to set parameter "lc_messages"
+ALTER ROLE regress_host_resource_admin SET max_stack_depth = '1MB'; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ setconfig
+-------------------------------------
+ {lc_messages=C,max_stack_depth=1MB}
+(1 row)
+
+ALTER ROLE regress_host_resource_admin RESET max_stack_depth; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ setconfig
+-----------------
+ {lc_messages=C}
+(1 row)
+
+ALTER ROLE regress_host_resource_admin SET max_stack_depth = '1MB'; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ setconfig
+-------------------------------------
+ {lc_messages=C,max_stack_depth=1MB}
+(1 row)
+
+ALTER ROLE regress_host_resource_admin RESET ALL; -- doesn't reset lc_messages
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ setconfig
+-----------------
+ {lc_messages=C}
+(1 row)
+
-- Check dropping/revoking behavior
SET SESSION AUTHORIZATION regress_admin;
DROP ROLE regress_host_resource_admin; -- fail, privileges remain
diff --git a/src/test/modules/unsafe_tests/sql/guc_privs.sql b/src/test/modules/unsafe_tests/sql/guc_privs.sql
index a86b957b9c0..6c7733fc397 100644
--- a/src/test/modules/unsafe_tests/sql/guc_privs.sql
+++ b/src/test/modules/unsafe_tests/sql/guc_privs.sql
@@ -163,6 +163,9 @@ SELECT classid::regclass,
objsubid
FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
+-- Make a per-role setting that regress_host_resource_admin can't change
+ALTER ROLE regress_host_resource_admin SET lc_messages = 'C';
+
-- Perform some operations as user 'regress_host_resource_admin'
SET SESSION AUTHORIZATION regress_host_resource_admin;
ALTER SYSTEM SET autovacuum_work_mem = 32; -- ok, privileges have been granted
@@ -187,6 +190,19 @@ ALTER SYSTEM RESET lc_messages; -- fail, insufficient privileges
SELECT set_config ('temp_buffers', '8192', false); -- ok
ALTER SYSTEM RESET autovacuum_work_mem; -- ok, privileges have been granted
ALTER SYSTEM RESET ALL; -- fail, insufficient privileges
+ALTER ROLE regress_host_resource_admin SET lc_messages = 'POSIX'; -- fail
+ALTER ROLE regress_host_resource_admin SET max_stack_depth = '1MB'; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ALTER ROLE regress_host_resource_admin RESET max_stack_depth; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ALTER ROLE regress_host_resource_admin SET max_stack_depth = '1MB'; -- ok
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
+ALTER ROLE regress_host_resource_admin RESET ALL; -- doesn't reset lc_messages
+SELECT setconfig FROM pg_db_role_setting
+ WHERE setrole = 'regress_host_resource_admin'::regrole;
-- Check dropping/revoking behavior
SET SESSION AUTHORIZATION regress_admin;