From 15c1c6fb86e2569fb498defa1e77ca25a770d074 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 15 Nov 2021 09:19:22 -0800
Subject: [PATCH v3] Allow GRANT of SET and ALTER SYSTEM for variables

Allow granting of privilege to set or alter system set variables
which otherwise can only be managed by superusers.  Each
(role,variable,privilege) triple is independently grantable, so a
user may be granted privilege to SET but not to ALTER SYSTEM SET on
a variable, or vice versa.  The privilege to SET a userset variable
may be granted, though doing so has no practical effect, since any
role can set userset variables anyway.
---
 doc/src/sgml/catalogs.sgml                  |  63 +++++
 doc/src/sgml/ddl.sgml                       |  39 ++-
 doc/src/sgml/ref/grant.sgml                 |   7 +
 doc/src/sgml/ref/set.sgml                   |   5 +-
 src/backend/catalog/Makefile                |   3 +-
 src/backend/catalog/aclchk.c                | 251 ++++++++++++++++++++
 src/backend/catalog/catalog.c               |   6 +
 src/backend/catalog/dependency.c            |   6 +
 src/backend/catalog/information_schema.sql  |  19 ++
 src/backend/catalog/objectaddress.c         |  46 ++++
 src/backend/catalog/pg_config_param.c       | 126 ++++++++++
 src/backend/commands/alter.c                |   1 +
 src/backend/commands/dropcmds.c             |   7 +
 src/backend/commands/event_trigger.c        |   6 +
 src/backend/commands/seclabel.c             |   1 +
 src/backend/commands/tablecmds.c            |   1 +
 src/backend/parser/gram.y                   |  99 +++++++-
 src/backend/utils/adt/acl.c                 |  58 +++++
 src/backend/utils/cache/lsyscache.c         |  20 ++
 src/backend/utils/cache/syscache.c          |  23 ++
 src/backend/utils/misc/guc.c                | 107 +++++++--
 src/bin/pg_dump/dumputils.c                 |   7 +-
 src/bin/pg_dump/pg_backup_archiver.c        |   2 +
 src/bin/pg_dump/pg_dump.c                   |   5 +-
 src/bin/pg_dump/pg_dumpall.c                |  71 ++++++
 src/include/catalog/dependency.h            |   1 +
 src/include/catalog/pg_config_param.h       |  63 +++++
 src/include/catalog/pg_default_acl.h        |   1 +
 src/include/nodes/parsenodes.h              |   5 +-
 src/include/parser/kwlist.h                 |   1 +
 src/include/tcop/cmdtaglist.h               |   2 +
 src/include/utils/acl.h                     |  10 +-
 src/include/utils/guc.h                     |   2 +
 src/include/utils/lsyscache.h               |   1 +
 src/include/utils/syscache.h                |   2 +
 src/test/modules/test_pg_dump/t/001_base.pl |  54 ++++-
 src/test/regress/expected/guc_privs.out     | 204 ++++++++++++++++
 src/test/regress/expected/privileges.out    |  54 ++++-
 src/test/regress/expected/sanity_check.out  |   1 +
 src/test/regress/parallel_schedule          |   2 +-
 src/test/regress/sql/guc_privs.sql          | 142 +++++++++++
 src/test/regress/sql/privileges.sql         |  44 +++-
 42 files changed, 1532 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/catalog/pg_config_param.c
 create mode 100644 src/include/catalog/pg_config_param.h
 create mode 100644 src/test/regress/expected/guc_privs.out
 create mode 100644 src/test/regress/sql/guc_privs.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f..fa085549b8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -105,6 +105,11 @@
       <entry>collations (locale information)</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-config-param"><structname>pg_config_param</structname></link></entry>
+      <entry>configuration parameters which have privileges granted to roles</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-constraint"><structname>pg_constraint</structname></link></entry>
       <entry>check constraints, unique constraints, primary key constraints, foreign key constraints</entry>
@@ -2423,6 +2428,64 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </para>
  </sect1>
 
+ <sect1 id="catalog-pg-config-param">
+  <title><structname>pg_config_param</structname></title>
+
+  <indexterm zone="catalog-pg-config-param">
+   <primary>pg_config_param</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_config_param</structname> records configuration
+   parameters which have had privileges to <literal>SET</literal> or
+   <literal>ALTER SYSTEM</literal> granted to one or more roles.
+  </para>
+
+  <para>
+   Unlike most system catalogs, <structname>pg_config_param</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_config_param</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_config_param</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>configname</structfield> <type>text</type>
+      </para>
+      <para>
+       The name of the configuration parameter for which privileges are granted.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>cfgacl</structfield> <type>aclitem[]</type>
+      </para>
+      <para>
+       Access privileges; see <xref linkend="ddl-priv"/> for details
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-constraint">
   <title><structname>pg_constraint</structname></title>
 
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 94f745aed0..d862ab8c0e 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1620,7 +1620,8 @@ ALTER TABLE products RENAME TO items;
    <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>,
    <literal>TRUNCATE</literal>, <literal>REFERENCES</literal>, <literal>TRIGGER</literal>,
    <literal>CREATE</literal>, <literal>CONNECT</literal>, <literal>TEMPORARY</literal>,
-   <literal>EXECUTE</literal>, and <literal>USAGE</literal>.
+   <literal>EXECUTE</literal>, <literal>USAGE</literal>, <literal>SET VALUE</literal>
+   and <literal>ALTER SYSTEM</literal>.
    The privileges applicable to a particular
    object vary depending on the object's type (table, function, etc).
    More detail about the meanings of these privileges appears below.
@@ -1888,6 +1889,26 @@ REVOKE ALL ON accounts FROM PUBLIC;
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET VALUE</literal></term>
+     <listitem>
+      <para>
+       Allows non-superuser roles to use the <command>SET</command> command to
+       change run-time configuration parameters.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER SYSTEM</literal></term>
+     <listitem>
+      <para>
+       Allows non-superuser roles to use the <command>ALTER SYSTEM
+       SET</command> command to change server configuration parameters.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    The privileges required by other commands are listed on the
@@ -2026,6 +2047,16 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>TYPE</literal>
       </entry>
      </row>
+     <row>
+      <entry><literal>SET VALUE</literal></entry>
+      <entry><literal>s</literal></entry>
+      <entry>configuration parameter</entry>
+     </row>
+     <row>
+      <entry><literal>ALTER SYSTEM</literal></entry>
+      <entry><literal>A</literal></entry>
+      <entry>configuration parameter</entry>
+     </row>
      </tbody>
    </tgroup>
   </table>
@@ -2132,6 +2163,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry><literal>U</literal></entry>
       <entry><literal>\dT+</literal></entry>
      </row>
+     <row>
+      <entry><literal>Configuration parameter</literal></entry>
+      <entry><literal>sA</literal></entry>
+      <entry>none</entry>
+      <entry><literal></literal></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index a897712de2..21ec320a8d 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -92,6 +92,11 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
 
+GRANT { SET VALUE | ALTER SYSTEM }
+    ON <replaceable class="parameter">configuration_parameter</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
 GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
     [ WITH ADMIN OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
@@ -185,6 +190,8 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
      <term><literal>TEMPORARY</literal></term>
      <term><literal>EXECUTE</literal></term>
      <term><literal>USAGE</literal></term>
+     <term><literal>SET VALUE</literal></term>
+     <term><literal>ALTER SYSTEM</literal></term>
      <listitem>
       <para>
        Specific types of privileges, as defined in <xref linkend="ddl-priv"/>.
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 339ee9eec9..a08057d1d1 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -34,8 +34,9 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="parameter">timezone</rep
    parameters.  Many of the run-time parameters listed in
    <xref linkend="runtime-config"/> can be changed on-the-fly with
    <command>SET</command>.
-   (But some require superuser privileges to change, and others cannot
-   be changed after server or session start.)
+   (But some require either superuser privileges or granted <literal>SET
+   VALUE</literal> privileges to change, and others cannot be changed after
+   server or session start.)
    <command>SET</command> only affects the value used by the current
    session.
   </para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f..bec208db98 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -28,6 +28,7 @@ OBJS = \
 	pg_cast.o \
 	pg_class.o \
 	pg_collation.o \
+	pg_config_param.o \
 	pg_constraint.o \
 	pg_conversion.o \
 	pg_db_role_setting.o \
@@ -54,7 +55,7 @@ include $(top_srcdir)/src/backend/common.mk
 # there are reputedly other, undocumented ordering dependencies.
 CATALOG_HEADERS := \
 	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
-	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
+	pg_attrdef.h pg_config_param.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
 	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
 	pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ce0a4ff14e..d309375a31 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
@@ -112,6 +113,7 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
+static void ExecGrant_ConfigParam(InternalGrant *grantStmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -259,6 +261,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_TYPE:
 			whole_mask = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_CONFIG_PARAM:
+			whole_mask = ACL_ALL_RIGHTS_CONFIG_PARAM;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -498,6 +503,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_CONFIG_PARAM:
+			all_privileges = ACL_ALL_RIGHTS_CONFIG_PARAM;
+			errormsg = gettext_noop("invalid privilege type %s for configuration parameter");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -600,6 +609,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_TABLESPACE:
 			ExecGrant_Tablespace(istmt);
 			break;
+		case OBJECT_CONFIG_PARAM:
+			ExecGrant_ConfigParam(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -759,6 +771,38 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				objects = lappend_oid(objects, srvid);
 			}
 			break;
+		case OBJECT_CONFIG_PARAM:
+			foreach(cell, objnames)
+			{
+				char	   *cfgname = strVal(lfirst(cell));
+				Oid			cfgid = get_cfgparam_oid(cfgname, true);
+
+				if (!OidIsValid(cfgid))
+				{
+					/*
+					 * Lookup the existing entry, or if necessary, add a new
+					 * entry for this parameter.  Entries only exist for
+					 * parameters which currently have, or previously have had,
+					 * privileges assigned.
+					 *
+					 * It is tempting to sanity-check the given configuration
+					 * parameter name against known guc names in the guc
+					 * tables, but for handling upgrades we need to accept
+					 * configuration parameter names that do not yet exist.
+					 */
+					cfgid = ConfigParamCreate(cfgname, true);
+
+					/*
+					 * Prevent error when processing duplicate objects, and
+					 * make this new entry visible to our later selves which
+					 * will need to update the Acl.
+					 */
+					CommandCounterIncrement();
+				}
+
+				objects = lappend_oid(objects, cfgid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -1494,6 +1538,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ForeignDataWrapperRelationId:
 				istmt.objtype = OBJECT_FDW;
 				break;
+			case ConfigParamRelationId:
+				istmt.objtype = OBJECT_CONFIG_PARAM;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -3225,6 +3272,128 @@ ExecGrant_Type(InternalGrant *istmt)
 	table_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_ConfigParam(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_CONFIG_PARAM;
+
+	relation = table_open(ConfigParamRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			cfgId = lfirst_oid(cell);
+		Form_pg_config_param pg_config_param_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_config_param];
+		bool		nulls[Natts_pg_config_param];
+		bool		replaces[Natts_pg_config_param];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(CONFIGOID, ObjectIdGetDatum(cfgId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for configuration parameter %u", cfgId);
+
+		pg_config_param_tuple = (Form_pg_config_param) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		aclDatum = SysCacheGetAttr(CONFIGNAME, tuple, Anum_pg_config_param_cfgacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_CONFIG_PARAM, InvalidOid);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, GetUserId(),
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 cfgId, grantorId, OBJECT_CONFIG_PARAM,
+									 text_to_cstring(&pg_config_param_tuple->configname),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, InvalidOid);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_config_param_cfgacl - 1] = true;
+		values[Anum_pg_config_param_cfgacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(cfgId, ConfigParamRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(ConfigParamRelationId,
+							  pg_config_param_tuple->oid, 0,
+							  InvalidOid,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	table_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3255,6 +3424,10 @@ string_to_privilege(const char *privname)
 		return ACL_CREATE_TEMP;
 	if (strcmp(privname, "connect") == 0)
 		return ACL_CONNECT;
+	if (strcmp(privname, "set value") == 0)
+		return ACL_SET;
+	if (strcmp(privname, "alter system") == 0)
+		return ACL_ALTER_SYSTEM;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
 	ereport(ERROR,
@@ -3292,6 +3465,10 @@ privilege_to_string(AclMode privilege)
 			return "TEMP";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_SET:
+			return "SET VALUE";
+		case ACL_ALTER_SYSTEM:
+			return "ALTER SYSTEM";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3328,6 +3505,13 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_COLUMN:
 						msg = gettext_noop("permission denied for column %s");
 						break;
+					case OBJECT_CONFIG_PARAM:
+						/*
+						 * Quote the object name for backward compatibility
+						 * with behavior before SET was handled here.
+						 */
+						msg = gettext_noop("permission denied to set parameter \"%s\"");
+						break;
 					case OBJECT_CONVERSION:
 						msg = gettext_noop("permission denied for conversion %s");
 						break;
@@ -3564,6 +3748,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_CONFIG_PARAM:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
@@ -4000,6 +4185,59 @@ pg_database_aclmask(Oid db_oid, Oid roleid,
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC)
+ */
+AclMode
+pg_config_param_aclmask(Oid config_oid, Oid roleid,
+						AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Get the configuration parameter's ACL from pg_config_param
+	 */
+	tuple = SearchSysCache1(CONFIGOID, ObjectIdGetDatum(config_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("configuration parameter with OID %u does not exist",
+						config_oid)));
+
+	aclDatum = SysCacheGetAttr(CONFIGOID, tuple, Anum_pg_config_param_cfgacl,
+							   &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_CONFIG_PARAM, InvalidOid);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, InvalidOid, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * Exported routine for examining a user's privileges for a function
  */
@@ -4713,6 +4951,19 @@ pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a configuration
+ * parameter
+ */
+AclResult
+pg_config_param_aclcheck(Oid config_oid, Oid roleid, AclMode mode)
+{
+	if (pg_config_param_aclmask(config_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a function
  */
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456..578bac485c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -29,6 +29,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_largeobject.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
 		relationId == AuthMemRelationId ||
+		relationId == ConfigParamRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == SharedDescriptionRelationId ||
 		relationId == SharedDependRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
 		relationId == AuthIdOidIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
+		relationId == ConfigParamNameIndexId ||
+		relationId == ConfigParamOidIndexId ||
 		relationId == DatabaseNameIndexId ||
 		relationId == DatabaseOidIndexId ||
 		relationId == SharedDescriptionObjIndexId ||
@@ -277,6 +281,8 @@ IsSharedRelation(Oid relationId)
 	/* These are their toast tables and toast indexes */
 	if (relationId == PgAuthidToastTable ||
 		relationId == PgAuthidToastIndex ||
+		relationId == PgConfigParamToastTable ||
+		relationId == PgConfigParamToastIndex ||
 		relationId == PgDatabaseToastTable ||
 		relationId == PgDatabaseToastIndex ||
 		relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fe9c714257..65cf4fbd0b 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
@@ -150,6 +151,7 @@ static const Oid object_classes[] = {
 	TypeRelationId,				/* OCLASS_TYPE */
 	CastRelationId,				/* OCLASS_CAST */
 	CollationRelationId,		/* OCLASS_COLLATION */
+	ConfigParamRelationId,		/* OCLASS_CONFIG_PARAM */
 	ConstraintRelationId,		/* OCLASS_CONSTRAINT */
 	ConversionRelationId,		/* OCLASS_CONVERSION */
 	AttrDefaultRelationId,		/* OCLASS_DEFAULT */
@@ -1504,6 +1506,7 @@ doDeletion(const ObjectAddress *object, int flags)
 			/*
 			 * These global object types are not supported here.
 			 */
+		case OCLASS_CONFIG_PARAM:
 		case OCLASS_ROLE:
 		case OCLASS_DATABASE:
 		case OCLASS_TBLSPACE:
@@ -2778,6 +2781,9 @@ getObjectClass(const ObjectAddress *object)
 		case CollationRelationId:
 			return OCLASS_COLLATION;
 
+		case ConfigParamRelationId:
+			return OCLASS_CONFIG_PARAM;
+
 		case ConstraintRelationId:
 			return OCLASS_CONSTRAINT;
 
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 11d9dd60c2..38dd62c398 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -2429,6 +2429,25 @@ CREATE VIEW usage_privileges AS
 GRANT SELECT ON usage_privileges TO PUBLIC;
 
 
+/*
+ * GUC_PRIVILEGES view
+ */
+CREATE VIEW guc_privileges AS
+	SELECT CAST(grantor.rolname AS sql_identifier) AS grantor,
+		   CAST(grantee.rolname AS sql_identifier) aS grantee,
+		   CAST(cfg.configname AS character_data) AS guc,
+		   CAST(acl.privilege_type AS character_data) AS privilege_type,
+		   CAST(CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable
+		FROM pg_catalog.pg_config_param cfg,
+		LATERAL (SELECT * FROM aclexplode(cfg.cfgacl)) acl
+		JOIN pg_catalog.pg_authid grantee
+		ON acl.grantee = grantee.oid
+		LEFT JOIN pg_catalog.pg_authid grantor ON acl.grantor = grantor.oid;
+		-- WHERE acl.grantee > 0;
+
+GRANT SELECT ON guc_privileges TO PUBLIC;
+
+
 /*
  * 5.45
  * ROLE_USAGE_GRANTS view
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2bae3fbb17..7355c7403d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
@@ -2309,6 +2310,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_COLLATION:
+		case OBJECT_CONFIG_PARAM:
 		case OBJECT_CONVERSION:
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TSPARSER:
@@ -3510,6 +3512,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_CONFIG_PARAM:
+			{
+				char	   *configname;
+
+				configname = get_cfgparam_name(object->objectId);
+				if (!configname)
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for configuration parameter %u",
+							 object->objectId);
+					break;
+				}
+				appendStringInfo(&buffer, _("configuration parameter %s"), configname);
+				break;
+			}
+
 		case OCLASS_SCHEMA:
 			{
 				char	   *nspname;
@@ -4473,6 +4491,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "collation");
 			break;
 
+		case OCLASS_CONFIG_PARAM:
+			appendStringInfoString(&buffer, "configuration parameter");
+			break;
+
 		case OCLASS_CONSTRAINT:
 			getConstraintTypeDescription(&buffer, object->objectId,
 										 missing_ok);
@@ -4977,6 +4999,30 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_CONFIG_PARAM:
+			{
+				HeapTuple	configTup;
+				Form_pg_config_param configForm;
+				char	   *namestr;
+
+				configTup = SearchSysCache1(CONFIGOID,
+											ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(configTup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for configuration parameter %u",
+							 object->objectId);
+					break;
+				}
+				configForm = (Form_pg_config_param) GETSTRUCT(configTup);
+				namestr = text_to_cstring(&configForm->configname);
+				appendStringInfoString(&buffer, namestr);
+				if (objname)
+					*objname = list_make1(namestr);
+				ReleaseSysCache(configTup);
+				break;
+			}
+
 		case OCLASS_CONVERSION:
 			{
 				HeapTuple	conTup;
diff --git a/src/backend/catalog/pg_config_param.c b/src/backend/catalog/pg_config_param.c
new file mode 100644
index 0000000000..f71de8aa25
--- /dev/null
+++ b/src/backend/catalog/pg_config_param.c
@@ -0,0 +1,126 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_config_param.c
+ *	  routines to support manipulation of the pg_config_param relation
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_config_param.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/table.h"
+#include "access/tableam.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_config_param.h"
+#include "catalog/pg_namespace.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/pg_locale.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * ConfigParamCreate
+ *
+ * Add a new tuple to pg_config_param.
+ *
+ * configname: the configuration parameter name to create.
+ * if_not_exists: if true, don't fail on duplicate name, but rather return
+ * the existing entry's Oid.
+ */
+Oid
+ConfigParamCreate(const char *configname, bool if_not_exists)
+{
+	Relation	rel;
+	TupleDesc	tupDesc;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_config_param];
+	bool		nulls[Natts_pg_config_param];
+	Oid			configParamId;
+	const char *canonical;
+
+	/*
+	 * Check whether the configuration parameter (by the given name or alias)
+	 * already exists.
+	 */
+	configParamId = get_cfgparam_oid(configname, true);
+	if (OidIsValid(configParamId))
+	{
+		if (if_not_exists)
+			return configParamId;
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("configuration parameter \"%s\" already exists",
+						configname)));
+	}
+
+	/*
+	 * We must not require the configname to be in the list of existent GUCs,
+	 * as we may be called at different points during upgrades or the
+	 * installation or removal of extensions.  Instead, perform a basic sanity
+	 * check of the configname, and translate old forms of known names to their
+	 * canonical forms.
+	 *
+	 * If you deprecate a configuration name in favor of a new spelling, be
+	 * sure to consider whether to also upgrade pg_config_param entries.
+	 */
+	if (!valid_variable_name(configname, NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_NAME),
+				 errmsg("invalid configuration parameter name \"%s\"",
+						configname)));
+	canonical = GetConfigOptionCanonicalName(configname);
+	if (!canonical)
+		canonical = configname;
+	else if (strcmp(canonical, configname) != 0)
+		ereport(NOTICE,
+				(errcode(ERRCODE_INVALID_NAME),
+				 errmsg("granting privileges on canonical configuration parameter name \"%s\"", canonical)));
+
+	/*
+	 * Create and insert a new record, starting with a blank Acl.
+	 *
+	 * We don't take a strong enough lock to prevent concurrent insertions,
+	 * relying instead on the unique index.
+	 */
+	rel = table_open(ConfigParamRelationId, RowExclusiveLock);
+	tupDesc = RelationGetDescr(rel);
+	MemSet(values, 0, sizeof(values));
+	MemSet(nulls, false, sizeof(nulls));
+	values[Anum_pg_config_param_configname - 1] =
+		DirectFunctionCall1(textin, CStringGetDatum(canonical));
+	configParamId = GetNewOidWithIndex(rel,
+									   ConfigParamOidIndexId,
+									   Anum_pg_config_param_oid);
+	values[Anum_pg_config_param_oid - 1] = ObjectIdGetDatum(configParamId);
+	nulls[Anum_pg_config_param_cfgacl - 1] = true;
+	tuple = heap_form_tuple(tupDesc, values, nulls);
+	CatalogTupleInsert(rel, tuple);
+
+	/* Post creation hook for new configuration parameter */
+	InvokeObjectPostCreateHook(ConfigParamRelationId, configParamId, 0);
+
+	/*
+	 * Close pg_config_param, but keep lock till commit.
+	 */
+	heap_freetuple(tuple);
+	table_close(rel, NoLock);
+
+	return configParamId;
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 40044070cf..1f8c3f78c2 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -639,6 +639,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 			break;
 
 		case OCLASS_CAST:
+		case OCLASS_CONFIG_PARAM:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 4e545adf95..cd98976e07 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -276,6 +276,13 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 				name = NameListToString(castNode(List, object));
 			}
 			break;
+		case OBJECT_CONFIG_PARAM:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("configuration parameter \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
 		case OBJECT_CONVERSION:
 			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
 			{
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index df264329d8..419a7a4fad 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -937,6 +937,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 {
 	switch (obtype)
 	{
+		case OBJECT_CONFIG_PARAM:
 		case OBJECT_DATABASE:
 		case OBJECT_TABLESPACE:
 		case OBJECT_ROLE:
@@ -1012,6 +1013,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 {
 	switch (objclass)
 	{
+		case OCLASS_CONFIG_PARAM:
 		case OCLASS_DATABASE:
 		case OCLASS_TBLSPACE:
 		case OCLASS_ROLE:
@@ -2078,6 +2080,8 @@ stringify_grant_objtype(ObjectType objtype)
 	{
 		case OBJECT_COLUMN:
 			return "COLUMN";
+		case OBJECT_CONFIG_PARAM:
+			return "CONFIGURATION PARAMETER";
 		case OBJECT_TABLE:
 			return "TABLE";
 		case OBJECT_SEQUENCE:
@@ -2161,6 +2165,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 	{
 		case OBJECT_COLUMN:
 			return "COLUMNS";
+		case OBJECT_CONFIG_PARAM:
+			return "CONFIGURATION PARAMETERS";
 		case OBJECT_TABLE:
 			return "TABLES";
 		case OBJECT_SEQUENCE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 53c18628a7..6a0fe325cf 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -67,6 +67,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLLATION:
+		case OBJECT_CONFIG_PARAM:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 785b282e69..c600169cbd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12262,6 +12262,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
 			case OCLASS_COLLATION:
+			case OCLASS_CONFIG_PARAM:
 			case OCLASS_CONVERSION:
 			case OCLASS_LANGUAGE:
 			case OCLASS_LARGEOBJECT:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a6d0cefa6b..2adadaa6e4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -348,8 +348,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		foreign_server_version opt_foreign_server_version
 %type <str>		opt_in_database
 
-%type <str>		OptSchemaName
-%type <list>	OptSchemaEltList
+%type <str>		OptSchemaName cfgparam_name
+%type <list>	OptSchemaEltList cfgparam_target
 
 %type <chr>		am_type
 
@@ -387,8 +387,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		iso_level opt_encoding
 %type <rolespec> grantee
 %type <list>	grantee_list
-%type <accesspriv> privilege
-%type <list>	privileges privilege_list
+%type <accesspriv> privilege guc_priv
+%type <list>	privileges privilege_list guc_priv_list
 %type <privtarget> privilege_target
 %type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
 %type <list>	function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
@@ -698,7 +698,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -6884,6 +6884,20 @@ GrantStmt:	GRANT privileges ON privilege_target TO grantee_list
 					n->grantor = $8;
 					$$ = (Node*)n;
 				}
+			| GRANT guc_priv_list ON cfgparam_target TO grantee_list
+			opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = true;
+					n->privileges = $2;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_CONFIG_PARAM;
+					n->objects = $4;
+					n->grantees = $6;
+					n->grant_option = $7;
+					n->grantor = $8;
+					$$ = (Node*)n;
+				}
 		;
 
 RevokeStmt:
@@ -6917,6 +6931,36 @@ RevokeStmt:
 					n->behavior = $11;
 					$$ = (Node *)n;
 				}
+			| REVOKE guc_priv_list ON cfgparam_target FROM grantee_list
+			opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = false;
+					n->grant_option = false;
+					n->privileges = $2;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_CONFIG_PARAM;
+					n->objects = $4;
+					n->grantees = $6;
+					n->grantor = $7;
+					n->behavior = $8;
+					$$ = (Node *)n;
+				}
+			| REVOKE GRANT OPTION FOR guc_priv_list ON cfgparam_target
+			FROM grantee_list opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = false;
+					n->grant_option = true;
+					n->privileges = $5;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_CONFIG_PARAM;
+					n->objects = $7;
+					n->grantees = $9;
+					n->grantor = $10;
+					n->behavior = $11;
+					$$ = (Node *)n;
+				}
 		;
 
 
@@ -6985,6 +7029,27 @@ privilege:	SELECT opt_column_list
 			}
 		;
 
+guc_priv_list: guc_priv								{ $$ = list_make1($1); }
+			| guc_priv_list ',' guc_priv			{ $$ = lappend($1, $3); }
+		;
+
+guc_priv:
+		ALTER SYSTEM_P
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup("alter system");
+				n->cols = NIL;
+				$$ = n;
+			}
+		| SET VALUE_P
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup("set value");
+				n->cols = NIL;
+				$$ = n;
+			}
+		;
+
 
 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
@@ -10658,6 +10723,28 @@ any_with:	WITH
 		;
 
 
+cfgparam_target:
+			cfgparam_name
+				{
+					$$ = list_make1(makeString($1));
+				}
+			| cfgparam_target ',' cfgparam_name
+				{
+					$$ = lappend($1, makeString($3));
+				}
+		;
+
+cfgparam_name:
+			ColId
+				{
+					$$ = $1;
+				}
+			| cfgparam_name '.' ColId
+				{
+					$$ = psprintf("%s.%s", $1, $3);
+				}
+		;
+
 /*****************************************************************************
  *
  * Manipulate a conversion
@@ -15728,6 +15815,7 @@ unreserved_keyword:
 			| OWNED
 			| OWNER
 			| PARALLEL
+			| PARAMETER
 			| PARSER
 			| PARTIAL
 			| PARTITION
@@ -16306,6 +16394,7 @@ bare_label_keyword:
 			| OWNED
 			| OWNER
 			| PARALLEL
+			| PARAMETER
 			| PARSER
 			| PARTIAL
 			| PARTITION
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434..895b542916 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
@@ -36,6 +37,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
+#include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -306,6 +308,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_CONNECT_CHR:
 				read = ACL_CONNECT;
 				break;
+			case ACL_SET_CHR:
+				read = ACL_SET;
+				break;
+			case ACL_ALTER_SYSTEM_CHR:
+				read = ACL_ALTER_SYSTEM;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -794,6 +802,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_USAGE;
 			owner_default = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_CONFIG_PARAM:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_NO_RIGHTS;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -1602,6 +1614,10 @@ convert_priv_string(text *priv_type_text)
 		return ACL_CREATE_TEMP;
 	if (pg_strcasecmp(priv_type, "CONNECT") == 0)
 		return ACL_CONNECT;
+	if (pg_strcasecmp(priv_type, "SET VALUE") == 0)
+		return ACL_SET;
+	if (pg_strcasecmp(priv_type, "ALTER SYSTEM") == 0)
+		return ACL_ALTER_SYSTEM;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
 
@@ -1698,6 +1714,10 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_SET:
+			return "SET VALUE";
+		case ACL_ALTER_SYSTEM:
+			return "ALTER SYSTEM";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
@@ -4670,6 +4690,44 @@ initialize_acl(void)
 	}
 }
 
+/*
+ * get_cfgparam_oid - Given a configuration parameter name, look up the
+ * configuration parameter's OID.  Note that names which are aliases for
+ * a canonical name will be translated automatically and the OID found.
+ *
+ * If missing_ok is false, throw an error if the configuration parameter name
+ * is not found.
+ *
+ * Returns the Oid of the configuration parameter.
+ */
+Oid
+get_cfgparam_oid(const char *cfgname, bool missing_ok)
+{
+	Oid			oid;
+
+	/* Check for the variable by the name we were given */
+	oid = GetSysCacheOid1(CONFIGNAME, Anum_pg_config_param_oid,
+						  PointerGetDatum(cstring_to_text(cfgname)));
+	if (!OidIsValid(oid))
+	{
+		const char *canonical;
+
+		/* Check if the variable has a different canonical spelling */
+		canonical = GetConfigOptionCanonicalName(cfgname);
+		if (canonical != NULL)
+			oid = GetSysCacheOid1(CONFIGNAME, Anum_pg_config_param_oid,
+								  PointerGetDatum(cstring_to_text(canonical)));
+
+		if (!OidIsValid(oid) && !missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("configuration parameter \"%s\" does not exist",
+							cfgname)));
+	}
+
+	return oid;
+}
+
 /*
  * RoleMembershipCacheCallback
  *		Syscache inval callback function
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..b8768f7506 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -25,6 +25,7 @@
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
@@ -3304,6 +3305,25 @@ free_attstatsslot(AttStatsSlot *sslot)
 		pfree(sslot->numbers_arr);
 }
 
+char *
+get_cfgparam_name(Oid configid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(CONFIGOID, ObjectIdGetDatum(configid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_config_param configtup = (Form_pg_config_param) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(text_to_cstring(&configtup->configname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
 /*				---------- PG_NAMESPACE CACHE ----------				 */
 
 /*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 56870b46e4..e66158d584 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_config_param.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
@@ -310,6 +311,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{ConfigParamRelationId,		/* CONFIGNAME */
+		ConfigParamNameIndexId,
+		1,
+		{
+			Anum_pg_config_param_configname,
+			0,
+			0,
+			0
+		},
+		4
+	},
+	{ConfigParamRelationId,		/* CONFIGOID */
+		ConfigParamOidIndexId,
+		1,
+		{
+			Anum_pg_config_param_oid,
+			0,
+			0,
+			0
+		},
+		4
+	},
 	{ConversionRelationId,		/* CONDEFAULT */
 		ConversionDefaultIndexId,
 		4,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..446e42d320 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -5021,6 +5021,10 @@ static struct config_enum ConfigureNamesEnum[] =
  * the following mappings to any unrecognized name.  Note that an old name
  * should be mapped to a new one only if the new variable has very similar
  * semantics to the old.
+ *
+ * If you deprecate a name in favor of a new spelling, be sure to consider what
+ * upgrade support will be needed, if any, for existing pg_config_param
+ * entries.
  */
 static const char *const map_old_guc_names[] = {
 	"sort_mem", "work_mem",
@@ -5420,25 +5424,29 @@ add_guc_variable(struct config_generic *var, int elevel)
 }
 
 /*
- * Decide whether a proposed custom variable name is allowed.
+ * Decide whether a proposed variable name is allowed.
  *
- * It must be two or more identifiers separated by dots, where the rules
- * for what is an identifier agree with scan.l.  (If you change this rule,
- * adjust the errdetail in find_option().)
+ * It must be one or more identifiers separated by zero or more dots, where the
+ * rules for what is an identifier agree with scan.l.  (If you change this
+ * rule, adjust the errdetail in find_option().)
+ *
+ * partcnt: returns by reference the number of dot separated identifiers.
  */
-static bool
-valid_custom_variable_name(const char *name)
+bool
+valid_variable_name(const char *name, int *partcnt)
 {
-	bool		saw_sep = false;
+	int			parts = 1;
 	bool		name_start = true;
 
+	if (partcnt)
+		*partcnt = -1;
 	for (const char *p = name; *p; p++)
 	{
 		if (*p == GUC_QUALIFIER_SEPARATOR)
 		{
 			if (name_start)
 				return false;	/* empty name component */
-			saw_sep = true;
+			parts++;
 			name_start = true;
 		}
 		else if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -5455,8 +5463,24 @@ valid_custom_variable_name(const char *name)
 	}
 	if (name_start)
 		return false;			/* empty name component */
-	/* OK if we found at least one separator */
-	return saw_sep;
+	if (partcnt)
+		*partcnt = parts;
+	return true;
+}
+
+/*
+ * Decide whether a proposed custom variable name is allowed.
+ *
+ * It must be two or more identifiers separated by dots, where the rules
+ * for what is an identifier agree with scan.l.  (If you change this rule,
+ * adjust the errdetail in find_option().)
+ */
+static bool
+valid_custom_variable_name(const char *name)
+{
+	int			partcnt;
+
+	return (valid_variable_name(name, &partcnt) && partcnt > 1);
 }
 
 /*
@@ -8533,16 +8557,32 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 	char		AutoConfFileName[MAXPGPATH];
 	char		AutoConfTmpFileName[MAXPGPATH];
 
-	if (!superuser())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to execute ALTER SYSTEM command")));
-
 	/*
 	 * Extract statement arguments
 	 */
 	name = altersysstmt->setstmt->name;
 
+	/*
+	 * Check permission to run ALTER SYSTEM on the target variable registered.
+	 */
+	if (!superuser())
+	{
+		AclResult	aclresult;
+		Oid			cfgId;
+
+		cfgId = get_cfgparam_oid(name, true);
+		if (!OidIsValid(cfgId))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied to set parameter \"%s\"",
+							name)));
+
+		aclresult = pg_config_param_aclcheck(cfgId, GetUserId(),
+											 ACL_ALTER_SYSTEM);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, OBJECT_CONFIG_PARAM, name);
+	}
+
 	switch (altersysstmt->setstmt->kind)
 	{
 		case VAR_SET_VALUE:
@@ -8735,6 +8775,9 @@ void
 ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 {
 	GucAction	action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET;
+	GucContext	context;
+	AclResult	aclresult;
+	Oid			cfgId;
 
 	/*
 	 * Workers synchronize these parameters at the start of the parallel
@@ -8745,6 +8788,20 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
 				 errmsg("cannot set parameters during a parallel operation")));
 
+	/*
+	 * Superusers and users who have been granted SET privilege can set with
+	 * PGC_SUSET context.  All others have only PGC_USERSET.
+	 */
+	context = PGC_USERSET;
+	if (superuser())
+		context = PGC_SUSET;
+	else if (OidIsValid(cfgId = get_cfgparam_oid(stmt->name, true)))
+	{
+		aclresult = pg_config_param_aclcheck(cfgId, GetUserId(), ACL_SET);
+		if (aclresult == ACLCHECK_OK)
+			context = PGC_SUSET;
+	}
+
 	switch (stmt->kind)
 	{
 		case VAR_SET_VALUE:
@@ -8753,7 +8810,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 				WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
 			(void) set_config_option(stmt->name,
 									 ExtractSetVariableArgs(stmt),
-									 (superuser() ? PGC_SUSET : PGC_USERSET),
+									 context,
 									 PGC_S_SESSION,
 									 action, true, 0, false);
 			break;
@@ -8838,7 +8895,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 
 			(void) set_config_option(stmt->name,
 									 NULL,
-									 (superuser() ? PGC_SUSET : PGC_USERSET),
+									 context,
 									 PGC_S_SESSION,
 									 action, true, 0, false);
 			break;
@@ -9578,6 +9635,22 @@ get_explain_guc_options(int *num)
 	return result;
 }
 
+/*
+ * Return GUC variable canonical name, or NULL if no variable by the given
+ * name or alias exists.
+ */
+const char *
+GetConfigOptionCanonicalName(const char *alias)
+{
+	struct config_generic *record;
+
+	record = find_option(alias, false, true, LOG);
+	if (record == NULL)
+		return NULL;
+
+	return record->name;
+}
+
 /*
  * Return GUC variable value by name; optionally return canonical form of
  * name.  If the GUC is unset, then throw an error unless missing_ok is true,
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index ea67e52a3f..07298198e3 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -37,7 +37,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
  *	nspname: the namespace the object is in (NULL if none); not pre-quoted
  *	type: the object type (as seen in GRANT command: must be one of
  *		TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
- *		FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
+ *		FOREIGN DATA WRAPPER, SERVER, CONFIGURATION PARAMETER or LARGE OBJECT)
  *	acls: the ACL string fetched from the database
  *	racls: the ACL string of any initial-but-now-revoked privileges
  *	owner: username of object owner (will be passed through fmtId); can be
@@ -573,6 +573,11 @@ do { \
 		CONVERT_PRIV('U', "USAGE");
 	else if (strcmp(type, "FOREIGN TABLE") == 0)
 		CONVERT_PRIV('r', "SELECT");
+	else if (strcmp(type, "CONFIGURATION PARAMETER") == 0)
+	{
+		CONVERT_PRIV('s', "SET VALUE");
+		CONVERT_PRIV('A', "ALTER SYSTEM");
+	}
 	else if (strcmp(type, "LARGE OBJECT") == 0)
 	{
 		CONVERT_PRIV('r', "SELECT");
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 59f4fbb2cc..aaadac8e32 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3413,6 +3413,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
 		strcmp(type, "SCHEMA") == 0 ||
 		strcmp(type, "EVENT TRIGGER") == 0 ||
 		strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+		strcmp(type, "CONFIGURATION PARAMETER") == 0 ||
 		strcmp(type, "SERVER") == 0 ||
 		strcmp(type, "PUBLICATION") == 0 ||
 		strcmp(type, "SUBSCRIPTION") == 0 ||
@@ -3596,6 +3597,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 			strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
 			strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
 			strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
+			strcmp(te->desc, "CONFIGURATION PARAMETER") == 0 ||
 			strcmp(te->desc, "SERVER") == 0 ||
 			strcmp(te->desc, "STATISTICS") == 0 ||
 			strcmp(te->desc, "PUBLICATION") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5a2094de9f..9e79fd129f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15078,6 +15078,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_CONFIG_PARAM:
+			type = "CONFIGURATION PARAMETERS";
+			break;
 		default:
 			/* shouldn't get here */
 			fatal("unrecognized object type in default privileges: %d",
@@ -15123,7 +15126,7 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
  *		or InvalidDumpId if there is no need for a second dependency.
  * 'type' must be one of
  *		TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
- *		FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT.
+ *		FOREIGN DATA WRAPPER, SERVER, CONFIGURATION PARAMETER or LARGE OBJECT.
  * 'name' is the formatted name of the object.  Must be quoted etc. already.
  * 'subname' is the formatted name of the sub-object, if any.  Must be quoted.
  *		(Currently we assume that subname is only provided for table columns.)
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..2f398c8094 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -36,6 +36,7 @@ static void help(void);
 static void dropRoles(PGconn *conn);
 static void dumpRoles(PGconn *conn);
 static void dumpRoleMembership(PGconn *conn);
+static void dumpRoleGUCPrivs(PGconn *conn);
 static void dumpGroups(PGconn *conn);
 static void dropTablespaces(PGconn *conn);
 static void dumpTablespaces(PGconn *conn);
@@ -586,6 +587,10 @@ main(int argc, char *argv[])
 				dumpRoleMembership(conn);
 			else
 				dumpGroups(conn);
+
+			/* Dump role guc privileges */
+			if (server_version >= 150000)
+				dumpRoleGUCPrivs(conn);
 		}
 
 		/* Dump tablespaces */
@@ -1049,6 +1054,72 @@ dumpRoleMembership(PGconn *conn)
 	fprintf(OPF, "\n\n");
 }
 
+/*
+ * Dump role configuration parameter privileges.  This code is used for 15.0
+ * and later servers.
+ *
+ * Note: we expect dumpRoles already created all the roles, but there are
+ * no per-role configuration parameter privileges yet..
+ */
+static void
+dumpRoleGUCPrivs(PGconn *conn)
+{
+	PQExpBuffer buf = createPQExpBuffer();
+	PGresult   *res;
+	int			i;
+
+	printfPQExpBuffer(buf, "SELECT string_agg(acl.privilege_type, ', ' ORDER BY acl.privilege_type), "
+					  "cfg.configname, "
+					  "grantee.rolname AS grantee, "
+					  "acl.is_grantable, "
+					  "grantor.rolname AS grantor "
+					  "FROM pg_catalog.pg_config_param cfg, "
+					  "LATERAL (SELECT * FROM aclexplode(cfg.cfgacl)) acl "
+					  "JOIN pg_catalog.pg_authid grantee "
+					  "ON acl.grantee = grantee.oid "
+					  "LEFT JOIN pg_catalog.pg_authid grantor ON "
+					  "acl.grantor = grantor.oid "
+					  "WHERE acl.grantee > 0 "
+					  "GROUP BY configname, grantee.rolname, is_grantable, grantor.rolname"
+					  );
+
+	res = executeQuery(conn, buf->data);
+
+	if (PQntuples(res) > 0)
+		fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n");
+
+	for (i = 0; i < PQntuples(res); i++)
+	{
+		char	   *privilege = PQgetvalue(res, i, 0);
+		char	   *cfgname = PQgetvalue(res, i, 1);
+		char	   *grantee = PQgetvalue(res, i, 2);
+		char	   *grantable = PQgetvalue(res, i, 3);
+
+		fprintf(OPF, "GRANT %s", privilege);
+		fprintf(OPF, " ON %s", cfgname);
+		fprintf(OPF, " TO %s", fmtId(grantee));
+		if (*grantable == 't')
+			fprintf(OPF, " WITH GRANT OPTION");
+
+		/*
+		 * We don't track the grantor very carefully in the backend, so cope
+		 * with the possibility that it has been dropped.
+		 */
+		if (!PQgetisnull(res, i, 4))
+		{
+			char	   *grantor = PQgetvalue(res, i, 4);
+
+			fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
+		}
+		fprintf(OPF, ";\n");
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(buf);
+
+	fprintf(OPF, "\n\n");
+}
+
 /*
  * Dump group memberships from a pre-8.1 server.  It's annoying that we
  * can't share any useful amount of code with the post-8.1 case, but
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3eca295ff4..ab92782d15 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -92,6 +92,7 @@ typedef enum ObjectClass
 	OCLASS_TYPE,				/* pg_type */
 	OCLASS_CAST,				/* pg_cast */
 	OCLASS_COLLATION,			/* pg_collation */
+	OCLASS_CONFIG_PARAM,		/* pg_config_param */
 	OCLASS_CONSTRAINT,			/* pg_constraint */
 	OCLASS_CONVERSION,			/* pg_conversion */
 	OCLASS_DEFAULT,				/* pg_attrdef */
diff --git a/src/include/catalog/pg_config_param.h b/src/include/catalog/pg_config_param.h
new file mode 100644
index 0000000000..6b92a0150e
--- /dev/null
+++ b/src/include/catalog/pg_config_param.h
@@ -0,0 +1,63 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_config_param.h
+ *	  definition of the "configuration parameter" system catalog
+ *	  (pg_config_param).
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_config_param.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_CONFIG_PARAM_H
+#define PG_CONFIG_PARAM_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_config_param_d.h"
+
+/* ----------------
+ *		pg_config_param definition.  cpp turns this into
+ *		typedef struct FormData_pg_config_param
+ * ----------------
+ */
+CATALOG(pg_config_param,8924,ConfigParamRelationId) BKI_SHARED_RELATION
+{
+	Oid			oid;			/* oid */
+	/*
+
+	 * Variable-length fields start here, but we allow direct access to
+	 * configname.
+	 */
+	text		configname BKI_FORCE_NOT_NULL;
+
+#ifdef CATALOG_VARLEN
+	/* Access privileges */
+	aclitem		cfgacl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_config_param;
+
+
+/* ----------------
+ *		Form_pg_config_param corresponds to a pointer to a tuple with
+ *		the format of pg_config_param relation.
+ * ----------------
+ */
+typedef FormData_pg_config_param *Form_pg_config_param;
+
+DECLARE_TOAST(pg_config_param, 8925, 8926);
+#define PgConfigParamToastTable 8925
+#define PgConfigParamToastIndex 8926
+
+DECLARE_UNIQUE_INDEX(pg_cfgparam_name_index, 8927, ConfigParamNameIndexId, on pg_config_param using btree(configname text_ops));
+DECLARE_UNIQUE_INDEX_PKEY(pg_config_param_oid_index, 8928, ConfigParamOidIndexId, on pg_config_param using btree(oid oid_ops));
+
+extern Oid	ConfigParamCreate(const char *configname, bool if_not_exists);
+
+#endif							/* PG_CONFIG_PARAM_H */
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index eb72dd6293..4ca71b2258 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -66,6 +66,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, DefaultAclOidIndexId, o
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_CONFIG_PARAM	'c' /* configuration parameter */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 067138e6b5..f717c6d024 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_SET			(1<<12) /* for configuration parameters */
+#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
@@ -1794,6 +1796,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_CONFIG_PARAM,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..527e723b39 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,7 @@ PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 9ba24d4ca9..203b916fec 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -86,6 +86,7 @@ PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, fals
 PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_CONFIG_PARAM, "CREATE CONFIGURATION PARAMETER", false, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
@@ -138,6 +139,7 @@ PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_CONFIG_PARAM, "DROP CONFIGURATION PARAMETER", false, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index af771c901d..afc36ab257 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_SET_CHR				's'
+#define ACL_ALTER_SYSTEM_CHR	'A'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
@@ -165,6 +167,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_CONFIG_PARAM	(ACL_SET|ACL_ALTER_SYSTEM)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -223,6 +226,8 @@ extern void select_best_grantor(Oid roleId, AclMode privileges,
 
 extern void initialize_acl(void);
 
+extern Oid	get_cfgparam_oid(const char *cfgname, bool missing_ok);
+
 /*
  * prototypes for functions in aclchk.c
  */
@@ -243,6 +248,8 @@ extern AclMode pg_class_aclmask_ext(Oid table_oid, Oid roleid,
 									bool *is_missing);
 extern AclMode pg_database_aclmask(Oid db_oid, Oid roleid,
 								   AclMode mask, AclMaskHow how);
+extern AclMode pg_config_param_aclmask(Oid config_oid, Oid roleid,
+								   AclMode mask, AclMaskHow how);
 extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid,
 							   AclMode mask, AclMaskHow how);
 extern AclMode pg_language_aclmask(Oid lang_oid, Oid roleid,
@@ -271,6 +278,7 @@ extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
 extern AclResult pg_class_aclcheck_ext(Oid table_oid, Oid roleid,
 									   AclMode mode, bool *is_missing);
 extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
+extern AclResult pg_config_param_aclcheck(Oid config_oid, Oid roleid, AclMode mode);
 extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);
 extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid,
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac..fb2fe193c5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -379,6 +379,8 @@ extern int	set_config_option(const char *name, const char *value,
 							  GucAction action, bool changeVal, int elevel,
 							  bool is_reload);
 extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt);
+extern bool valid_variable_name(const char *name, int *partcnt);
+extern const char *GetConfigOptionCanonicalName(const char *alias);
 extern char *GetConfigOptionByName(const char *name, const char **varname,
 								   bool missing_ok);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 77871aaefc..33c21f7102 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -188,6 +188,7 @@ extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
 extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 							 int reqkind, Oid reqop, int flags);
 extern void free_attstatsslot(AttStatsSlot *sslot);
+extern char *get_cfgparam_name(Oid configid);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index c8cfbc30f6..14d803a6dc 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -48,6 +48,8 @@ enum SysCacheIdentifier
 	CLAOID,
 	COLLNAMEENCNSP,
 	COLLOID,
+	CONFIGNAME,
+	CONFIGOID,
 	CONDEFAULT,
 	CONNAMENSP,
 	CONSTROID,
diff --git a/src/test/modules/test_pg_dump/t/001_base.pl b/src/test/modules/test_pg_dump/t/001_base.pl
index 16f7610883..7fbf2d871b 100644
--- a/src/test/modules/test_pg_dump/t/001_base.pl
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -9,7 +9,12 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
-my $tempdir       = PostgreSQL::Test::Utils::tempdir;
+# my $tempdir       = PostgreSQL::Test::Utils::tempdir;
+my $tempbase = '/tmp/test_pg_dump';
+my $subdir = 0;
+$subdir++ while (-e "$tempbase/$subdir");
+my $tempdir = "$tempbase/$subdir";
+system("mkdir $tempdir");
 
 ###############################################################
 # This structure is based off of the src/bin/pg_dump/t test
@@ -317,6 +322,53 @@ my %tests = (
 		like         => { pg_dumpall_globals => 1, },
 	},
 
+	'GRANT ALTER SYSTEM ON ignore_checksum_failure' => {
+		create_order => 2,
+		create_sql   =>
+			'GRANT ALTER SYSTEM ON ignore_checksum_failure TO regress_dump_test_role;',
+		regexp       =>
+			qr/^GRANT ALTER SYSTEM ON ignore_checksum_failure TO regress_dump_test_role GRANTED BY /m,
+		like         => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT SET VALUE ON my.missing.guc' => {
+		create_order => 2,
+		create_sql   =>
+			'GRANT SET VALUE ON my.missing.guc TO regress_dump_test_role;',
+		regexp       =>
+			qr/^GRANT SET VALUE ON my\.missing\.guc TO regress_dump_test_role GRANTED BY /m,
+		like         => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT SET VALUE, ALTER SYSTEM ON something WITH GRANT OPTION' => {
+		create_order => 2,
+		create_sql =>
+			'GRANT SET VALUE, ALTER SYSTEM ON something TO regress_dump_test_role WITH GRANT OPTION;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM, SET VALUE ON something TO regress_dump_test_role WITH GRANT OPTION GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT ALTER SYSTEM ON MyReallyLong.ButValidCustom.GucNameThatCannotFit.InNamedata64Byte.Format' => {
+		create_order => 2,
+		create_sql =>
+			# configuration parameters get cased folded
+			'GRANT ALTER SYSTEM ON MyReallyLong.ButValidCustom.GucNameThatCannotFit.InNamedata64Byte.Format TO regress_dump_test_role;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM ON myreallylong\.butvalidcustom\.gucnamethatcannotfit\.innamedata64byte\.format TO regress_dump_test_role GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT ALTER SYSTEM, SET VALUE ON my.guc TO regress_dump_test_role GRANTED BY CURRENT_ROLE' => {
+		create_order => 2,
+		create_sql =>
+			# GRANTED BY CURRENT_ROLE is allowed for SQL compatibility, but is ignored
+			'GRANT ALTER SYSTEM, SET VALUE ON my.guc TO regress_dump_test_role GRANTED BY CURRENT_ROLE;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM, SET VALUE ON my\.guc TO regress_dump_test_role GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
 	'CREATE SCHEMA public' => {
 		regexp => qr/^CREATE SCHEMA public;/m,
 		like   => {
diff --git a/src/test/regress/expected/guc_privs.out b/src/test/regress/expected/guc_privs.out
new file mode 100644
index 0000000000..ae02b3fe77
--- /dev/null
+++ b/src/test/regress/expected/guc_privs.out
@@ -0,0 +1,204 @@
+-- Test superuser
+-- Superuser DBA
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform all operations as user 'regress_admin' --
+SET SESSION AUTHORIZATION regress_admin;
+-- PGC_BACKEND
+SET ignore_system_indexes = OFF;  -- fail, cannot be set after connection start
+ERROR:  parameter "ignore_system_indexes" cannot be set after connection start
+RESET ignore_system_indexes;  -- fail, cannot be set after connection start
+ERROR:  parameter "ignore_system_indexes" cannot be set after connection start
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- ok
+ALTER SYSTEM RESET ignore_system_indexes;  -- ok
+-- PGC_INTERNAL
+SET block_size = 50;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+RESET block_size;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+ALTER SYSTEM SET block_size = 50;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+ALTER SYSTEM RESET block_size;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+-- PGC_POSTMASTER
+SET autovacuum_freeze_max_age = 1000050000;  -- fail, requires restart
+ERROR:  parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server
+RESET autovacuum_freeze_max_age;  -- fail, requires restart
+ERROR:  parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server
+ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000;  -- ok
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- ok
+ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf';  -- fail, cannot be changed
+ERROR:  parameter "config_file" cannot be changed
+ALTER SYSTEM RESET config_file;  -- fail, cannot be changed
+ERROR:  parameter "config_file" cannot be changed
+-- PGC_SIGHUP
+SET autovacuum = OFF;  -- fail, requires reload
+ERROR:  parameter "autovacuum" cannot be changed now
+RESET autovacuum;  -- fail, requires reload
+ERROR:  parameter "autovacuum" cannot be changed now
+ALTER SYSTEM SET autovacuum = OFF;  -- ok
+ALTER SYSTEM RESET autovacuum;  -- ok
+-- PGC_SUSET
+SET lc_messages = 'en_US.UTF-8';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'en_US.UTF-8';  -- ok
+ALTER SYSTEM RESET lc_messages;  -- ok
+-- PGC_SU_BACKEND
+SET jit_debugging_support = OFF;  -- fail, cannot be set after connection start
+ERROR:  parameter "jit_debugging_support" cannot be set after connection start
+RESET jit_debugging_support;  -- fail, cannot be set after connection start
+ERROR:  parameter "jit_debugging_support" cannot be set after connection start
+ALTER SYSTEM SET jit_debugging_support = OFF;  -- ok
+ALTER SYSTEM RESET jit_debugging_support;  -- ok
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- ok
+ALTER SYSTEM RESET DateStyle;  -- ok
+ALTER SYSTEM SET ssl_renegotiation_limit = 0;  -- fail, cannot be changed
+ERROR:  parameter "ssl_renegotiation_limit" cannot be changed
+ALTER SYSTEM RESET ssl_renegotiation_limit;  -- fail, cannot be changed
+ERROR:  parameter "ssl_renegotiation_limit" cannot be changed
+-- Finished testing superuser
+RESET statement_timeout;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin;
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Perform all 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
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "ignore_system_indexes"
+ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum_multixact_freeze_max_age"
+SET jit_provider = 'llvmjit';  -- fail, insufficient privileges
+ERROR:  parameter "jit_provider" cannot be changed without restarting the server
+ALTER SYSTEM SET shared_buffers = 50;  -- ok
+ALTER SYSTEM RESET shared_buffers;  -- ok
+SET autovacuum_work_mem = 50;  -- cannot be changed now
+ERROR:  parameter "autovacuum_work_mem" cannot be changed now
+ALTER SYSTEM RESET temp_file_limit;  -- ok
+SET TimeZone = 'Europe/Helsinki';  -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+RESET TimeZone;  -- ok
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for configuration parameter work_mem
+privileges for configuration parameter maintenance_work_mem
+privileges for configuration parameter autovacuum_work_mem
+privileges for configuration parameter hash_mem_multiplier
+privileges for configuration parameter logical_decoding_work_mem
+privileges for configuration parameter max_stack_depth
+privileges for configuration parameter min_dynamic_shared_memory
+privileges for configuration parameter shared_buffers
+privileges for configuration parameter temp_buffers
+privileges for configuration parameter temp_file_limit
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+FROM regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "drop owned by" instead of "revoke"
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, privileges not yet granted
+ERROR:  permission denied to set parameter "autovacuum_work_mem"
+RESET SESSION AUTHORIZATION;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for configuration parameter work_mem
+privileges for configuration parameter maintenance_work_mem
+privileges for configuration parameter autovacuum_work_mem
+privileges for configuration parameter hash_mem_multiplier
+privileges for configuration parameter logical_decoding_work_mem
+privileges for configuration parameter max_stack_depth
+privileges for configuration parameter min_dynamic_shared_memory
+privileges for configuration parameter shared_buffers
+privileges for configuration parameter temp_buffers
+privileges for configuration parameter temp_file_limit
+DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, "drop owned" has dropped privileges
+ERROR:  permission denied to set parameter "autovacuum_work_mem"
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "reassign owned by" this time
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, "reassign owned" did not change privileges
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for configuration parameter work_mem
+privileges for configuration parameter maintenance_work_mem
+privileges for configuration parameter autovacuum_work_mem
+privileges for configuration parameter hash_mem_multiplier
+privileges for configuration parameter logical_decoding_work_mem
+privileges for configuration parameter max_stack_depth
+privileges for configuration parameter min_dynamic_shared_memory
+privileges for configuration parameter shared_buffers
+privileges for configuration parameter temp_buffers
+privileges for configuration parameter temp_file_limit
+DROP ROLE regress_host_resource_newadmin;  -- ok, nothing was transferred
+-- Use "drop owned by" so we can drop the role
+DROP OWNED BY regress_host_resource_admin;  -- ok
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Create non-superuser with privileges to configure plgsql custom variables
+CREATE ROLE regress_plpgsql_admin NOSUPERUSER;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;
+SET plpgsql.extra_errors TO 'all';
+SET plpgsql.extra_warnings TO 'all';
+RESET plpgsql.extra_errors;
+RESET plpgsql.extra_warnings;
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';
+ERROR:  permission denied to set parameter "plpgsql.extra_errors"
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';
+ERROR:  permission denied to set parameter "plpgsql.extra_warnings"
+ALTER SYSTEM RESET plpgsql.extra_errors;
+ERROR:  permission denied to set parameter "plpgsql.extra_errors"
+ALTER SYSTEM RESET plpgsql.extra_warnings;
+ERROR:  permission denied to set parameter "plpgsql.extra_warnings"
+RESET SESSION AUTHORIZATION;
+GRANT SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+TO regress_plpgsql_admin;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;  -- ok
+SET plpgsql.extra_errors TO 'all';  -- ok
+SET plpgsql.extra_warnings TO 'all';  -- ok
+RESET plpgsql.extra_errors;  -- ok
+RESET plpgsql.extra_warnings;  -- ok
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';  -- ok
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';  -- ok
+ALTER SYSTEM RESET plpgsql.extra_errors;  -- ok
+ALTER SYSTEM RESET plpgsql.extra_warnings;  -- ok
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_plpgsql_admin;  -- fail, privileges remain
+ERROR:  role "regress_plpgsql_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for configuration parameter plpgsql.extra_warnings
+privileges for configuration parameter plpgsql.extra_errors
+REVOKE SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+FROM regress_plpgsql_admin;
+DROP ROLE regress_plpgsql_admin;  -- ok
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 9b91865dcc..ef4197c0f3 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -31,6 +31,34 @@ CREATE USER regress_priv_user6;
 CREATE USER regress_priv_user7;
 GRANT pg_read_all_data TO regress_priv_user6;
 GRANT pg_write_all_data TO regress_priv_user7;
+GRANT SET VALUE ON enable_memoize TO regress_priv_user6;
+GRANT SET VALUE ON enable_nestloop TO regress_priv_user6;
+SET ROLE regress_priv_user6;
+SET enable_memoize TO false;
+SET enable_nestloop TO false;
+RESET enable_memoize;
+RESET enable_nestloop;
+RESET ROLE;
+GRANT ALTER SYSTEM ON enable_seqscan TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON sort_mem TO regress_priv_user7; -- old name for "work_mem"
+NOTICE:  granting privileges on canonical configuration parameter name "work_mem"
+GRANT ALTER SYSTEM ON maintenance_work_mem TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON "" TO regress_priv_user7; -- bad name
+ERROR:  zero-length delimited identifier at or near """"
+LINE 1: GRANT ALTER SYSTEM ON "" TO regress_priv_user7;
+                              ^
+GRANT ALTER SYSTEM ON " " TO regress_priv_user7; -- bad name
+ERROR:  invalid configuration parameter name " "
+GRANT ALTER SYSTEM ON " foo " TO regress_priv_user7; -- bad name
+ERROR:  invalid configuration parameter name " foo "
+GRANT SELECT ON public.persons2 TO regress_priv_user7;
+SET ROLE regress_priv_user7;
+ALTER SYSTEM SET enable_seqscan = OFF;
+ALTER SYSTEM RESET enable_seqscan;
+RESET ROLE;
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;
 ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4;
@@ -2326,10 +2354,32 @@ DROP USER regress_priv_user2;
 DROP USER regress_priv_user3;
 DROP USER regress_priv_user4;
 DROP USER regress_priv_user5;
-DROP USER regress_priv_user6;
-DROP USER regress_priv_user7;
+DROP USER regress_priv_user6; -- privileges remain
+ERROR:  role "regress_priv_user6" cannot be dropped because some objects depend on it
+DETAIL:  privileges for configuration parameter enable_memoize
+privileges for configuration parameter enable_nestloop
+DROP USER regress_priv_user7; -- privileges remain
+ERROR:  role "regress_priv_user7" cannot be dropped because some objects depend on it
+DETAIL:  privileges for table persons2
+privileges for configuration parameter enable_seqscan
+privileges for configuration parameter work_mem
+privileges for configuration parameter maintenance_work_mem
+privileges for configuration parameter no_such_param
+privileges for configuration parameter no_such_extension.no_such_param
+privileges for configuration parameter no_such_extension.no_such_param.longer.than.maximum.namedata.length
 DROP USER regress_priv_user8; -- does not exist
 ERROR:  role "regress_priv_user8" does not exist
+REVOKE SELECT ON public.persons2 FROM regress_priv_user7;
+REVOKE ALTER SYSTEM ON enable_seqscan FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON work_mem FROM regress_priv_user7; -- ok, use new name
+REVOKE ALTER SYSTEM ON vacuum_mem FROM regress_priv_user7; -- ok, use old name
+REVOKE ALTER SYSTEM ON no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length FROM regress_priv_user7; -- ok
+DROP USER regress_priv_user7; -- ok
+REVOKE SET VALUE ON enable_memoize FROM regress_priv_user6;
+REVOKE SET VALUE ON enable_nestloop FROM regress_priv_user6;
+DROP USER regress_priv_user6; -- ok
 -- permissions with LOCK TABLE
 CREATE USER regress_locktable_user;
 CREATE TABLE lock_table (a int);
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 63706a28cc..aff31f0d24 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -113,6 +113,7 @@ pg_authid|t
 pg_cast|t
 pg_class|t
 pg_collation|t
+pg_config_param|t
 pg_constraint|t
 pg_conversion|t
 pg_database|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 017e962fed..b45a203eb6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort guc_privs
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/sql/guc_privs.sql b/src/test/regress/sql/guc_privs.sql
new file mode 100644
index 0000000000..e5dbc7ad3b
--- /dev/null
+++ b/src/test/regress/sql/guc_privs.sql
@@ -0,0 +1,142 @@
+-- Test superuser
+-- Superuser DBA
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform all operations as user 'regress_admin' --
+SET SESSION AUTHORIZATION regress_admin;
+-- PGC_BACKEND
+SET ignore_system_indexes = OFF;  -- fail, cannot be set after connection start
+RESET ignore_system_indexes;  -- fail, cannot be set after connection start
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- ok
+ALTER SYSTEM RESET ignore_system_indexes;  -- ok
+-- PGC_INTERNAL
+SET block_size = 50;  -- fail, cannot be changed
+RESET block_size;  -- fail, cannot be changed
+ALTER SYSTEM SET block_size = 50;  -- fail, cannot be changed
+ALTER SYSTEM RESET block_size;  -- fail, cannot be changed
+-- PGC_POSTMASTER
+SET autovacuum_freeze_max_age = 1000050000;  -- fail, requires restart
+RESET autovacuum_freeze_max_age;  -- fail, requires restart
+ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000;  -- ok
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- ok
+ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf';  -- fail, cannot be changed
+ALTER SYSTEM RESET config_file;  -- fail, cannot be changed
+-- PGC_SIGHUP
+SET autovacuum = OFF;  -- fail, requires reload
+RESET autovacuum;  -- fail, requires reload
+ALTER SYSTEM SET autovacuum = OFF;  -- ok
+ALTER SYSTEM RESET autovacuum;  -- ok
+-- PGC_SUSET
+SET lc_messages = 'en_US.UTF-8';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'en_US.UTF-8';  -- ok
+ALTER SYSTEM RESET lc_messages;  -- ok
+-- PGC_SU_BACKEND
+SET jit_debugging_support = OFF;  -- fail, cannot be set after connection start
+RESET jit_debugging_support;  -- fail, cannot be set after connection start
+ALTER SYSTEM SET jit_debugging_support = OFF;  -- ok
+ALTER SYSTEM RESET jit_debugging_support;  -- ok
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- ok
+ALTER SYSTEM RESET DateStyle;  -- ok
+ALTER SYSTEM SET ssl_renegotiation_limit = 0;  -- fail, cannot be changed
+ALTER SYSTEM RESET ssl_renegotiation_limit;  -- fail, cannot be changed
+-- Finished testing superuser
+RESET statement_timeout;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin;
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Perform all 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
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- fail, insufficient privileges
+ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age;  -- fail, insufficient privileges
+SET jit_provider = 'llvmjit';  -- fail, insufficient privileges
+ALTER SYSTEM SET shared_buffers = 50;  -- ok
+ALTER SYSTEM RESET shared_buffers;  -- ok
+SET autovacuum_work_mem = 50;  -- cannot be changed now
+ALTER SYSTEM RESET temp_file_limit;  -- ok
+SET TimeZone = 'Europe/Helsinki';  -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+RESET TimeZone;  -- ok
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+FROM regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "drop owned by" instead of "revoke"
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, privileges not yet granted
+RESET SESSION AUTHORIZATION;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, "drop owned" has dropped privileges
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "reassign owned by" this time
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, "reassign owned" did not change privileges
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+DROP ROLE regress_host_resource_newadmin;  -- ok, nothing was transferred
+-- Use "drop owned by" so we can drop the role
+DROP OWNED BY regress_host_resource_admin;  -- ok
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Create non-superuser with privileges to configure plgsql custom variables
+CREATE ROLE regress_plpgsql_admin NOSUPERUSER;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;
+SET plpgsql.extra_errors TO 'all';
+SET plpgsql.extra_warnings TO 'all';
+RESET plpgsql.extra_errors;
+RESET plpgsql.extra_warnings;
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';
+ALTER SYSTEM RESET plpgsql.extra_errors;
+ALTER SYSTEM RESET plpgsql.extra_warnings;
+RESET SESSION AUTHORIZATION;
+GRANT SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+TO regress_plpgsql_admin;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;  -- ok
+SET plpgsql.extra_errors TO 'all';  -- ok
+SET plpgsql.extra_warnings TO 'all';  -- ok
+RESET plpgsql.extra_errors;  -- ok
+RESET plpgsql.extra_warnings;  -- ok
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';  -- ok
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';  -- ok
+ALTER SYSTEM RESET plpgsql.extra_errors;  -- ok
+ALTER SYSTEM RESET plpgsql.extra_warnings;  -- ok
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_plpgsql_admin;  -- fail, privileges remain
+REVOKE SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+FROM regress_plpgsql_admin;
+DROP ROLE regress_plpgsql_admin;  -- ok
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 6353a1cb8c..eee18e7ba3 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -36,6 +36,33 @@ CREATE USER regress_priv_user7;
 GRANT pg_read_all_data TO regress_priv_user6;
 GRANT pg_write_all_data TO regress_priv_user7;
 
+GRANT SET VALUE ON enable_memoize TO regress_priv_user6;
+GRANT SET VALUE ON enable_nestloop TO regress_priv_user6;
+
+SET ROLE regress_priv_user6;
+SET enable_memoize TO false;
+SET enable_nestloop TO false;
+RESET enable_memoize;
+RESET enable_nestloop;
+RESET ROLE;
+
+GRANT ALTER SYSTEM ON enable_seqscan TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON sort_mem TO regress_priv_user7; -- old name for "work_mem"
+GRANT ALTER SYSTEM ON maintenance_work_mem TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON "" TO regress_priv_user7; -- bad name
+GRANT ALTER SYSTEM ON " " TO regress_priv_user7; -- bad name
+GRANT ALTER SYSTEM ON " foo " TO regress_priv_user7; -- bad name
+
+GRANT SELECT ON public.persons2 TO regress_priv_user7;
+
+SET ROLE regress_priv_user7;
+ALTER SYSTEM SET enable_seqscan = OFF;
+ALTER SYSTEM RESET enable_seqscan;
+RESET ROLE;
+
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;
 
@@ -1389,10 +1416,23 @@ DROP USER regress_priv_user2;
 DROP USER regress_priv_user3;
 DROP USER regress_priv_user4;
 DROP USER regress_priv_user5;
-DROP USER regress_priv_user6;
-DROP USER regress_priv_user7;
+DROP USER regress_priv_user6; -- privileges remain
+DROP USER regress_priv_user7; -- privileges remain
 DROP USER regress_priv_user8; -- does not exist
 
+REVOKE SELECT ON public.persons2 FROM regress_priv_user7;
+REVOKE ALTER SYSTEM ON enable_seqscan FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON work_mem FROM regress_priv_user7; -- ok, use new name
+REVOKE ALTER SYSTEM ON vacuum_mem FROM regress_priv_user7; -- ok, use old name
+REVOKE ALTER SYSTEM ON no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length FROM regress_priv_user7; -- ok
+DROP USER regress_priv_user7; -- ok
+
+REVOKE SET VALUE ON enable_memoize FROM regress_priv_user6;
+REVOKE SET VALUE ON enable_nestloop FROM regress_priv_user6;
+
+DROP USER regress_priv_user6; -- ok
 
 -- permissions with LOCK TABLE
 CREATE USER regress_locktable_user;
-- 
2.21.1 (Apple Git-122.3)

