Обсуждение: Granting SET and ALTER SYSTE privileges for GUCs

Поиск
Список
Период
Сортировка

Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:
Hackers,

In the ongoing effort [1] to reduce the number of tasks which require operating under superuser privileges, this patch
extendsthe system to allow, per GUC variable, the ability to SET or ALTER SYSTEM for the variable.  A previous patch
setwas submitted [2] which created hard-coded privileged roles with the authority to manage associated hard-coded sets
ofGUC variables.  This current patch appears superior in several ways: 

- It allows much greater flexibility in how roles and GUCs are associated
- Custom GUC variables defined by extensions can be covered by this approach

and perhaps most importantly,

- It's what Andrew suggested

Granting SET privilege on a USERSET variable makes no practical difference, but for SUSET variables it does, and
grantingALTER SYSTEM is meaningful for all variables. The patch does not mandate that non-login roles be created for
this,but as a usage suggestion, one could define a non-login role and assign privileges for a set of GUCs, such as: 

  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;

and then delegate authority to manage the set of GUCs to a non-superuser by granting membership in non-login role:

  CREATE ROLE regress_admin_member IN ROLE regress_host_resource_admin;

One disadvantage of this approach is that the GUC variables are represented both in the list of C structures in guc.c
andin the new system catalog pg_config_param's .dat file.  Failure to enter a GUC in the .dat file will result in the
inabilityto grant privileges on the GUC, at least unless/until you run CREATE CONFIGURATION PARAMETER on the GUC.
(Thisis, in fact, how extension scripts deal with the issue.)  It would perhaps be better if the list of GUCs were not
duplicated,but I wasn't clever enough to find a clean way to do that without greatly expanding the patch (nor did I
completeprototyping any such thing.) 




[1] https://www.postgresql.org/message-id/flat/F9408A5A-B20B-42D2-9E7F-49CD3D1547BC%40enterprisedb.com
[2] https://www.postgresql.org/message-id/flat/0BE45772-31CF-4BE9-9FE8-FB7D8B130240%40enterprisedb.com

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Mon, Nov 15, 2021 at 3:37 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> One disadvantage of this approach is that the GUC variables are represented both in the list of C structures in guc.c
andin the new system catalog pg_config_param's .dat file.  Failure to enter a GUC in the .dat file will result in the
inabilityto grant privileges on the GUC, at least unless/until you run CREATE CONFIGURATION PARAMETER on the GUC.
(Thisis, in fact, how extension scripts deal with the issue.)  It would perhaps be better if the list of GUCs were not
duplicated,but I wasn't clever enough to find a clean way to do that without greatly expanding the patch (nor did I
completeprototyping any such thing.) 

I think this imposes an unnecessary burden on developers. It seems
like it would be easy to write some code that lives inside guc.c and
runs during bootstrap, and it could just iterate over each
ConfigureNamesWhatever array and insert catalog entries for everything
it finds.

It's also going to be important to think about what happens with
extension GUCs. If somebody installs an extension, we can't ask them
to perform a manual step in order to be able to grant privileges. And
if somebody then loads up a different .so for that extension, the set
of GUCs that it provides can change without any DDL being executed.
New GUCs could appear, and old GUCs could vanish.

So I wonder if we should instead not do what I proposed above, and
instead just adjust the GRANT command to automatically insert a new
row into the relevant catalog if there isn't one already. That seems
nicer for extensions, and also nicer for core GUCs, since it avoids
bloating the catalog with a bunch of entries that aren't needed.

--
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> It's also going to be important to think about what happens with
> extension GUCs. If somebody installs an extension, we can't ask them
> to perform a manual step in order to be able to grant privileges. And
> if somebody then loads up a different .so for that extension, the set
> of GUCs that it provides can change without any DDL being executed.
> New GUCs could appear, and old GUCs could vanish.

Right.  I think that any design that involves per-GUC catalog entries
is going to be an abysmal failure, precisely because the set of GUCs
is not stable enough.  So I'm suspicious of this entire proposal.
Maybe there's a way to make it work, but that way isn't how.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Tue, Nov 16, 2021 at 10:17 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Right.  I think that any design that involves per-GUC catalog entries
> is going to be an abysmal failure, precisely because the set of GUCs
> is not stable enough.

In practice it's pretty stable. I think it's just a matter of having a
plan that covers the cases where it isn't stable reasonably elegantly.

We already embed GUC names in catalog entries when someone runs ALTER
USER SET or ALTER DATABASE SET, so this proposal doesn't seem to be
moving the goalposts in any meaningful way.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 7:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>
> It's also going to be important to think about what happens with
> extension GUCs. If somebody installs an extension, we can't ask them
> to perform a manual step in order to be able to grant privileges.

The burden isn't on the installer of an extension.  As implemented, it's the extension's installation .sql file that
setsit up, and the upgrade .sql files that make adjustments, if necessary. 

> And
> if somebody then loads up a different .so for that extension, the set
> of GUCs that it provides can change without any DDL being executed.
> New GUCs could appear, and old GUCs could vanish.

Well, the same is true for functions, right?  If you add, remove, or redefine functions in the extension, you need an
upgradescript that defines the new functions, removes the old ones, changes function signatures, or whatever.  The same
istrue here for GUCs. 

I don't think we support using a .so that is mismatched against the version of the extension that is installed.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Tue, Nov 16, 2021 at 10:17 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Right.  I think that any design that involves per-GUC catalog entries
>> is going to be an abysmal failure, precisely because the set of GUCs
>> is not stable enough.

> In practice it's pretty stable. I think it's just a matter of having a
> plan that covers the cases where it isn't stable reasonably elegantly.

Um.  Really the point comes down to having sane default behavior
when there's no entry, which ought to eliminate any need to do the
sort of "run over all the entries at startup" processing that you
seemed to be proposing.  So I guess I don't understand why such a
thing would be needed.

> We already embed GUC names in catalog entries when someone runs ALTER
> USER SET or ALTER DATABASE SET, so this proposal doesn't seem to be
> moving the goalposts in any meaningful way.

True; as long as the expectation is that entries will exist for only
a tiny subset of GUCs, it's probably fine.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> I don't think we support using a .so that is mismatched against the
> version of the extension that is installed.

You are entirely mistaken.  That's not only "supported", it's *required*.
Consider binary upgrades, where the SQL objects are transferred as-is
but the receiving installation may have a different (hopefully newer)
version of the .so.  That .so has to cope with the older SQL object
definitions; it doesn't get to assume that the upgrade script has been
run yet.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 7:32 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> I don't think we support using a .so that is mismatched against the
>> version of the extension that is installed.
>
> You are entirely mistaken.  That's not only "supported", it's *required*.
> Consider binary upgrades, where the SQL objects are transferred as-is
> but the receiving installation may have a different (hopefully newer)
> version of the .so.  That .so has to cope with the older SQL object
> definitions; it doesn't get to assume that the upgrade script has been
> run yet.

You are talking about mismatches in the other direction, aren't you?  I was responding to Robert's point that new gucs
couldappear, and old gucs disappear.  That seems analogous to new functions appearing and old functions disappearing.
Ifyou upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be callable/grantable
fromsql until the upgrade script runs.  The old gucs and functions will be missing from the .so, and attempts to call
them/grantthem from sql before the upgrade will fail.  What am I missing here? 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 7:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> True; as long as the expectation is that entries will exist for only
> a tiny subset of GUCs, it's probably fine.

I understand that bloating a frequently used catalog can be pretty harmful to performance.  I wasn't aware that the
sizeof an infrequently used catalog was critical.  This new catalog would be used during GRANT SET ... and GRANT ALTER
SYSTEMcommands, which should be rare, and potentially consulted when SET or ALTER SYSTEM commands are issued.  Is there
amore substantial performance impact to this than I'm aware?  It can be a bit challenging to run performance tests on
suchthings, given the way everything interacts with everything else.  

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Tue, Nov 16, 2021 at 10:45 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> You are talking about mismatches in the other direction, aren't you?  I was responding to Robert's point that new
gucscould appear, and old gucs disappear.  That seems analogous to new functions appearing and old functions
disappearing. If you upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be
callable/grantablefrom sql until the upgrade script runs.  The old gucs and functions will be missing from the .so, and
attemptsto call them/grant them from sql before the upgrade will fail.  What am I missing here? 

It's true that we could impose such a restriction, but I don't think
we should. If I install a different .so, I want the new GUCs to be
grantable immediately, without running any separate DDL.

I also don't think we should burden extension authors with putting
stuff in their upgrade scripts for this. We should solve the problem
in our code rather than forcing them to do so in theirs.

--
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> You are talking about mismatches in the other direction, aren't you?  I was responding to Robert's point that new
gucscould appear, and old gucs disappear.  That seems analogous to new functions appearing and old functions
disappearing. If you upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be
callable/grantablefrom sql until the upgrade script runs. 

True, but in general users might not care about getting access to new
features (or at least, they might not care until much later, at which
point they'd bother to run ALTER EXTENSION UPDATE).

> The old gucs and functions will be missing from the .so, and attempts to call them/grant them from sql before the
upgradewill fail.  What am I missing here? 

Here, you are describing behavior that's against project policy and would
be rejected immediately if anyone submitted a patch that made an extension
do that.  Newer .so versions are expected to run successfully, not fail,
with an older version of their SQL objects.  Were this not so, it's almost
inevitable that a binary upgrade would fail, because the extension is
likely to be called into action somewhere before there is any opportunity
to issue ALTER EXTENSION UPDATE.  Even in-place updates of extensions
would become problematic: you can't assume that a rollout of new
executables will be instantly followed by ALTER EXTENSION UPDATE.

Basically, I think the proposed new system catalog is both unworkable
and entirely unnecessary.  Maybe it would have been okay if Peter had
designed GUCs like that a couple of decades ago, but at this point
we have a ton of infrastructure --- much of it not under the core
project's control --- that assumes that GUCs can be created with just
a bit of code.  I do not think that this feature is worth the cost of
changing that.  Or IOW: ALTER SYSTEM SET has gotten along fine without
such a catalog; why can't this feature?

I also notice that the patch seems to intend to introduce tracking
of which user "owns" a GUC, which I think is even more unworkable.
They are all going to wind up owned by the bootstrap superuser
(extension GUCs too), so why bother?

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> On Nov 16, 2021, at 7:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> True; as long as the expectation is that entries will exist for only
>> a tiny subset of GUCs, it's probably fine.

> I understand that bloating a frequently used catalog can be pretty
> harmful to performance.  I wasn't aware that the size of an infrequently
> used catalog was critical.

My concern is not about performance, it's about the difficulty of
maintaining a catalog that expects to be a more-or-less exhaustive
list of GUCs.  I think you need to get rid of that expectation.
In the analogy to ALTER DATABASE/USER SET, we don't expect that
pg_db_role_setting catalog entries will exist for all, or even
very many, GUCs.  Also, the fact that pg_db_role_setting entries
aren't tied very hard to the actual existence of a GUC is a good
thing from the maintenance and upgrade standpoint.

BTW, if we did create such a catalog, there would need to be
pg_dump and pg_upgrade support for its entries, and you'd have to
think about (e.g.) whether pg_upgrade would attempt to preserve
the same OIDs.  I don't see any indication that the patch has
addressed that infrastructure ... which is probably just as well,
since it's work that I'd be wanting to reject.  (Hm, but actually,
doesn't pg_dump need work anyway to dump this new type of GRANT?)

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 8:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> My concern is not about performance, it's about the difficulty of
> maintaining a catalog that expects to be a more-or-less exhaustive
> list of GUCs.  I think you need to get rid of that expectation.

I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response to
GRANTcommands.  That also handles the issues of extension upgrades, which I think the old patch handled better than the
discussionon this thread suggested, but no matter.  The new behavior will allow granting privileges on non-existent
gucs,just as ALTER ROLE..SET allows registering settings on non-existent gucs. 

The reason I had resisted allowing grants of privileges on non-existent gucs is that you can get the following sort of
behavior(note the last two lines): 

  DROP USER regress_priv_user7;
  ERROR:  role "regress_priv_user7" cannot be dropped because some objects depend on it
  DETAIL:  privileges for table persons2
  privileges for configuration parameter sort_mem
  privileges for configuration parameter no_such_param

Rejecting "no_such_param" in the original GRANT statement seemed cleaner to me, but this discussion suggests pretty
stronglythat I can't do it that way.  Changing the historical "sort_mem" to the canonical "work_mem" name also seems
betterto me, as otherwise you could have different grants on the same GUC under different names.  I'm inclined to keep
thecanonicalization of names where known, but maybe that runs afoul the rule that these grants should not be tied very
hardto the GUC? 

> In the analogy to ALTER DATABASE/USER SET, we don't expect that
> pg_db_role_setting catalog entries will exist for all, or even
> very many, GUCs.  Also, the fact that pg_db_role_setting entries
> aren't tied very hard to the actual existence of a GUC is a good
> thing from the maintenance and upgrade standpoint.

Doing it that way....

> BTW, if we did create such a catalog, there would need to be
> pg_dump and pg_upgrade support for its entries, and you'd have to
> think about (e.g.) whether pg_upgrade would attempt to preserve
> the same OIDs.  I don't see any indication that the patch has
> addressed that infrastructure ... which is probably just as well,
> since it's work that I'd be wanting to reject.

Yeah, that's why I didn't write it.  I wanted feedback on the basic implementation before doing that work.

>  (Hm, but actually,
> doesn't pg_dump need work anyway to dump this new type of GRANT?)

Yes, if the idea of this kind of grant isn't being outright rejected, then I'll need to write that.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/16/21 14:48, Mark Dilger wrote:
>
>> On Nov 16, 2021, at 8:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>
>> My concern is not about performance, it's about the difficulty of
>> maintaining a catalog that expects to be a more-or-less exhaustive
>> list of GUCs.  I think you need to get rid of that expectation.
> I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response
toGRANT commands.  That also handles the issues of extension upgrades, which I think the old patch handled better than
thediscussion on this thread suggested, but no matter.  The new behavior will allow granting privileges on non-existent
gucs,just as ALTER ROLE..SET allows registering settings on non-existent gucs.
 



Your original and fairly simple set of patches used hardcoded role names
and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
privately that a more flexible approach would be to drive this from a
catalog table. I had in mind a table of more or less <roleid, guc_name>.
You could prepopulate it with the roles / GUCs from your original patch
set. I don't think it needs to be initially empty. But DBAs would be
able to modify and extend the settings. I agree with Tom that we
shouldn't try to cover all GUCs in the table - any GUC without an entry
can only be updated by a superuser.


To support pg_dump and pg_upgrade, it might be better to have an
enabled/disabled flag rather than to delete rows.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 12:38 PM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
> Your original and fairly simple set of patches used hardcoded role names
> and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
> privately that a more flexible approach would be to drive this from a
> catalog table. I had in mind a table of more or less <roleid, guc_name>.

Right.  I prefer a table of <guc_name, acl> matching the structure of most of the rest of the grantable objects, so
thatentries from pg_depend or pg_shdepend can track the dependencies in the usual way and prevent dangling references
whenDROP ROLE is executed.  Is there a reason to prefer an ad hoc tables structure? 

> You could prepopulate it with the roles / GUCs from your original patch
> set. I don't think it needs to be initially empty. But DBAs would be
> able to modify and extend the settings. I agree with Tom that we
> shouldn't try to cover all GUCs in the table - any GUC without an entry
> can only be updated by a superuser.

The patch already posted on this thread already works that way.  Robert and Tom seemed to infer that all gucs need to
bepresent in the catalog, but in fact, not entering them in the catalog simply means that they aren't grantable.  I
thinkthe confusion arose from the fact that I created a mechanism for extension authors to enter additional gucs into
thecatalog, which gave the false impression that such entries were required.  They are not.  I didn't bother explaining
thatbefore, because Robert and Tom both seem to expect all gucs to be grantable, an expectation you do not appear to
share.

> To support pg_dump and pg_upgrade, it might be better to have an
> enabled/disabled flag rather than to delete rows.

I'm not really sure what this means.  Are you suggesting that the <guc_name, acl> (or in your formulation <roleid,
guc_name>)should have a "bool enabled" field, and when the guc gets dropped, the "enabled" field gets set to false? 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> The patch already posted on this thread already works that way.  Robert and Tom seemed to infer that all gucs need to
bepresent in the catalog, but in fact, not entering them in the catalog simply means that they aren't grantable.  I
thinkthe confusion arose from the fact that I created a mechanism for extension authors to enter additional gucs into
thecatalog, which gave the false impression that such entries were required.  They are not.  I didn't bother explaining
thatbefore, because Robert and Tom both seem to expect all gucs to be grantable, an expectation you do not appear to
share.

The question is why you need pg_config_param at all, then.
AFAICS it just adds maintenance complexity we could do without.
I think we'd be better off with a catalog modeled on the design of
pg_db_role_setting, which would have entries for roles and lists
of GUC names that those roles could set.

BTW, another objection to pg_config_param as designed here is that
a "name" is not an appropriate way to store possibly-qualified
custom GUC names.  It's not long enough (cf. valid_custom_variable_name).

>> To support pg_dump and pg_upgrade, it might be better to have an
>> enabled/disabled flag rather than to delete rows.

> I'm not really sure what this means.

I didn't see the point of this either.  We really need to KISS here.
Every bit of added complexity in the catalog representation is another
opportunity for bugs-of-omission, not to mention a detail that you
have to provide mechanisms to dump and restore.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 2:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> The question is why you need pg_config_param at all, then.
> AFAICS it just adds maintenance complexity we could do without.
> I think we'd be better off with a catalog modeled on the design of
> pg_db_role_setting, which would have entries for roles and lists
> of GUC names that those roles could set.

Originally, I was trying to have dependency linkage between two proper types of objects, so that DROP ROLE and DROP
CONFIGURATIONPARAMETER would behave as expected, complaining about privileges remaining rather than dropping an object
andleaving a dangling reference. 

I've deleted the whole CREATE CONFIGURATION PARAMETER and DROP CONFIGURATION PARAMETER syntax and implementation, but
itstill seems odd to me that: 

  CREATE ROLE somebody;
  GRANT SELECT ON TABLE sometable TO ROLE somebody;
  GRANT ALTER SYSTEM ON someguc TO ROLE somebody;
  DROP ROLE somebody;
  ERROR:  role "somebody" cannot be dropped because some objects depend on it
  DETAIL:  privileges for table sometable

would not mention privileges for "someguc" as well.  That's why I want configuration parameters to be proper objects
withOIDs and with entries in pg_depend and/or pg_shdepend.  Maybe there is some better way to do it, but that's why
I'vebeen doing this. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 2:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> BTW, another objection to pg_config_param as designed here is that
> a "name" is not an appropriate way to store possibly-qualified
> custom GUC names.  It's not long enough (cf. valid_custom_variable_name).

I was aware of that, but figured not all GUCs have to be grantable.  If it doesn't fit in a NameData, you can't grant
onit. 

If we want to be more accommodating than that, we can store it as text, just like pg_db_role_names does, but then we
needmore code complexity to look it up and to verify that it is unique.  (We wouldn't want multiple records for the
same<role,guc> pair.)  

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Tue, Nov 16, 2021 at 2:48 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response
toGRANT commands.  That also handles the issues of extension upgrades, which I think the old patch handled better than
thediscussion on this thread suggested, but no matter.  The new behavior will allow granting privileges on non-existent
gucs,just as ALTER ROLE..SET allows registering settings on non-existent gucs. 
>
> The reason I had resisted allowing grants of privileges on non-existent gucs is that you can get the following sort
ofbehavior (note the last two lines): 
>
>   DROP USER regress_priv_user7;
>   ERROR:  role "regress_priv_user7" cannot be dropped because some objects depend on it
>   DETAIL:  privileges for table persons2
>   privileges for configuration parameter sort_mem
>   privileges for configuration parameter no_such_param
>
> Rejecting "no_such_param" in the original GRANT statement seemed cleaner to me, but this discussion suggests pretty
stronglythat I can't do it that way. 

I think it's perfectly fine to refuse a GRANT statement on a GUC that
doesn't exist, and I don't believe I ever said otherwise. What I don't
think is OK is to require a preparatory statement like CREATE
CONFIGURATION PARAMETER to be executed before you can grant
permissions on it. There's no reason for that. If somebody tries to
grant privileges on a GUC that does not exist, fail. If the GUC does
exist but there's no catalog entry, make one. If the GUC exists and
the catalog entry also exists, update it.

At REVOKE time, don't check whether the GUC exists - only check the
catalog. That way people can remove privileges on GUCs that don't
exist any more. If somebody issues a REVOKE and there's no catalog
entry, do nothing. If somebody issues a REVOKE and there is a catalog
entry, remove stuff from it; but if that would leave it completely
empty, instead delete it.

Whenever you create a catalog entry, also add dependency entries
pointing to it. When you delete one, remove those entries.

> Changing the historical "sort_mem" to the canonical "work_mem" name also seems better to me, as otherwise you could
havedifferent grants on the same GUC under different names.  I'm inclined to keep the canonicalization of names where
known,but maybe that runs afoul the rule that these grants should not be tied very hard to the GUC? 

No. If somebody grants privileges on an old name, record the grant
under the canonical name.

--
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Tue, Nov 16, 2021 at 3:38 PM Andrew Dunstan <andrew@dunslane.net> wrote:
> Your original and fairly simple set of patches used hardcoded role names
> and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
> privately that a more flexible approach would be to drive this from a
> catalog table. I had in mind a table of more or less <roleid, guc_name>.
> You could prepopulate it with the roles / GUCs from your original patch
> set. I don't think it needs to be initially empty. But DBAs would be
> able to modify and extend the settings. I agree with Tom that we
> shouldn't try to cover all GUCs in the table - any GUC without an entry
> can only be updated by a superuser.

I simply can't understand the point of this. You're basically
proposing that somebody has to execute one SQL statement to make a GUC
grantable, and then a second SQL statement to actually grant access to
it. What is the value in that? It is the same person doing both
things, and the system can work out automatically what needs to be
done.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Tue, Nov 16, 2021 at 5:45 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> I was aware of that, but figured not all GUCs have to be grantable.  If it doesn't fit in a NameData, you can't grant
onit. 

Such restrictions are rather counterintuitive for users, and here it
doesn't even buy anything. Using 'text' rather than 'name' as the data
type isn't going to cost any meaningful amount of performance.

> If we want to be more accommodating than that, we can store it as text, just like pg_db_role_names does, but then we
needmore code complexity to look it up and to verify that it is unique.  (We wouldn't want multiple records for the
same<role,guc> pair.) 

If you're verifying that it's unique in any way other than using a
unique index, I think you're doing it wrong.

Also, maybe I'm confused here, but why isn't the schema:

gucoid
gucname
gucacl

IOW, I don't understand why this table has <role,guc> as the primary
key rather than just guc. Everywhere else, we have a single ACL array
for the all privileges on an object. Why wouldn't we do the same thing
here?

--
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/17/21 08:32, Robert Haas wrote:
> On Tue, Nov 16, 2021 at 5:45 PM Mark Dilger
> <mark.dilger@enterprisedb.com> wrote:
>> I was aware of that, but figured not all GUCs have to be grantable.  If it doesn't fit in a NameData, you can't
granton it.
 
> Such restrictions are rather counterintuitive for users, and here it
> doesn't even buy anything. Using 'text' rather than 'name' as the data
> type isn't going to cost any meaningful amount of performance.


indeed


>> If we want to be more accommodating than that, we can store it as text, just like pg_db_role_names does, but then we
needmore code complexity to look it up and to verify that it is unique.  (We wouldn't want multiple records for the
same<role,guc> pair.)
 
> If you're verifying that it's unique in any way other than using a
> unique index, I think you're doing it wrong.


yeah


>
> Also, maybe I'm confused here, but why isn't the schema:
>
> gucoid
> gucname
> gucacl
>
> IOW, I don't understand why this table has <role,guc> as the primary
> key rather than just guc. Everywhere else, we have a single ACL array
> for the all privileges on an object. Why wouldn't we do the same thing
> here?
>


Yes, that should work, It seems like a better scheme.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/16/21 17:12, Tom Lane wrote:
>
>>> To support pg_dump and pg_upgrade, it might be better to have an
>>> enabled/disabled flag rather than to delete rows.
>> I'm not really sure what this means.
> I didn't see the point of this either.  We really need to KISS here.
> Every bit of added complexity in the catalog representation is another
> opportunity for bugs-of-omission, not to mention a detail that you
> have to provide mechanisms to dump and restore.
>
>             


Well, I was trying (perhaps not very well) to imagine how to deal with
someone modifying the permissions of one of the predefined roles. Say
pg_foo has initial permission to set bar and baz, and the DBA removes
permission to set baz. How is pg_dump going to emit the right commands
to allow a safe pg_upgrade? Maybe we should say that the permissions for
the predefined roles are immutable, so only permissions sets for user
defined roles are mutable.


cheers


andrew

-- 

Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 17, 2021, at 5:32 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>
>> I was aware of that, but figured not all GUCs have to be grantable.  If it doesn't fit in a NameData, you can't
granton it. 
>
> Such restrictions are rather counterintuitive for users, and here it
> doesn't even buy anything. Using 'text' rather than 'name' as the data
> type isn't going to cost any meaningful amount of performance.

That sounds fine.

>> If we want to be more accommodating than that, we can store it as text, just like pg_db_role_names does, but then we
needmore code complexity to look it up and to verify that it is unique.  (We wouldn't want multiple records for the
same<role,guc> pair.) 
>
> If you're verifying that it's unique in any way other than using a
> unique index, I think you're doing it wrong.

No, I'm using a unique index.  I was overthinking it, concerned about changing from name_ops to text_ops and needing
thetoast table, but that's silly, because I need one for the acl anyway. 

> Also, maybe I'm confused here, but why isn't the schema:
>
> gucoid
> gucname
> gucacl

It is, both in v2 already posted, and in the v3, written but not yet posted, as I haven't finished the pg_dump work,
andalso I'm waiting to see how this discussion gets resolved before asking for a review of v3. 

> IOW, I don't understand why this table has <role,guc> as the primary
> key rather than just guc.

I was responding to Tom's recommendation that I follow the pattern in pg_db_role_setting, and speculating how that
wouldwork.  I was not proposing to do it that way. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 17, 2021, at 6:31 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
> Well, I was trying (perhaps not very well) to imagine how to deal with
> someone modifying the permissions of one of the predefined roles. Say
> pg_foo has initial permission to set bar and baz, and the DBA removes
> permission to set baz. How is pg_dump going to emit the right commands
> to allow a safe pg_upgrade? Maybe we should say that the permissions for
> the predefined roles are immutable, so only permissions sets for user
> defined roles are mutable.

I find this somewhat amusing.  When you suggested off-list that I make gucs individually grantable rather than creating
predefinedroles with privileges, that was a great idea precisely because sites could define their own security policies
usingtheir own site-defined roles: 

  CREATE ROLE admin_type_a NOLOGIN NOSUPERUSER;
  CREATE ROLE admin_type_b NOLOGIN NOSUPERUSER;
  ...
  GRANT ALTER SYSTEM ON guc_a1, guc_a2, guc_a3, ... TO admin_type_a;
  GRANT ALTER SYSTEM ON guc_b1, guc_b2, guc_b3, ... TO admin_type_b;
  ...

That has all the power of a system based on predefined roles, but with site-specific flexibility, which is better.  So
itamuses me that we'd now be talking about granting some of these to predefined roles, as that is a regression in
flexibility. (How would a site revoke it from one of those predefined roles if they wanted a different policy?) 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Wed, Nov 17, 2021 at 9:31 AM Andrew Dunstan <andrew@dunslane.net> wrote:
> Well, I was trying (perhaps not very well) to imagine how to deal with
> someone modifying the permissions of one of the predefined roles. Say
> pg_foo has initial permission to set bar and baz, and the DBA removes
> permission to set baz. How is pg_dump going to emit the right commands
> to allow a safe pg_upgrade? Maybe we should say that the permissions for
> the predefined roles are immutable, so only permissions sets for user
> defined roles are mutable.

That's a great question, but it isn't a new problem. If I create a
brand new database and do thIs:

rhaas=# revoke execute on function pg_ls_waldir() from pg_monitor;
REVOKE

And then I do this:

[rhaas pgsql]$ pg_dump

Then the output includes this:

REVOKE ALL ON FUNCTION pg_catalog.pg_ls_waldir(OUT name text, OUT size
bigint, OUT modification timestamp with time zone) FROM pg_monitor;

I recommend looking at how that works and making this work the same way.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/17/21 11:07, Mark Dilger wrote:
>
>> On Nov 17, 2021, at 6:31 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>>
>> Well, I was trying (perhaps not very well) to imagine how to deal with
>> someone modifying the permissions of one of the predefined roles. Say
>> pg_foo has initial permission to set bar and baz, and the DBA removes
>> permission to set baz. How is pg_dump going to emit the right commands
>> to allow a safe pg_upgrade? Maybe we should say that the permissions for
>> the predefined roles are immutable, so only permissions sets for user
>> defined roles are mutable.
> I find this somewhat amusing.  When you suggested off-list that I make gucs individually grantable rather than
creatingpredefined roles with privileges, that was a great idea precisely because sites could define their own security
policiesusing their own site-defined roles:
 
>
>   CREATE ROLE admin_type_a NOLOGIN NOSUPERUSER;
>   CREATE ROLE admin_type_b NOLOGIN NOSUPERUSER;
>   ...
>   GRANT ALTER SYSTEM ON guc_a1, guc_a2, guc_a3, ... TO admin_type_a;
>   GRANT ALTER SYSTEM ON guc_b1, guc_b2, guc_b3, ... TO admin_type_b;
>   ...
>
> That has all the power of a system based on predefined roles, but with site-specific flexibility, which is better.
Soit amuses me that we'd now be talking about granting some of these to predefined roles, as that is a regression in
flexibility. (How would a site revoke it from one of those predefined roles if they wanted a different policy?)
 
>

I agree it's not ideal. At the time I suggested a more flexible approach
I hadn't really thought about the problems of upgrading. If you can come
up with something that works there then I'll be all ears.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 17, 2021, at 9:06 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
> I agree it's not ideal. At the time I suggested a more flexible approach
> I hadn't really thought about the problems of upgrading. If you can come
> up with something that works there then I'll be all ears.

Are you talking about upgrades preserving revocations of privileges on gucs from predefined roles, or merely preserving
grantsand revocation of privileges on gucs to regular roles?  I think the former problem is easily handled by not
shippingany predefined roles with such privileges.  The latter problem would seem to be a mere matter of programming,
somethingI'm working on but don't have finished.  (But maybe you see dragons ahead for me that I'm not seeing yet?) 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/17/21 12:12, Mark Dilger wrote:
>
>> On Nov 17, 2021, at 9:06 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>>
>> I agree it's not ideal. At the time I suggested a more flexible approach
>> I hadn't really thought about the problems of upgrading. If you can come
>> up with something that works there then I'll be all ears.
> Are you talking about upgrades preserving revocations of privileges on gucs from predefined roles, or merely
preservinggrants and revocation of privileges on gucs to regular roles?  I think the former problem is easily handled
bynot shipping any predefined roles with such privileges.  The latter problem would seem to be a mere matter of
programming,something I'm working on but don't have finished.  (But maybe you see dragons ahead for me that I'm not
seeingyet?)
 
>

Yes, if we don't ship with any preset privileges then the problem I was
thinking about disappears.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 16, 2021, at 7:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>
> It's also going to be important to think about what happens with
> extension GUCs. If somebody installs an extension, we can't ask them
> to perform a manual step in order to be able to grant privileges. And
> if somebody then loads up a different .so for that extension, the set
> of GUCs that it provides can change without any DDL being executed.
> New GUCs could appear, and old GUCs could vanish.

The v3 patch allows grants on unrecognized guc names.  This should allow a grant statement to precede the loading of a
new.so which provides the named guc. 

> instead just adjust the GRANT command to automatically insert a new
> row into the relevant catalog if there isn't one already. That seems
> nicer for extensions, and also nicer for core GUCs, since it avoids
> bloating the catalog with a bunch of entries that aren't needed.

Grants on GUCs create a new catalog entry if necessary, or update the existing catalog entry if found.

There is a new information_schema.guc_privileges view, not present in v2.



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Mon, Nov 22, 2021 at 7:21 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> There is a new information_schema.guc_privileges view, not present in v2.

It's my impression that information_schema is a child of the SQL
standard, and that inventions specific to PG go in pg_catalog.

Also, I think the user-facing name for GUCs is "settings".

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Nov 23, 2021, at 8:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>
> It's my impression that information_schema is a child of the SQL
> standard, and that inventions specific to PG go in pg_catalog.
>
> Also, I think the user-facing name for GUCs is "settings".

Thanks.  These issues should be fixed in v4.

Along the way, I also added has_setting_privilege() functions overlooked in v3 and before.



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 11/23/21 21:14, Mark Dilger wrote:
>
>> On Nov 23, 2021, at 8:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>>
>> It's my impression that information_schema is a child of the SQL
>> standard, and that inventions specific to PG go in pg_catalog.
>>
>> Also, I think the user-facing name for GUCs is "settings".
> Thanks.  These issues should be fixed in v4.
>
> Along the way, I also added has_setting_privilege() functions overlooked in v3 and before.



This patch had bit-rotted slightly, and I was attempting to remedy it.
However, I got a failure running the TAP tests because of this change:


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");
 


What's going on here?


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 13, 2021, at 12:56 PM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
> This patch had bit-rotted slightly, and I was attempting to remedy it.

I have that already, and getting ready to post.  Give me a few minutes and I'll repost.

> However, I got a failure running the TAP tests because of this change:
>
>
> 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");
>
>
>
> What's going on here?

Yeah, I hit that, too.  That was an accidentally committed bit of local testing.  Please ignore it for now.


—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 13, 2021, at 12:58 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> Give me a few minutes and I'll repost.

Ok, this new version is rebased, cleaned up, and has the ability to revoke SET VALUE on most settings which were
previouslyuserset, but are now suset with initdb time granted SET VALUE privilege to public. 

I have not yet altered gucs defined by contrib modules, but will repost in a few hours, or tomorrow, with those
changes.



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 13, 2021, at 1:33 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> but will repost in a few hours

... and here it is:



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Mon, Dec 13, 2021 at 5:34 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
>
>
>
> > On Dec 13, 2021, at 1:33 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
> >
> > but will repost in a few hours
>
> ... and here it is:

currently there is a failure in check-world (not sure if it's known):

diff -U3 /src/postgres/src/test/regress/expected/guc_privs.out
/src/postgres/src/test/regress/results/guc_privs.out
--- /src/postgres/src/test/regress/expected/guc_privs.out 2021-12-14
14:11:45.000000000 +0000
+++ /src/postgres/src/test/regress/results/guc_privs.out 2021-12-14
15:50:52.219583421 +0000
@@ -39,8 +39,10 @@
 ALTER SYSTEM RESET autovacuum;  -- ok
 -- PGC_SUSET
 SET lc_messages = 'en_US.UTF-8';  -- ok
+ERROR:  invalid value for parameter "lc_messages": "en_US.UTF-8"
 RESET lc_messages;  -- ok
 ALTER SYSTEM SET lc_messages = 'en_US.UTF-8';  -- ok
+ERROR:  invalid value for parameter "lc_messages": "en_US.UTF-8"
 ALTER SYSTEM RESET lc_messages;  -- ok
 -- PGC_SU_BACKEND
 SET jit_debugging_support = OFF;  -- fail, cannot be set after connection start

Aside from that I've tested this and it seems to function as
advertised and in my view is worth adding to PG.

One thing that seems like an omission to me is the absence of a
InvokeObjectPostAlterHook in pg_setting_acl_aclcheck or
pg_setting_acl_aclmask so that MAC extensions can also block this,
InvokeObjectPostCreateHook is already in the create path so a
PostAlter hook seems appropriate.

Thank you.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 14, 2021, at 2:26 PM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
>
> currently there is a failure in check-world (not sure if it's known):

That one is definitely my fault.  'en_US.UTF-8' exists on my platform, so I hadn't noticed.  I've changed it to use
'C',which should be portable. 

> One thing that seems like an omission to me is the absence of a
> InvokeObjectPostAlterHook in pg_setting_acl_aclcheck or
> pg_setting_acl_aclmask so that MAC extensions can also block this,
> InvokeObjectPostCreateHook is already in the create path so a
> PostAlter hook seems appropriate.

Good catch, but that seems like a strange place to put a PostAlterHook, so I added it to ExecGrant_Setting for v6,
instead. This seems more consistent with the hook in SetDefaultACL. 

(If you are really trying to do Managed Access Control (MAC), wouldn't that be a separate patch which adds security
hooksinto all *_aclcheck functions?) 




—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Wed, Dec 15, 2021 at 10:56 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
>
>
>
> > On Dec 14, 2021, at 2:26 PM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
> >
> > currently there is a failure in check-world (not sure if it's known):
>
> That one is definitely my fault.  'en_US.UTF-8' exists on my platform, so I hadn't noticed.  I've changed it to use
'C',which should be portable.
 
>
> > One thing that seems like an omission to me is the absence of a
> > InvokeObjectPostAlterHook in pg_setting_acl_aclcheck or
> > pg_setting_acl_aclmask so that MAC extensions can also block this,
> > InvokeObjectPostCreateHook is already in the create path so a
> > PostAlter hook seems appropriate.
>
> Good catch, but that seems like a strange place to put a PostAlterHook, so I added it to ExecGrant_Setting for v6,
instead. This seems more consistent with the hook in SetDefaultACL.
 

Ah, I was actually requesting a hook where the acl check was done for
setting a GUC, such that we could deny setting them in a hook,
something that would be useful for the set_user extension
(github.com/pgaudit/set_user) but having a hook for grant/revoke is
also helpful.

> (If you are really trying to do Managed Access Control (MAC), wouldn't that be a separate patch which adds security
hooksinto all *_aclcheck functions?)
 

MAC is mandatory access controls, so something that can be layered on
top of DAC and can only be enforced even on object owners and
superuser. sepgsql is a MAC extension, for example. It uses the object
access hooks to enforce SELinux policy on PG objects.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 15, 2021, at 10:02 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
>
> Ah, I was actually requesting a hook where the acl check was done for
> setting a GUC, such that we could deny setting them in a hook,
> something that would be useful for the set_user extension
> (github.com/pgaudit/set_user)

Hmm, this seems orthogonal to the patch under discussion.  This patch only adds a pg_setting_acl_aclcheck in
ExecSetVariableStmt()for settings which have been explicitly granted, otherwise it works the traditional way (checking
whetherthe setting is suset/userset).  I don't think you'd get MAC support without finding a way to fire the hook for
allsettings, regardless of their presence in the new pg_setting_acl table.  That is hard, because
InvokeObjectPostAlterHookexpects the classId (SettingAclRelationId) and the objectId (pg_setting_acl.oid), but you
don'thave those for many (most?) settings.  As discussed upthread, we *do not* want to force an entry into the table
forall settings, only for ones that have been explicitly granted. 

Do you agree?  I'm happy to support MAC in this patch if can explain a simple way of doing so.

> but having a hook for grant/revoke is
> also helpful.

Yes, I see no reason to rip this out.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Wed, Dec 15, 2021 at 1:18 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
>
> > On Dec 15, 2021, at 10:02 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
> >
> > Ah, I was actually requesting a hook where the acl check was done for
> > setting a GUC, such that we could deny setting them in a hook,
> > something that would be useful for the set_user extension
> > (github.com/pgaudit/set_user)
>
> Hmm, this seems orthogonal to the patch under discussion.  This patch only adds a pg_setting_acl_aclcheck in
ExecSetVariableStmt()for settings which have been explicitly granted, otherwise it works the traditional way (checking
whetherthe setting is suset/userset).  I don't think you'd get MAC support without finding a way to fire the hook for
allsettings, regardless of their presence in the new pg_setting_acl table.  That is hard, because
InvokeObjectPostAlterHookexpects the classId (SettingAclRelationId) and the objectId (pg_setting_acl.oid), but you
don'thave those for many (most?) settings.  As discussed upthread, we *do not* want to force an entry into the table
forall settings, only for ones that have been explicitly granted. 
>
> Do you agree?  I'm happy to support MAC in this patch if can explain a simple way of doing so.

Ah, I understand now. Would it be possible to pass the
SettingAclRelationId if it exists or InvalidOid if not? That way if a
MAC implementation cares about a particular GUC it'll ensure it's in
pg_setting_acl.

I don't know if others will object to that but it seems like an okay
compromise.

> > but having a hook for grant/revoke is
> > also helpful.
>
> Yes, I see no reason to rip this out.
>



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 16, 2021, at 7:43 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
>
> Ah, I understand now. Would it be possible to pass the
> SettingAclRelationId if it exists or InvalidOid if not?

SettingAclRelationId is always defined, so we can always pass that value.  But the settingId itself may sometimes be
InvalidOid.

> That way if a
> MAC implementation cares about a particular GUC it'll ensure it's in
> pg_setting_acl.

A much cleaner solution would be to create new ObjectAccessTypes with a corresponding new Invoke macro and Run
function. Those could take setting names, not Oids, and include additional information about whether the operation is
SET,RESET or ALTER SYSTEM, what the new value is (if any), what kind of setting it is (bool, int, ...), etc.  I don't
thinksuch a patch would even be all that hard to write. 

What do you think?

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Thu, Dec 16, 2021 at 12:53 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
>
>
>
> > On Dec 16, 2021, at 7:43 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
> >
> > Ah, I understand now. Would it be possible to pass the
> > SettingAclRelationId if it exists or InvalidOid if not?
>
> SettingAclRelationId is always defined, so we can always pass that value.  But the settingId itself may sometimes be
InvalidOid.

Yes, that is what I meant.

> > That way if a
> > MAC implementation cares about a particular GUC it'll ensure it's in
> > pg_setting_acl.
>
> A much cleaner solution would be to create new ObjectAccessTypes with a corresponding new Invoke macro and Run
function. Those could take setting names, not Oids, and include additional information about whether the operation is
SET,RESET or ALTER SYSTEM, what the new value is (if any), what kind of setting it is (bool, int, ...), etc.  I don't
thinksuch a patch would even be all that hard to write. 
>
> What do you think?

Personally, I would be happy with that, but since it's a whole new
hooking method I suspect it'll be an uphill battle. That definitely
seems like another patchset though, if you do submit this I will test
and review.

Thank you.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Dec 15, 2021, at 7:56 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> <v6-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-SET-for-gucs.patch>

Rebased:



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Jan 26, 2022, at 7:24 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> Rebased:
>
> <v7-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-SET-for-gucs.patch>

Hackers,

Joshua and I had some off-list back-and-forth on this patch and I think the attached v8 patch meets his needs.
Specifically,v8 adds support for object access hooks in the case of SET and ALTER SYSTEM SET of a guc variable. 

The way InvokeObjectPostAlterHookArg() is written, it expects some things like a Oid that we don't always have.  I
abusedthe parameters of that function to pass as much information as possible.  Anyone wanting to review this might
considerhow that is coded. 

Joshua, this is byte-for-byte the same patch that you already reviewed off-list (or I attached the wrong patch!)  If
youare satisfied, could you set the status to ready-for-committer? 



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> [ v8-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-SET-for-gucs.patch ]

I noticed that this is failing in the cfbot as a side-effect of my
cc50080a8, so here's a rebase (basically it just removes the no-
longer-necessary sanity_check.out hunk).

I haven't reviewed this particularly, or read the thread, but I'm
pretty disturbed by the contrib/ changes that it proposes.

1. If we need to change these two contrib modules, doesn't that imply
a lot of changes forced on external modules as well?  What are the
security implications if somebody doesn't make such a change?

2. It looks to me like if someone installs the updated postgres_fdw.so,
but doesn't run ALTER EXTENSION UPDATE, they are going to be left with a
rather broken extension.  This seems not good either, especially if it's
multiplied by a boatload of third-party extensions requiring updates.

So I think some more thought is needed to see if we can't avoid
the need to touch extension modules in this patch.  Or at least
avoid the need for synchronized C-level and SQL-level updates,
because that is going to create a lot of pain for end users.

I'm also fairly distressed by the number of changes in guc.c,
mainly because I fear that that means that pending patches that
add GUC variables will be subtly incorrect/insecure if they're not
updated to account for this.  Frankly, I also reject the apparent
position that we need to support forbidding users from touching
many of these GUCs.  Or, if that's our position, why are there
per-GUC changes at all, rather than just redefining what the
context values mean?  (That is, why not redefine USERSET and
SUSET as simply indicating the default ACL to be applied if there's
no entry in the catalog.)

            regards, tom lane

diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile
index 1fbdc9ec1e..8fac4f6289 100644
--- a/contrib/pg_trgm/Makefile
+++ b/contrib/pg_trgm/Makefile
@@ -9,9 +9,9 @@ OBJS = \
     trgm_regexp.o

 EXTENSION = pg_trgm
-DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \
-    pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql pg_trgm--1.1--1.2.sql \
-    pg_trgm--1.0--1.1.sql
+DATA = pg_trgm--1.6--1.7.sql pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql \
+    pg_trgm--1.3--1.4.sql pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql \
+    pg_trgm--1.1--1.2.sql pg_trgm--1.0--1.1.sql
 PGFILEDESC = "pg_trgm - trigram matching"

 REGRESS = pg_trgm pg_word_trgm pg_strict_word_trgm
diff --git a/contrib/pg_trgm/pg_trgm--1.6--1.7.sql b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
new file mode 100644
index 0000000000..06b783acbd
--- /dev/null
+++ b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
@@ -0,0 +1,8 @@
+/* contrib/pg_trgm/pg_trgm--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.7'" to load this file. \quit
+
+GRANT SET VALUE ON "pg_trgm.similarity_threshold" TO public;
+GRANT SET VALUE ON "pg_trgm.word_similarity_threshold" TO public;
+GRANT SET VALUE ON "pg_trgm.strict_word_similarity_threshold" TO public;
diff --git a/contrib/pg_trgm/pg_trgm.control b/contrib/pg_trgm/pg_trgm.control
index 1d6a9ddf25..6e3ee43c51 100644
--- a/contrib/pg_trgm/pg_trgm.control
+++ b/contrib/pg_trgm/pg_trgm.control
@@ -1,6 +1,6 @@
 # pg_trgm extension
 comment = 'text similarity measurement and index searching based on trigrams'
-default_version = '1.6'
+default_version = '1.7'
 module_pathname = '$libdir/pg_trgm'
 relocatable = true
 trusted = true
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index c1b0cad453..3564015ee8 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,8 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
 SHLIB_LINK_INTERNAL = $(libpq)

 EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql \
+    postgres_fdw--1.1--1.2.sql

 REGRESS = postgres_fdw

diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 572591a558..df58b5f339 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -534,7 +534,7 @@ _PG_init(void)
                                NULL,
                                &pgfdw_application_name,
                                NULL,
-                               PGC_USERSET,
+                               PGC_SUSET,
                                0,
                                NULL,
                                NULL,
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
new file mode 100644
index 0000000000..b174e04283
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
@@ -0,0 +1,6 @@
+/* contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.2'" to load this file. \quit
+
+GRANT SET VALUE ON "postgres_fdw.application_name" TO public;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index d489382064..a4b800be4f 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
 # postgres_fdw extension
 comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.1'
+default_version = '1.2'
 module_pathname = '$libdir/postgres_fdw'
 relocatable = true
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..7f899fa8bf 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -449,7 +449,7 @@ _PG_init(void)
                              NULL,
                              &sepgsql_debug_audit,
                              false,
-                             PGC_USERSET,
+                             PGC_SUSET,
                              GUC_NOT_IN_SAMPLE,
                              NULL,
                              NULL,
diff --git a/contrib/sepgsql/sepgsql.sql.in b/contrib/sepgsql/sepgsql.sql.in
index 917d12dbbe..4dce769306 100644
--- a/contrib/sepgsql/sepgsql.sql.in
+++ b/contrib/sepgsql/sepgsql.sql.in
@@ -34,4 +34,5 @@ CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_setcon(text) RETURNS bool AS 'MODU
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_in(text) RETURNS text AS 'MODULE_PATHNAME',
'sepgsql_mcstrans_in'LANGUAGE C STRICT; 
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_out(text) RETURNS text AS 'MODULE_PATHNAME',
'sepgsql_mcstrans_out'LANGUAGE C STRICT; 
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_restorecon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_restorecon'
LANGUAGEC; 
+GRANT SET VALUE ON "sepgsql.debug_audit" TO public;
 SELECT sepgsql_restorecon(NULL);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 83987a9904..7b288cce30 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-setting-acl"><structname>pg_setting_acl</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><iteration count></replaceable>:<replaceable>&l
   </para>
  </sect1>

+ <sect1 id="catalog-pg-setting-acl">
+  <title><structname>pg_setting_acl</structname></title>
+
+  <indexterm zone="catalog-pg-setting-acl">
+   <primary>pg_setting_acl</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_setting_acl</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_setting_acl</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_setting_acl</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_setting_acl</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>setting</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>setacl</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>

@@ -12540,11 +12603,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
     <term><literal>superuser</literal></term>
     <listitem>
      <para>
-      These settings can be set from <filename>postgresql.conf</filename>,
-      or within a session via the <command>SET</command> command; but only superusers
-      can change them via <command>SET</command>.  Changes in
-      <filename>postgresql.conf</filename> will affect existing sessions
-      only if no session-local value has been established with <command>SET</command>.
+      These settings can be set from <filename>postgresql.conf</filename>, or
+      within a session via the <command>SET</command> command; but only
+      superusers or users with <literal>SET VALUE</literal> privilege granted
+      on the setting can change them via <command>SET</command>.  Changes in
+      <filename>postgresql.conf</filename> will affect existing sessions only
+      if no session-local value has been established with
+      <command>SET</command>.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 166b7a352d..78d97a9689 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1691,7 +1691,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.
@@ -1959,6 +1960,28 @@ 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 <literal>superuser</literal> run-time configuration parameters.
+       (Any user can set <literal>user</literal> 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
@@ -2097,6 +2120,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>
@@ -2203,6 +2236,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/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb225..36b33d8639 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22691,8 +22691,7 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
    privilege is held with grant option. Also, multiple privilege types can be
    listed separated by commas, in which case the result will be true if any of
    the listed privileges is held. (Case of the privilege string is not
-   significant, and extra whitespace is allowed between but not within
-   privilege names.)
+   significant, and extra whitespace is allowed between privilege names.)
    Some examples:
 <programlisting>
 SELECT has_table_privilege('myschema.mytable', 'select');
@@ -22957,6 +22956,23 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>

+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_setting_privilege</primary>
+        </indexterm>
+        <function>has_setting_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>setting</parameter> <type>text</type> or <type>oid</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for setting?
+        Allowable privilege types are <literal>SET VALUE</literal> and <literal>ALTER SYSTEM</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
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 eefebb7bb8..fefbfc343d 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_setting_acl.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_setting_acl.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 \
@@ -126,6 +127,7 @@ install-data: bki-stamp installdirs
     $(INSTALL_DATA) $(srcdir)/system_functions.sql '$(DESTDIR)$(datadir)/system_functions.sql'
     $(INSTALL_DATA) $(srcdir)/system_views.sql '$(DESTDIR)$(datadir)/system_views.sql'
     $(INSTALL_DATA) $(srcdir)/information_schema.sql '$(DESTDIR)$(datadir)/information_schema.sql'
+    $(INSTALL_DATA) $(srcdir)/setting_privileges.sql '$(DESTDIR)$(datadir)/setting_privileges.sql'
     $(INSTALL_DATA) $(srcdir)/sql_features.txt '$(DESTDIR)$(datadir)/sql_features.txt'

 installdirs:
@@ -133,7 +135,7 @@ installdirs:

 .PHONY: uninstall-data
 uninstall-data:
-    rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql
system_views.sqlinformation_schema.sql sql_features.txt) 
+    rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql
system_views.sqlinformation_schema.sql setting_privileges.sql sql_features.txt) 

 # postgres.bki, system_constraints.sql, and the generated headers are
 # in the distribution tarball, so they are not cleaned here.
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd03a8e51..3f8699b867 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.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_Setting(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_SETTING:
+            whole_mask = ACL_ALL_RIGHTS_SETTING;
+            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_SETTING:
+            all_privileges = ACL_ALL_RIGHTS_SETTING;
+            errormsg = gettext_noop("invalid privilege type %s for setting");
+            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_SETTING:
+            ExecGrant_Setting(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_SETTING:
+            foreach(cell, objnames)
+            {
+                char       *setting = strVal(lfirst(cell));
+                Oid            settingid = get_setting_oid(setting, true);
+
+                if (!OidIsValid(settingid))
+                {
+                    /*
+                     * 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
+                     * setting names that do not yet exist.
+                     */
+                    settingid = SettingAclCreate(setting, 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, settingid);
+            }
+            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 SettingAclRelationId:
+                istmt.objtype = OBJECT_SETTING;
+                break;
             default:
                 elog(ERROR, "unexpected object class %u", classid);
                 break;
@@ -3225,6 +3272,131 @@ ExecGrant_Type(InternalGrant *istmt)
     table_close(relation, RowExclusiveLock);
 }

+static void
+ExecGrant_Setting(InternalGrant *istmt)
+{
+    Relation    relation;
+    ListCell   *cell;
+
+    if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+        istmt->privileges = ACL_ALL_RIGHTS_SETTING;
+
+    relation = table_open(SettingAclRelationId, RowExclusiveLock);
+
+    foreach(cell, istmt->objects)
+    {
+        Oid            settingId = lfirst_oid(cell);
+        Form_pg_setting_acl pg_setting_acl_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_setting_acl];
+        bool        nulls[Natts_pg_setting_acl];
+        bool        replaces[Natts_pg_setting_acl];
+        int            noldmembers;
+        int            nnewmembers;
+        Oid           *oldmembers;
+        Oid           *newmembers;
+
+        tuple = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(settingId));
+        if (!HeapTupleIsValid(tuple))
+            elog(ERROR, "cache lookup failed for setting %u", settingId);
+
+        pg_setting_acl_tuple = (Form_pg_setting_acl) GETSTRUCT(tuple);
+
+        /*
+         * Get owner ID and working copy of existing ACL. If there's no ACL,
+         * substitute the proper default.
+         */
+        aclDatum = SysCacheGetAttr(SETTINGNAME, tuple, Anum_pg_setting_acl_setacl,
+                                   &isNull);
+        if (isNull)
+        {
+            old_acl = acldefault(OBJECT_SETTING, 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,
+                                     settingId, grantorId, OBJECT_SETTING,
+                                     text_to_cstring(&pg_setting_acl_tuple->setting),
+                                     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_setting_acl_setacl - 1] = true;
+        values[Anum_pg_setting_acl_setacl - 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(settingId, SettingAclRelationId, 0, new_acl);
+
+        /* Update the shared dependency ACL info */
+        updateAclDependencies(SettingAclRelationId,
+                              pg_setting_acl_tuple->oid, 0,
+                              InvalidOid,
+                              noldmembers, oldmembers,
+                              nnewmembers, newmembers);
+
+        ReleaseSysCache(tuple);
+
+        pfree(new_acl);
+
+        /* Post alter hook called for grant and revoke */
+        InvokeObjectPostAlterHook(SettingAclRelationId, settingId, 0);
+
+        /* prevent error when processing duplicate objects */
+        CommandCounterIncrement();
+    }
+
+    table_close(relation, RowExclusiveLock);
+}
+

 static AclMode
 string_to_privilege(const char *privname)
@@ -3255,6 +3427,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_VALUE;
+    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 +3468,10 @@ privilege_to_string(AclMode privilege)
             return "TEMP";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET_VALUE:
+            return "SET VALUE";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized privilege: %d", (int) privilege);
     }
@@ -3328,6 +3508,13 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_COLUMN:
                         msg = gettext_noop("permission denied for column %s");
                         break;
+                    case OBJECT_SETTING:
+                        /*
+                         * 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 +3751,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_AMPROC:
                     case OBJECT_ATTRIBUTE:
                     case OBJECT_CAST:
+                    case OBJECT_SETTING:
                     case OBJECT_DEFAULT:
                     case OBJECT_DEFACL:
                     case OBJECT_DOMCONSTRAINT:
@@ -4000,6 +4188,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_setting_acl_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 setting's ACL from pg_setting_acl
+     */
+    tuple = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(config_oid));
+    if (!HeapTupleIsValid(tuple))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_DATABASE),
+                 errmsg("setting with OID %u does not exist",
+                        config_oid)));
+
+    aclDatum = SysCacheGetAttr(SETTINGOID, tuple, Anum_pg_setting_acl_setacl,
+                               &isNull);
+    if (isNull)
+    {
+        /* No ACL, so build default ACL */
+        acl = acldefault(OBJECT_SETTING, 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 +4954,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_setting_acl_aclcheck(Oid config_oid, Oid roleid, AclMode mode)
+{
+    if (pg_setting_acl_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 dfd5fb669e..78868e7052 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_replication_origin.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
 #include "catalog/pg_shseclabel.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
     /* These are the shared catalogs (look for BKI_SHARED_RELATION) */
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
+        relationId == SettingAclRelationId ||
         relationId == DatabaseRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
         relationId == AuthIdOidIndexId ||
         relationId == AuthMemRoleMemIndexId ||
         relationId == AuthMemMemRoleIndexId ||
+        relationId == SettingAclSettingIndexId ||
+        relationId == SettingAclOidIndexId ||
         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 == PgSettingAclToastTable ||
+        relationId == PgSettingAclToastIndex ||
         relationId == PgDatabaseToastTable ||
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ab9e42d7d1..5d2504463d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
@@ -150,6 +151,7 @@ static const Oid object_classes[] = {
     TypeRelationId,                /* OCLASS_TYPE */
     CastRelationId,                /* OCLASS_CAST */
     CollationRelationId,        /* OCLASS_COLLATION */
+    SettingAclRelationId,        /* OCLASS_SETTING */
     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_SETTING:
         case OCLASS_ROLE:
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
@@ -2778,6 +2781,9 @@ getObjectClass(const ObjectAddress *object)
         case CollationRelationId:
             return OCLASS_COLLATION;

+        case SettingAclRelationId:
+            return OCLASS_SETTING;
+
         case ConstraintRelationId:
             return OCLASS_CONSTRAINT;

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index f30c742d48..1ef833a0e3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
@@ -2309,6 +2310,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
         case OBJECT_COLUMN:
         case OBJECT_ATTRIBUTE:
         case OBJECT_COLLATION:
+        case OBJECT_SETTING:
         case OBJECT_CONVERSION:
         case OBJECT_STATISTIC_EXT:
         case OBJECT_TSPARSER:
@@ -3510,6 +3512,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
                 break;
             }

+        case OCLASS_SETTING:
+            {
+                char       *setting;
+
+                setting = get_setting_name(object->objectId);
+                if (!setting)
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for setting %u",
+                             object->objectId);
+                    break;
+                }
+                appendStringInfo(&buffer, _("setting %s"), setting);
+                break;
+            }
+
         case OCLASS_SCHEMA:
             {
                 char       *nspname;
@@ -4473,6 +4491,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
             appendStringInfoString(&buffer, "collation");
             break;

+        case OCLASS_SETTING:
+            appendStringInfoString(&buffer, "setting");
+            break;
+
         case OCLASS_CONSTRAINT:
             getConstraintTypeDescription(&buffer, object->objectId,
                                          missing_ok);
@@ -4977,6 +4999,30 @@ getObjectIdentityParts(const ObjectAddress *object,
                 break;
             }

+        case OCLASS_SETTING:
+            {
+                HeapTuple    configTup;
+                Form_pg_setting_acl configForm;
+                char       *namestr;
+
+                configTup = SearchSysCache1(SETTINGOID,
+                                            ObjectIdGetDatum(object->objectId));
+                if (!HeapTupleIsValid(configTup))
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for setting %u",
+                             object->objectId);
+                    break;
+                }
+                configForm = (Form_pg_setting_acl) GETSTRUCT(configTup);
+                namestr = text_to_cstring(&configForm->setting);
+                appendStringInfoString(&buffer, namestr);
+                if (objname)
+                    *objname = list_make1(namestr);
+                ReleaseSysCache(configTup);
+                break;
+            }
+
         case OCLASS_CONVERSION:
             {
                 HeapTuple    conTup;
diff --git a/src/backend/catalog/pg_setting_acl.c b/src/backend/catalog/pg_setting_acl.c
new file mode 100644
index 0000000000..3c6594da03
--- /dev/null
+++ b/src/backend/catalog/pg_setting_acl.c
@@ -0,0 +1,122 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_setting_acl.c
+ *      routines to support manipulation of the pg_setting_acl 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_setting_acl.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_namespace.h"
+#include "catalog/pg_setting_acl.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"
+
+
+/*
+ * SettingAclCreate
+ *
+ * Add a new tuple to pg_setting_acl.
+ *
+ * setting: the setting name to create.
+ * if_not_exists: if true, don't fail on duplicate name, but rather return
+ * the existing entry's Oid.
+ */
+Oid
+SettingAclCreate(const char *setting, bool if_not_exists)
+{
+    Relation    rel;
+    TupleDesc    tupDesc;
+    HeapTuple    tuple;
+    Datum        values[Natts_pg_setting_acl];
+    bool        nulls[Natts_pg_setting_acl];
+    Oid            settingId;
+    const char *canonical;
+
+    /*
+     * Check whether the setting (by the given name or alias)
+     * already exists.
+     */
+    settingId = get_setting_oid(setting, true);
+    if (OidIsValid(settingId))
+    {
+        if (if_not_exists)
+            return settingId;
+        ereport(ERROR,
+                (errcode(ERRCODE_DUPLICATE_OBJECT),
+                 errmsg("setting \"%s\" already exists",
+                        setting)));
+    }
+
+    /*
+     * We must not require the setting 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 setting, 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_setting_acl entries.
+     */
+    if (!valid_variable_name(setting, NULL))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_NAME),
+                 errmsg("invalid setting name \"%s\"",
+                        setting)));
+    canonical = GetConfigOptionCanonicalName(setting);
+    if (!canonical)
+        canonical = setting;
+
+    /*
+     * 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(SettingAclRelationId, RowExclusiveLock);
+    tupDesc = RelationGetDescr(rel);
+    MemSet(values, 0, sizeof(values));
+    MemSet(nulls, false, sizeof(nulls));
+    values[Anum_pg_setting_acl_setting - 1] =
+        DirectFunctionCall1(textin, CStringGetDatum(canonical));
+    settingId = GetNewOidWithIndex(rel,
+                                       SettingAclOidIndexId,
+                                       Anum_pg_setting_acl_oid);
+    values[Anum_pg_setting_acl_oid - 1] = ObjectIdGetDatum(settingId);
+    nulls[Anum_pg_setting_acl_setacl - 1] = true;
+    tuple = heap_form_tuple(tupDesc, values, nulls);
+    CatalogTupleInsert(rel, tuple);
+
+    /* Post creation hook for new setting */
+    InvokeObjectPostCreateHook(SettingAclRelationId, settingId, 0);
+
+    /*
+     * Close pg_setting_acl, but keep lock till commit.
+     */
+    heap_freetuple(tuple);
+    table_close(rel, NoLock);
+
+    return settingId;
+}
diff --git a/src/backend/catalog/setting_privileges.sql b/src/backend/catalog/setting_privileges.sql
new file mode 100644
index 0000000000..b788ebee7f
--- /dev/null
+++ b/src/backend/catalog/setting_privileges.sql
@@ -0,0 +1,62 @@
+/*
+ * PostgreSQL User SET variables
+ *
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/backend/catalog/setting_privileges.sql
+ *
+ * Note: this file is read in single-user -j mode, which means that the
+ * command terminator is semicolon-newline-newline; whenever the backend
+ * sees that, it stops and executes what it's got.  If you write a lot of
+ * statements without empty lines between, they'll all get quoted to you
+ * in any error message about one of them, so don't do that.  Also, you
+ * cannot write a semicolon immediately followed by an empty line in a
+ * string literal (including a function body!) or a multiline comment.
+ */
+
+GRANT SET VALUE ON
+    enable_seqscan, enable_indexscan, enable_indexonlyscan, enable_bitmapscan,
+    enable_tidscan, enable_sort, enable_incremental_sort, enable_hashagg,
+    enable_material, enable_memoize, enable_nestloop, enable_mergejoin,
+    enable_hashjoin, enable_gathermerge, enable_partitionwise_join,
+    enable_partitionwise_aggregate, enable_parallel_append,
+    enable_parallel_hash, enable_partition_pruning, enable_async_append, geqo,
+    exit_on_error, debug_print_parse, debug_print_rewritten, debug_print_plan,
+    debug_pretty_print, trace_notify, transform_null_equals,
+    default_transaction_read_only, transaction_read_only,
+    default_transaction_deferrable, transaction_deferrable, row_security,
+    check_function_bodies, array_nulls, default_with_oids, trace_sort,
+    trace_syncscan, optimize_bounded_sort, escape_string_warning,
+    standard_conforming_strings, synchronize_seqscans, quote_all_identifiers,
+    parallel_leader_participation, jit, jit_expressions, jit_tuple_deforming,
+    default_statistics_target, from_collapse_limit, join_collapse_limit,
+    geqo_threshold, geqo_effort, geqo_pool_size, geqo_generations,
+    temp_buffers, work_mem, maintenance_work_mem, logical_decoding_work_mem,
+    vacuum_cost_page_hit, vacuum_cost_page_miss, vacuum_cost_page_dirty,
+    vacuum_cost_limit, statement_timeout, lock_timeout,
+    idle_in_transaction_session_timeout, idle_session_timeout,
+    vacuum_freeze_min_age, vacuum_freeze_table_age,
+    vacuum_multixact_freeze_min_age, vacuum_multixact_freeze_table_age,
+    vacuum_failsafe_age, vacuum_multixact_failsafe_age, wal_skip_threshold,
+    wal_sender_timeout, commit_siblings, extra_float_digits,
+    log_parameter_max_length_on_error, effective_io_concurrency,
+    maintenance_io_concurrency, backend_flush_after,
+    max_parallel_maintenance_workers, max_parallel_workers_per_gather,
+    max_parallel_workers, tcp_keepalives_idle, tcp_keepalives_interval,
+    ssl_renegotiation_limit, tcp_keepalives_count, gin_fuzzy_search_limit,
+    effective_cache_size, min_parallel_table_scan_size,
+    min_parallel_index_scan_size, gin_pending_list_limit, tcp_user_timeout,
+    client_connection_check_interval, seq_page_cost, random_page_cost,
+    cpu_tuple_cost, cpu_index_tuple_cost, cpu_operator_cost,
+    parallel_tuple_cost, parallel_setup_cost, jit_above_cost,
+    jit_optimize_above_cost, jit_inline_above_cost, cursor_tuple_fraction,
+    geqo_selection_bias, geqo_seed, hash_mem_multiplier, seed,
+    vacuum_cost_delay, DateStyle, default_table_access_method,
+    default_tablespace, temp_tablespaces, lc_monetary, lc_numeric, lc_time,
+    local_preload_libraries, search_path, role, TimeZone,
+    timezone_abbreviations, default_text_search_config, application_name,
+    backslash_quote, bytea_output, client_min_messages, constraint_exclusion,
+    default_toast_compression, default_transaction_isolation,
+    transaction_isolation, IntervalStyle, synchronous_commit, xmlbinary,
+    xmloption, force_parallel_mode, password_encryption, plan_cache_mode
+    TO public;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 40b7bca5a9..92b226b8d2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -595,6 +595,19 @@ CREATE RULE pg_settings_n AS

 GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;

+CREATE VIEW pg_setting_privileges AS
+    SELECT grantor.rolname AS grantor,
+           grantee.rolname AS grantee,
+           set_acl.setting AS setting,
+           acl.privilege_type AS privilege_type,
+           acl.is_grantable
+        FROM pg_catalog.pg_setting_acl set_acl,
+        LATERAL (SELECT * FROM aclexplode(set_acl.setacl)) acl
+        LEFT JOIN pg_catalog.pg_authid grantee ON acl.grantee = grantee.oid
+        LEFT JOIN pg_catalog.pg_authid grantor ON acl.grantor = grantor.oid;
+
+GRANT SELECT ON pg_setting_privileges TO PUBLIC;
+
 CREATE VIEW pg_file_settings AS
    SELECT * FROM pg_show_all_file_settings() AS A;

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1f64c8aa51..3dd0c5cadb 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_SETTING:
         case OCLASS_CONSTRAINT:
         case OCLASS_DEFAULT:
         case OCLASS_LANGUAGE:
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index c9b5732448..00398727ed 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_SETTING:
+            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+            {
+                msg = gettext_noop("setting \"%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 1e8587502e..038486a622 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_SETTING:
         case OBJECT_DATABASE:
         case OBJECT_TABLESPACE:
         case OBJECT_ROLE:
@@ -1012,6 +1013,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 {
     switch (objclass)
     {
+        case OCLASS_SETTING:
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
         case OCLASS_ROLE:
@@ -2072,6 +2074,8 @@ stringify_grant_objtype(ObjectType objtype)
     {
         case OBJECT_COLUMN:
             return "COLUMN";
+        case OBJECT_SETTING:
+            return "SETTING";
         case OBJECT_TABLE:
             return "TABLE";
         case OBJECT_SEQUENCE:
@@ -2155,6 +2159,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
     {
         case OBJECT_COLUMN:
             return "COLUMNS";
+        case OBJECT_SETTING:
+            return "SETTINGS";
         case OBJECT_TABLE:
             return "TABLES";
         case OBJECT_SEQUENCE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7a62d547e2..7b05eb0110 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_SETTING:
         case OBJECT_CONVERSION:
         case OBJECT_DEFAULT:
         case OBJECT_DEFACL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc5872f988..11671390a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12620,6 +12620,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
             case OCLASS_TYPE:
             case OCLASS_CAST:
             case OCLASS_COLLATION:
+            case OCLASS_SETTING:
             case OCLASS_CONVERSION:
             case OCLASS_LANGUAGE:
             case OCLASS_LARGEOBJECT:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53b..45a9f078f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -363,8 +363,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 setting_name
+%type <list>    OptSchemaEltList setting_target

 %type <chr>        am_type

@@ -402,8 +402,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 setting_priv
+%type <list>    privileges privilege_list setting_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
@@ -716,7 +716,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

@@ -6981,6 +6981,20 @@ GrantStmt:    GRANT privileges ON privilege_target TO grantee_list
                     n->grantor = $8;
                     $$ = (Node*)n;
                 }
+            | GRANT setting_priv_list ON setting_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_SETTING;
+                    n->objects = $4;
+                    n->grantees = $6;
+                    n->grant_option = $7;
+                    n->grantor = $8;
+                    $$ = (Node*)n;
+                }
         ;

 RevokeStmt:
@@ -7014,6 +7028,36 @@ RevokeStmt:
                     n->behavior = $11;
                     $$ = (Node *)n;
                 }
+            | REVOKE setting_priv_list ON setting_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_SETTING;
+                    n->objects = $4;
+                    n->grantees = $6;
+                    n->grantor = $7;
+                    n->behavior = $8;
+                    $$ = (Node *)n;
+                }
+            | REVOKE GRANT OPTION FOR setting_priv_list ON setting_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_SETTING;
+                    n->objects = $7;
+                    n->grantees = $9;
+                    n->grantor = $10;
+                    n->behavior = $11;
+                    $$ = (Node *)n;
+                }
         ;


@@ -7082,6 +7126,49 @@ privilege:    SELECT opt_column_list
             }
         ;

+setting_priv_list: setting_priv                        { $$ = list_make1($1); }
+            | setting_priv_list ',' setting_priv    { $$ = lappend($1, $3); }
+        ;
+
+setting_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;
+            }
+        ;
+
+
+setting_target:
+            setting_name
+                {
+                    $$ = list_make1(makeString($1));
+                }
+            | setting_target ',' setting_name
+                {
+                    $$ = lappend($1, makeString($3));
+                }
+        ;
+
+setting_name:
+            ColId
+                {
+                    $$ = $1;
+                }
+            | setting_name '.' ColId
+                {
+                    $$ = psprintf("%s.%s", $1, $3);
+                }
+        ;

 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
@@ -15851,6 +15938,7 @@ unreserved_keyword:
             | OWNED
             | OWNER
             | PARALLEL
+            | PARAMETER
             | PARSER
             | PARTIAL
             | PARTITION
@@ -16429,6 +16517,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 0a16f8156c..b0e43118bf 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/proclang.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"
@@ -109,6 +111,8 @@ static Oid    convert_tablespace_name(text *tablespacename);
 static AclMode convert_tablespace_priv_string(text *priv_type_text);
 static Oid    convert_type_name(text *typename);
 static AclMode convert_type_priv_string(text *priv_type_text);
+static Oid    convert_setting_name(text *setting);
+static AclMode convert_setting_priv_string(text *priv_setting_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);

@@ -306,6 +310,12 @@ aclparse(const char *s, AclItem *aip)
             case ACL_CONNECT_CHR:
                 read = ACL_CONNECT;
                 break;
+            case ACL_SET_VALUE_CHR:
+                read = ACL_SET_VALUE;
+                break;
+            case ACL_ALTER_SYSTEM_CHR:
+                read = ACL_ALTER_SYSTEM;
+                break;
             case 'R':            /* ignore old RULE privileges */
                 read = 0;
                 break;
@@ -794,6 +804,10 @@ acldefault(ObjectType objtype, Oid ownerId)
             world_default = ACL_USAGE;
             owner_default = ACL_ALL_RIGHTS_TYPE;
             break;
+        case OBJECT_SETTING:
+            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 +1616,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_VALUE;
+    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 +1716,10 @@ convert_aclright_to_string(int aclright)
             return "TEMPORARY";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET_VALUE:
+            return "SET VALUE";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized aclright: %d", aclright);
             return NULL;
@@ -4429,6 +4451,205 @@ convert_type_priv_string(text *priv_type_text)
     return convert_any_priv_string(priv_type_text, type_priv_map);
 }

+/*
+ * has_setting_privilege variants
+ *        These are all named "has_setting_privilege" at the SQL level.
+ *        They take various combinations of setting name, setting OID,
+ *        user name, user OID, or implicit user = current_user.
+ *
+ *        The result is a boolean value: true if user has the indicated
+ *        privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_setting_privilege_name_name
+ *        Check user privileges on a setting given
+ *        name username, text setting, and text priv name.
+ */
+Datum
+has_setting_privilege_name_name(PG_FUNCTION_ARGS)
+{
+    Name        username = PG_GETARG_NAME(0);
+    text       *setting = PG_GETARG_TEXT_PP(1);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(2);
+    Oid            roleid;
+    Oid            settingoid;
+    AclMode        mode;
+    AclResult    aclresult;
+
+    roleid = get_role_oid_or_public(NameStr(*username));
+    settingoid = convert_setting_name(setting);
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_name
+ *        Check user privileges on a setting given
+ *        text setting and text priv name.
+ *        current_user is assumed
+ */
+Datum
+has_setting_privilege_name(PG_FUNCTION_ARGS)
+{
+    text       *setting = PG_GETARG_TEXT_PP(0);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(1);
+    Oid            roleid;
+    Oid            settingoid;
+    AclMode        mode;
+    AclResult    aclresult;
+
+    roleid = GetUserId();
+    settingoid = convert_setting_name(setting);
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_name_id
+ *        Check user privileges on a setting given
+ *        name usename, setting oid, and text priv name.
+ */
+Datum
+has_setting_privilege_name_id(PG_FUNCTION_ARGS)
+{
+    Name        username = PG_GETARG_NAME(0);
+    Oid            settingoid = PG_GETARG_OID(1);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(2);
+    Oid            roleid;
+    AclMode        mode;
+    AclResult    aclresult;
+
+    roleid = get_role_oid_or_public(NameStr(*username));
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+        PG_RETURN_NULL();
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id
+ *        Check user privileges on a setting given
+ *        setting oid, and text priv name.
+ *        current_user is assumed
+ */
+Datum
+has_setting_privilege_id(PG_FUNCTION_ARGS)
+{
+    Oid            settingoid = PG_GETARG_OID(0);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(1);
+    Oid            roleid;
+    AclMode        mode;
+    AclResult    aclresult;
+
+    roleid = GetUserId();
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+        PG_RETURN_NULL();
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id_name
+ *        Check user privileges on a setting given
+ *        roleid, text setting, and text priv name.
+ */
+Datum
+has_setting_privilege_id_name(PG_FUNCTION_ARGS)
+{
+    Oid            roleid = PG_GETARG_OID(0);
+    text       *setting = PG_GETARG_TEXT_PP(1);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(2);
+    Oid            settingoid;
+    AclMode        mode;
+    AclResult    aclresult;
+
+    settingoid = convert_setting_name(setting);
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id_id
+ *        Check user privileges on a setting given
+ *        roleid, setting oid, and text priv name.
+ */
+Datum
+has_setting_privilege_id_id(PG_FUNCTION_ARGS)
+{
+    Oid            roleid = PG_GETARG_OID(0);
+    Oid            settingoid = PG_GETARG_OID(1);
+    text       *priv_setting_text = PG_GETARG_TEXT_PP(2);
+    AclMode        mode;
+    AclResult    aclresult;
+
+    mode = convert_setting_priv_string(priv_setting_text);
+
+    if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+        PG_RETURN_NULL();
+
+    aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+    PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *        Support routines for has_setting_privilege family.
+ */
+
+/*
+ * Given a setting name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_setting_name(text *settingname)
+{
+    Oid            oid;
+    char       *setting = text_to_cstring(settingname);
+
+    oid = get_setting_oid(setting, true);
+
+    if (!OidIsValid(oid))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("setting \"%s\" does not exist", setting)));
+
+    return oid;
+}
+
+/*
+ * convert_setting_priv_string
+ *        Convert text string to AclMode value.
+ */
+static AclMode
+convert_setting_priv_string(text *priv_setting_text)
+{
+    static const priv_map setting_priv_map[] = {
+        {"SET VALUE", ACL_SET_VALUE},
+        {"SET VALUE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET_VALUE)},
+        {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+        {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
+        {NULL, 0}
+    };
+
+    return convert_any_priv_string(priv_setting_text, setting_priv_map);
+}

 /*
  * pg_has_role variants
@@ -4670,6 +4891,43 @@ initialize_acl(void)
     }
 }

+/*
+ * get_setting_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_setting_oid(const char *setting, bool missing_ok)
+{
+    Oid            oid;
+
+    /* Check for the variable by the name we were given */
+    oid = GetSysCacheOid1(SETTINGNAME, Anum_pg_setting_acl_oid,
+                          PointerGetDatum(cstring_to_text(setting)));
+    if (!OidIsValid(oid))
+    {
+        const char *canonical;
+
+        /* Check if the variable has a different canonical spelling */
+        canonical = GetConfigOptionCanonicalName(setting);
+        if (canonical != NULL)
+            oid = GetSysCacheOid1(SETTINGNAME, Anum_pg_setting_acl_oid,
+                                  PointerGetDatum(cstring_to_text(canonical)));
+
+        if (!OidIsValid(oid) && !missing_ok)
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_OBJECT),
+                     errmsg("setting \"%s\" does not exist", setting)));
+    }
+
+    return oid;
+}
+
 /*
  * RoleMembershipCacheCallback
  *        Syscache inval callback function
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef999863..f4e806fe8e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
@@ -3304,6 +3305,25 @@ free_attstatsslot(AttStatsSlot *sslot)
         pfree(sslot->numbers_arr);
 }

+char *
+get_setting_name(Oid configid)
+{
+    HeapTuple    tp;
+
+    tp = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(configid));
+    if (HeapTupleIsValid(tp))
+    {
+        Form_pg_setting_acl configtup = (Form_pg_setting_acl) GETSTRUCT(tp);
+        char       *result;
+
+        result = pstrdup(text_to_cstring(&configtup->setting));
+        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 f4e7819f1e..cbcbf02839 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -57,6 +57,7 @@
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_seclabel.h"
 #include "catalog/pg_sequence.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
 #include "catalog/pg_shseclabel.h"
@@ -762,6 +763,28 @@ static const struct cachedesc cacheinfo[] = {
         },
         32
     },
+    {SettingAclRelationId,        /* SETTINGNAME */
+        SettingAclSettingIndexId,
+        1,
+        {
+            Anum_pg_setting_acl_setting,
+            0,
+            0,
+            0
+        },
+        4
+    },
+    {SettingAclRelationId,        /* SETTINGOID */
+        SettingAclOidIndexId,
+        1,
+        {
+            Anum_pg_setting_acl_oid,
+            0,
+            0,
+            0
+        },
+        4
+    },
     {StatisticExtDataRelationId,    /* STATEXTDATASTXOID */
         StatisticExtDataStxoidInhIndexId,
         2,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1e3650184b..2fa38b14af 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,7 +43,9 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
@@ -977,7 +979,7 @@ static const unit_conversion time_unit_conversion_table[] =
 static struct config_bool ConfigureNamesBool[] =
 {
     {
-        {"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_seqscan", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of sequential-scan plans."),
             NULL,
             GUC_EXPLAIN
@@ -987,7 +989,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_indexscan", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of index-scan plans."),
             NULL,
             GUC_EXPLAIN
@@ -997,7 +999,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_indexonlyscan", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of index-only-scan plans."),
             NULL,
             GUC_EXPLAIN
@@ -1007,7 +1009,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_bitmapscan", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of bitmap-scan plans."),
             NULL,
             GUC_EXPLAIN
@@ -1017,7 +1019,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_tidscan", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of TID scan plans."),
             NULL,
             GUC_EXPLAIN
@@ -1027,7 +1029,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_sort", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of explicit sort steps."),
             NULL,
             GUC_EXPLAIN
@@ -1037,7 +1039,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_incremental_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_incremental_sort", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of incremental sort steps."),
             NULL,
             GUC_EXPLAIN
@@ -1047,7 +1049,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_hashagg", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of hashed aggregation plans."),
             NULL,
             GUC_EXPLAIN
@@ -1057,7 +1059,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_material", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of materialization."),
             NULL,
             GUC_EXPLAIN
@@ -1067,7 +1069,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_memoize", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_memoize", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of memoization."),
             NULL,
             GUC_EXPLAIN
@@ -1077,7 +1079,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_nestloop", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of nested-loop join plans."),
             NULL,
             GUC_EXPLAIN
@@ -1087,7 +1089,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_mergejoin", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of merge join plans."),
             NULL,
             GUC_EXPLAIN
@@ -1097,7 +1099,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_hashjoin", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of hash join plans."),
             NULL,
             GUC_EXPLAIN
@@ -1107,7 +1109,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_gathermerge", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of gather merge plans."),
             NULL,
             GUC_EXPLAIN
@@ -1117,7 +1119,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_partitionwise_join", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables partitionwise join."),
             NULL,
             GUC_EXPLAIN
@@ -1127,7 +1129,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_partitionwise_aggregate", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables partitionwise aggregation and grouping."),
             NULL,
             GUC_EXPLAIN
@@ -1137,7 +1139,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_parallel_append", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of parallel append plans."),
             NULL,
             GUC_EXPLAIN
@@ -1147,7 +1149,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_parallel_hash", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of parallel hash plans."),
             NULL,
             GUC_EXPLAIN
@@ -1157,7 +1159,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_partition_pruning", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables plan-time and execution-time partition pruning."),
             gettext_noop("Allows the query planner and executor to compare partition "
                          "bounds to conditions in the query to determine which "
@@ -1169,7 +1171,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"enable_async_append", PGC_USERSET, QUERY_TUNING_METHOD,
+        {"enable_async_append", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enables the planner's use of async append plans."),
             NULL,
             GUC_EXPLAIN
@@ -1179,7 +1181,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("Enables genetic query optimization."),
             gettext_noop("This algorithm attempts to do planning without "
                          "exhaustive searching."),
@@ -1401,7 +1403,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"exit_on_error", PGC_USERSET, ERROR_HANDLING_OPTIONS,
+        {"exit_on_error", PGC_SUSET, ERROR_HANDLING_OPTIONS,
             gettext_noop("Terminate session on any error."),
             NULL
         },
@@ -1439,7 +1441,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"debug_print_parse", PGC_USERSET, LOGGING_WHAT,
+        {"debug_print_parse", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Logs each query's parse tree."),
             NULL
         },
@@ -1448,7 +1450,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"debug_print_rewritten", PGC_USERSET, LOGGING_WHAT,
+        {"debug_print_rewritten", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Logs each query's rewritten parse tree."),
             NULL
         },
@@ -1457,7 +1459,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"debug_print_plan", PGC_USERSET, LOGGING_WHAT,
+        {"debug_print_plan", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Logs each query's execution plan."),
             NULL
         },
@@ -1466,7 +1468,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"debug_pretty_print", PGC_USERSET, LOGGING_WHAT,
+        {"debug_pretty_print", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Indents parse and plan tree displays."),
             NULL
         },
@@ -1587,7 +1589,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"trace_notify", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"trace_notify", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Generates debugging output for LISTEN and NOTIFY."),
             NULL,
             GUC_NOT_IN_SAMPLE
@@ -1671,7 +1673,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
+        {"transform_null_equals", PGC_SUSET, COMPAT_OPTIONS_CLIENT,
             gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
             gettext_noop("When turned on, expressions of the form expr = NULL "
                          "(or NULL = expr) are treated as expr IS NULL, that is, they "
@@ -1693,7 +1695,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_transaction_read_only", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the default read-only status of new transactions."),
             NULL,
             GUC_REPORT
@@ -1703,7 +1705,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"transaction_read_only", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the current transaction's read-only status."),
             NULL,
             GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -1713,7 +1715,7 @@ static struct config_bool ConfigureNamesBool[] =
         check_transaction_read_only, NULL, NULL
     },
     {
-        {"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_transaction_deferrable", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the default deferrable status of new transactions."),
             NULL
         },
@@ -1722,7 +1724,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"transaction_deferrable", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no
possibleserialization failures."), 
             NULL,
             GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -1732,7 +1734,7 @@ static struct config_bool ConfigureNamesBool[] =
         check_transaction_deferrable, NULL, NULL
     },
     {
-        {"row_security", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"row_security", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Enable row security."),
             gettext_noop("When enabled, row security will be applied to all users.")
         },
@@ -1741,7 +1743,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"check_function_bodies", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Check routine bodies during CREATE FUNCTION and CREATE PROCEDURE."),
             NULL
         },
@@ -1750,7 +1752,7 @@ static struct config_bool ConfigureNamesBool[] =
         NULL, NULL, NULL
     },
     {
-        {"array_nulls", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"array_nulls", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("Enable input of NULL elements in arrays."),
             gettext_noop("When turned on, unquoted NULL in an array input "
                          "value means a null value; "
@@ -1767,7 +1769,7 @@ static struct config_bool ConfigureNamesBool[] =
      * avoid unnecessarily breaking older dump files.
      */
     {
-        {"default_with_oids", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"default_with_oids", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("WITH OIDS is no longer supported; this can only be false."),
             NULL,
             GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE
@@ -1797,7 +1799,7 @@ static struct config_bool ConfigureNamesBool[] =

 #ifdef TRACE_SORT
     {
-        {"trace_sort", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"trace_sort", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Emit information about resource usage in sorting."),
             NULL,
             GUC_NOT_IN_SAMPLE
@@ -1811,7 +1813,7 @@ static struct config_bool ConfigureNamesBool[] =
 #ifdef TRACE_SYNCSCAN
     /* this is undocumented because not exposed in a standard build */
     {
-        {"trace_syncscan", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"trace_syncscan", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Generate debugging output for synchronized scanning."),
             NULL,
             GUC_NOT_IN_SAMPLE
@@ -1826,7 +1828,7 @@ static struct config_bool ConfigureNamesBool[] =
     /* this is undocumented because not exposed in a standard build */
     {
         {
-            "optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+            "optimize_bounded_sort", PGC_SUSET, QUERY_TUNING_METHOD,
             gettext_noop("Enable bounded sorting using heap sort."),
             NULL,
             GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
@@ -1872,7 +1874,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"escape_string_warning", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"escape_string_warning", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("Warn about backslash escapes in ordinary string literals."),
             NULL
         },
@@ -1882,7 +1884,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"standard_conforming_strings", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"standard_conforming_strings", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("Causes '...' strings to treat backslashes literally."),
             NULL,
             GUC_REPORT
@@ -1893,7 +1895,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"synchronize_seqscans", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"synchronize_seqscans", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("Enable synchronized sequential scans."),
             NULL
         },
@@ -1989,7 +1991,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"quote_all_identifiers", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"quote_all_identifiers", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("When generating SQL fragments, quote all identifiers."),
             NULL,
         },
@@ -2030,7 +2032,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"parallel_leader_participation", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+        {"parallel_leader_participation", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
             gettext_noop("Controls whether Gather and Gather Merge also run subplans."),
             gettext_noop("Should gather nodes also run subplans or just gather tuples?"),
             GUC_EXPLAIN
@@ -2041,7 +2043,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"jit", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"jit", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Allow JIT compilation."),
             NULL,
             GUC_EXPLAIN
@@ -2080,7 +2082,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"jit_expressions", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"jit_expressions", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Allow JIT compilation of expressions."),
             NULL,
             GUC_NOT_IN_SAMPLE
@@ -2108,7 +2110,7 @@ static struct config_bool ConfigureNamesBool[] =
     },

     {
-        {"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"jit_tuple_deforming", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Allow JIT compilation of tuple deforming."),
             NULL,
             GUC_NOT_IN_SAMPLE
@@ -2168,7 +2170,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"default_statistics_target", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"default_statistics_target", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Sets the default statistics target."),
             gettext_noop("This applies to table columns that have not had a "
                          "column-specific target set via ALTER TABLE SET STATISTICS.")
@@ -2178,7 +2180,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"from_collapse_limit", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Sets the FROM-list size beyond which subqueries "
                          "are not collapsed."),
             gettext_noop("The planner will merge subqueries into upper "
@@ -2191,7 +2193,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"join_collapse_limit", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Sets the FROM-list size beyond which JOIN "
                          "constructs are not flattened."),
             gettext_noop("The planner will flatten explicit JOIN "
@@ -2204,7 +2206,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_threshold", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."),
             NULL,
             GUC_EXPLAIN
@@ -2214,7 +2216,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_effort", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("GEQO: effort is used to set the default for other GEQO parameters."),
             NULL,
             GUC_EXPLAIN
@@ -2224,7 +2226,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_pool_size", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("GEQO: number of individuals in the population."),
             gettext_noop("Zero selects a suitable default value."),
             GUC_EXPLAIN
@@ -2234,7 +2236,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_generations", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("GEQO: number of iterations of the algorithm."),
             gettext_noop("Zero selects a suitable default value."),
             GUC_EXPLAIN
@@ -2381,7 +2383,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"temp_buffers", PGC_USERSET, RESOURCES_MEM,
+        {"temp_buffers", PGC_SUSET, RESOURCES_MEM,
             gettext_noop("Sets the maximum number of temporary buffers used by each session."),
             NULL,
             GUC_UNIT_BLOCKS | GUC_EXPLAIN
@@ -2446,7 +2448,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"work_mem", PGC_USERSET, RESOURCES_MEM,
+        {"work_mem", PGC_SUSET, RESOURCES_MEM,
             gettext_noop("Sets the maximum memory to be used for query workspaces."),
             gettext_noop("This much memory can be used by each internal "
                          "sort operation and hash table before switching to "
@@ -2459,7 +2461,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"maintenance_work_mem", PGC_USERSET, RESOURCES_MEM,
+        {"maintenance_work_mem", PGC_SUSET, RESOURCES_MEM,
             gettext_noop("Sets the maximum memory to be used for maintenance operations."),
             gettext_noop("This includes operations such as VACUUM and CREATE INDEX."),
             GUC_UNIT_KB
@@ -2470,7 +2472,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"logical_decoding_work_mem", PGC_USERSET, RESOURCES_MEM,
+        {"logical_decoding_work_mem", PGC_SUSET, RESOURCES_MEM,
             gettext_noop("Sets the maximum memory to be used for logical decoding."),
             gettext_noop("This much memory can be used by each internal "
                          "reorder buffer before spilling to disk."),
@@ -2509,7 +2511,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_cost_page_hit", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+        {"vacuum_cost_page_hit", PGC_SUSET, RESOURCES_VACUUM_DELAY,
             gettext_noop("Vacuum cost for a page found in the buffer cache."),
             NULL
         },
@@ -2519,7 +2521,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_cost_page_miss", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+        {"vacuum_cost_page_miss", PGC_SUSET, RESOURCES_VACUUM_DELAY,
             gettext_noop("Vacuum cost for a page not found in the buffer cache."),
             NULL
         },
@@ -2529,7 +2531,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_cost_page_dirty", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+        {"vacuum_cost_page_dirty", PGC_SUSET, RESOURCES_VACUUM_DELAY,
             gettext_noop("Vacuum cost for a page dirtied by vacuum."),
             NULL
         },
@@ -2539,7 +2541,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_cost_limit", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+        {"vacuum_cost_limit", PGC_SUSET, RESOURCES_VACUUM_DELAY,
             gettext_noop("Vacuum cost amount available before napping."),
             NULL
         },
@@ -2605,7 +2607,7 @@ static struct config_int ConfigureNamesInt[] =
 #endif

     {
-        {"statement_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"statement_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum allowed duration of any statement."),
             gettext_noop("A value of 0 turns off the timeout."),
             GUC_UNIT_MS
@@ -2616,7 +2618,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"lock_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"lock_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum allowed duration of any wait for a lock."),
             gettext_noop("A value of 0 turns off the timeout."),
             GUC_UNIT_MS
@@ -2627,7 +2629,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"idle_in_transaction_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"idle_in_transaction_session_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum allowed idle time between queries, when in a transaction."),
             gettext_noop("A value of 0 turns off the timeout."),
             GUC_UNIT_MS
@@ -2638,7 +2640,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"idle_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"idle_session_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum allowed idle time between queries, when not in a transaction."),
             gettext_noop("A value of 0 turns off the timeout."),
             GUC_UNIT_MS
@@ -2649,7 +2651,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_freeze_min_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Minimum age at which VACUUM should freeze a table row."),
             NULL
         },
@@ -2659,7 +2661,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_freeze_table_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
             NULL
         },
@@ -2669,7 +2671,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_multixact_freeze_min_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
             NULL
         },
@@ -2679,7 +2681,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_multixact_freeze_table_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
             NULL
         },
@@ -2698,7 +2700,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_failsafe_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
             NULL
         },
@@ -2707,7 +2709,7 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
     {
-        {"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"vacuum_multixact_failsafe_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
             NULL
         },
@@ -2895,7 +2897,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"wal_skip_threshold", PGC_USERSET, WAL_SETTINGS,
+        {"wal_skip_threshold", PGC_SUSET, WAL_SETTINGS,
             gettext_noop("Minimum size of new file to fsync instead of writing WAL."),
             NULL,
             GUC_UNIT_KB
@@ -2940,7 +2942,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"wal_sender_timeout", PGC_USERSET, REPLICATION_SENDING,
+        {"wal_sender_timeout", PGC_SUSET, REPLICATION_SENDING,
             gettext_noop("Sets the maximum time to wait for WAL replication."),
             NULL,
             GUC_UNIT_MS
@@ -2963,7 +2965,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"commit_siblings", PGC_USERSET, WAL_SETTINGS,
+        {"commit_siblings", PGC_SUSET, WAL_SETTINGS,
             gettext_noop("Sets the minimum number of concurrent open transactions "
                          "required before performing commit_delay."),
             NULL
@@ -2974,7 +2976,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"extra_float_digits", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"extra_float_digits", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the number of digits displayed for floating-point values."),
             gettext_noop("This affects real, double precision, and geometric data types. "
                          "A zero or negative parameter value is added to the standard "
@@ -3036,7 +3038,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"log_parameter_max_length_on_error", PGC_USERSET, LOGGING_WHAT,
+        {"log_parameter_max_length_on_error", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Sets the maximum length in bytes of data logged for bind "
                          "parameter values when logging statements, on error."),
             gettext_noop("-1 to print values in full."),
@@ -3081,7 +3083,7 @@ static struct config_int ConfigureNamesInt[] =

     {
         {"effective_io_concurrency",
-            PGC_USERSET,
+            PGC_SUSET,
             RESOURCES_ASYNCHRONOUS,
             gettext_noop("Number of simultaneous requests that can be handled efficiently by the disk subsystem."),
             NULL,
@@ -3099,7 +3101,7 @@ static struct config_int ConfigureNamesInt[] =

     {
         {"maintenance_io_concurrency",
-            PGC_USERSET,
+            PGC_SUSET,
             RESOURCES_ASYNCHRONOUS,
             gettext_noop("A variant of effective_io_concurrency that is used for maintenance work."),
             NULL,
@@ -3116,7 +3118,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+        {"backend_flush_after", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
             gettext_noop("Number of pages after which previously performed writes are flushed to disk."),
             NULL,
             GUC_UNIT_BLOCKS
@@ -3348,7 +3350,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"max_parallel_maintenance_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+        {"max_parallel_maintenance_workers", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
             gettext_noop("Sets the maximum number of parallel processes per maintenance operation."),
             NULL
         },
@@ -3358,7 +3360,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"max_parallel_workers_per_gather", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+        {"max_parallel_workers_per_gather", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
             gettext_noop("Sets the maximum number of parallel processes per executor node."),
             NULL,
             GUC_EXPLAIN
@@ -3369,7 +3371,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"max_parallel_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+        {"max_parallel_workers", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
             gettext_noop("Sets the maximum number of parallel workers that can be active at one time."),
             NULL,
             GUC_EXPLAIN
@@ -3402,7 +3404,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"tcp_keepalives_idle", PGC_USERSET, CONN_AUTH_SETTINGS,
+        {"tcp_keepalives_idle", PGC_SUSET, CONN_AUTH_SETTINGS,
             gettext_noop("Time between issuing TCP keepalives."),
             gettext_noop("A value of 0 uses the system default."),
             GUC_UNIT_S
@@ -3413,7 +3415,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"tcp_keepalives_interval", PGC_USERSET, CONN_AUTH_SETTINGS,
+        {"tcp_keepalives_interval", PGC_SUSET, CONN_AUTH_SETTINGS,
             gettext_noop("Time between TCP keepalive retransmits."),
             gettext_noop("A value of 0 uses the system default."),
             GUC_UNIT_S
@@ -3424,7 +3426,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"ssl_renegotiation_limit", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"ssl_renegotiation_limit", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("SSL renegotiation is no longer supported; this can only be 0."),
             NULL,
             GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE,
@@ -3435,7 +3437,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"tcp_keepalives_count", PGC_USERSET, CONN_AUTH_SETTINGS,
+        {"tcp_keepalives_count", PGC_SUSET, CONN_AUTH_SETTINGS,
             gettext_noop("Maximum number of TCP keepalive retransmits."),
             gettext_noop("This controls the number of consecutive keepalive retransmits that can be "
                          "lost before a connection is considered dead. A value of 0 uses the "
@@ -3447,7 +3449,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"gin_fuzzy_search_limit", PGC_USERSET, CLIENT_CONN_OTHER,
+        {"gin_fuzzy_search_limit", PGC_SUSET, CLIENT_CONN_OTHER,
             gettext_noop("Sets the maximum allowed result for exact search by GIN."),
             NULL,
             0
@@ -3458,7 +3460,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"effective_cache_size", PGC_USERSET, QUERY_TUNING_COST,
+        {"effective_cache_size", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's assumption about the total size of the data caches."),
             gettext_noop("That is, the total size of the caches (kernel cache and shared buffers) used for PostgreSQL
datafiles. " 
                          "This is measured in disk pages, which are normally 8 kB each."),
@@ -3470,7 +3472,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"min_parallel_table_scan_size", PGC_USERSET, QUERY_TUNING_COST,
+        {"min_parallel_table_scan_size", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the minimum amount of table data for a parallel scan."),
             gettext_noop("If the planner estimates that it will read a number of table pages too small to reach this
limit,a parallel scan will not be considered."), 
             GUC_UNIT_BLOCKS | GUC_EXPLAIN,
@@ -3481,7 +3483,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"min_parallel_index_scan_size", PGC_USERSET, QUERY_TUNING_COST,
+        {"min_parallel_index_scan_size", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the minimum amount of index data for a parallel scan."),
             gettext_noop("If the planner estimates that it will read a number of index pages too small to reach this
limit,a parallel scan will not be considered."), 
             GUC_UNIT_BLOCKS | GUC_EXPLAIN,
@@ -3526,7 +3528,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"gin_pending_list_limit", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"gin_pending_list_limit", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum size of the pending list for GIN index."),
             NULL,
             GUC_UNIT_KB
@@ -3537,7 +3539,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"tcp_user_timeout", PGC_USERSET, CONN_AUTH_SETTINGS,
+        {"tcp_user_timeout", PGC_SUSET, CONN_AUTH_SETTINGS,
             gettext_noop("TCP user timeout."),
             gettext_noop("A value of 0 uses the system default."),
             GUC_UNIT_MS
@@ -3582,7 +3584,7 @@ static struct config_int ConfigureNamesInt[] =
     },

     {
-        {"client_connection_check_interval", PGC_USERSET, CONN_AUTH_SETTINGS,
+        {"client_connection_check_interval", PGC_SUSET, CONN_AUTH_SETTINGS,
             gettext_noop("Sets the time interval between checks for disconnection while running queries."),
             NULL,
             GUC_UNIT_MS
@@ -3614,7 +3616,7 @@ static struct config_int ConfigureNamesInt[] =
 static struct config_real ConfigureNamesReal[] =
 {
     {
-        {"seq_page_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"seq_page_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of a "
                          "sequentially fetched disk page."),
             NULL,
@@ -3625,7 +3627,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"random_page_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"random_page_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of a "
                          "nonsequentially fetched disk page."),
             NULL,
@@ -3636,7 +3638,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"cpu_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"cpu_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of "
                          "processing each tuple (row)."),
             NULL,
@@ -3647,7 +3649,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"cpu_index_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"cpu_index_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of "
                          "processing each index entry during an index scan."),
             NULL,
@@ -3658,7 +3660,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"cpu_operator_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"cpu_operator_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of "
                          "processing each operator or function call."),
             NULL,
@@ -3669,7 +3671,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"parallel_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"parallel_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of "
                          "passing each tuple (row) from worker to leader backend."),
             NULL,
@@ -3680,7 +3682,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"parallel_setup_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"parallel_setup_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Sets the planner's estimate of the cost of "
                          "starting up worker processes for parallel query."),
             NULL,
@@ -3692,7 +3694,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"jit_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"jit_above_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Perform JIT compilation if query is more expensive."),
             gettext_noop("-1 disables JIT compilation."),
             GUC_EXPLAIN
@@ -3703,7 +3705,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"jit_optimize_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"jit_optimize_above_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Optimize JIT-compiled functions if query is more expensive."),
             gettext_noop("-1 disables optimization."),
             GUC_EXPLAIN
@@ -3714,7 +3716,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"jit_inline_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+        {"jit_inline_above_cost", PGC_SUSET, QUERY_TUNING_COST,
             gettext_noop("Perform JIT inlining if query is more expensive."),
             gettext_noop("-1 disables inlining."),
             GUC_EXPLAIN
@@ -3725,7 +3727,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"cursor_tuple_fraction", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"cursor_tuple_fraction", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Sets the planner's estimate of the fraction of "
                          "a cursor's rows that will be retrieved."),
             NULL,
@@ -3737,7 +3739,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"geqo_selection_bias", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_selection_bias", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("GEQO: selective pressure within the population."),
             NULL,
             GUC_EXPLAIN
@@ -3748,7 +3750,7 @@ static struct config_real ConfigureNamesReal[] =
         NULL, NULL, NULL
     },
     {
-        {"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO,
+        {"geqo_seed", PGC_SUSET, QUERY_TUNING_GEQO,
             gettext_noop("GEQO: seed for random path selection."),
             NULL,
             GUC_EXPLAIN
@@ -3759,7 +3761,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"hash_mem_multiplier", PGC_USERSET, RESOURCES_MEM,
+        {"hash_mem_multiplier", PGC_SUSET, RESOURCES_MEM,
             gettext_noop("Multiple of work_mem to use for hash tables."),
             NULL,
             GUC_EXPLAIN
@@ -3780,7 +3782,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"seed", PGC_USERSET, UNGROUPED,
+        {"seed", PGC_SUSET, UNGROUPED,
             gettext_noop("Sets the seed for random-number generation."),
             NULL,
             GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -3791,7 +3793,7 @@ static struct config_real ConfigureNamesReal[] =
     },

     {
-        {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+        {"vacuum_cost_delay", PGC_SUSET, RESOURCES_VACUUM_DELAY,
             gettext_noop("Vacuum cost delay in milliseconds."),
             NULL,
             GUC_UNIT_MS
@@ -4051,7 +4053,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"DateStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"DateStyle", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the display format for date and time values."),
             gettext_noop("Also controls interpretation of ambiguous "
                          "date inputs."),
@@ -4063,7 +4065,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"default_table_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_table_access_method", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the default table access method for new tables."),
             NULL,
             GUC_IS_NAME
@@ -4074,7 +4076,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_tablespace", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the default tablespace to create tables and indexes in."),
             gettext_noop("An empty string selects the database's default tablespace."),
             GUC_IS_NAME
@@ -4085,7 +4087,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"temp_tablespaces", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"temp_tablespaces", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the tablespace(s) to use for temporary tables and sort files."),
             NULL,
             GUC_LIST_INPUT | GUC_LIST_QUOTE
@@ -4165,7 +4167,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"lc_monetary", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"lc_monetary", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the locale for formatting monetary amounts."),
             NULL
         },
@@ -4175,7 +4177,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"lc_numeric", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"lc_numeric", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the locale for formatting numbers."),
             NULL
         },
@@ -4185,7 +4187,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"lc_time", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"lc_time", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the locale for formatting date and time values."),
             NULL
         },
@@ -4217,7 +4219,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"local_preload_libraries", PGC_USERSET, CLIENT_CONN_PRELOAD,
+        {"local_preload_libraries", PGC_SUSET, CLIENT_CONN_PRELOAD,
             gettext_noop("Lists unprivileged shared libraries to preload into each backend."),
             NULL,
             GUC_LIST_INPUT | GUC_LIST_QUOTE
@@ -4228,7 +4230,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"search_path", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"search_path", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the schema search order for names that are not schema-qualified."),
             NULL,
             GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_EXPLAIN
@@ -4264,7 +4266,7 @@ static struct config_string ConfigureNamesString[] =

     {
         /* Not for general use --- used by SET ROLE */
-        {"role", PGC_USERSET, UNGROUPED,
+        {"role", PGC_SUSET, UNGROUPED,
             gettext_noop("Sets the current role."),
             NULL,
             GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE |
GUC_NOT_WHILE_SEC_REST
@@ -4343,7 +4345,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"TimeZone", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"TimeZone", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the time zone for displaying and interpreting time stamps."),
             NULL,
             GUC_REPORT
@@ -4353,7 +4355,7 @@ static struct config_string ConfigureNamesString[] =
         check_timezone, assign_timezone, show_timezone
     },
     {
-        {"timezone_abbreviations", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"timezone_abbreviations", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Selects a file of time zone abbreviations."),
             NULL
         },
@@ -4546,7 +4548,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"default_text_search_config", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"default_text_search_config", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets default text search configuration."),
             NULL
         },
@@ -4608,7 +4610,7 @@ static struct config_string ConfigureNamesString[] =
     },

     {
-        {"application_name", PGC_USERSET, LOGGING_WHAT,
+        {"application_name", PGC_SUSET, LOGGING_WHAT,
             gettext_noop("Sets the application name to be reported in statistics and logs."),
             NULL,
             GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE
@@ -4672,7 +4674,7 @@ static struct config_string ConfigureNamesString[] =
 static struct config_enum ConfigureNamesEnum[] =
 {
     {
-        {"backslash_quote", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+        {"backslash_quote", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
             gettext_noop("Sets whether \"\\'\" is allowed in string literals."),
             NULL
         },
@@ -4682,7 +4684,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"bytea_output", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"bytea_output", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the output format for bytea."),
             NULL
         },
@@ -4692,7 +4694,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"client_min_messages", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"client_min_messages", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the message levels that are sent to the client."),
             gettext_noop("Each level includes all the levels that follow it. The later"
                          " the level, the fewer messages are sent.")
@@ -4713,7 +4715,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"constraint_exclusion", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Enables the planner to use constraints to optimize queries."),
             gettext_noop("Table scans will be skipped if their constraints"
                          " guarantee that no rows match the query."),
@@ -4725,7 +4727,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"default_toast_compression", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_toast_compression", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the default compression method for compressible values."),
             NULL
         },
@@ -4736,7 +4738,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"default_transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"default_transaction_isolation", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the transaction isolation level of each new transaction."),
             NULL
         },
@@ -4746,7 +4748,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"transaction_isolation", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the current transaction's isolation level."),
             NULL,
             GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -4757,7 +4759,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
+        {"IntervalStyle", PGC_SUSET, CLIENT_CONN_LOCALE,
             gettext_noop("Sets the display format for interval values."),
             NULL,
             GUC_REPORT
@@ -4835,7 +4837,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"synchronous_commit", PGC_USERSET, WAL_SETTINGS,
+        {"synchronous_commit", PGC_SUSET, WAL_SETTINGS,
             gettext_noop("Sets the current transaction's synchronization level."),
             NULL
         },
@@ -4942,7 +4944,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"xmlbinary", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"xmlbinary", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets how binary values are to be encoded in XML."),
             NULL
         },
@@ -4952,7 +4954,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"xmloption", PGC_USERSET, CLIENT_CONN_STATEMENT,
+        {"xmloption", PGC_SUSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets whether XML data in implicit parsing and serialization "
                          "operations is to be considered as documents or content fragments."),
             NULL
@@ -4973,7 +4975,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"force_parallel_mode", PGC_USERSET, DEVELOPER_OPTIONS,
+        {"force_parallel_mode", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Forces use of parallel query facilities."),
             gettext_noop("If possible, run query using a parallel worker and with parallel restrictions."),
             GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
@@ -4984,7 +4986,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"password_encryption", PGC_USERSET, CONN_AUTH_AUTH,
+        {"password_encryption", PGC_SUSET, CONN_AUTH_AUTH,
             gettext_noop("Chooses the algorithm for encrypting passwords."),
             NULL
         },
@@ -4994,7 +4996,7 @@ static struct config_enum ConfigureNamesEnum[] =
     },

     {
-        {"plan_cache_mode", PGC_USERSET, QUERY_TUNING_OTHER,
+        {"plan_cache_mode", PGC_SUSET, QUERY_TUNING_OTHER,
             gettext_noop("Controls the planner's selection of custom or generic plan."),
             gettext_noop("Prepared statements can have custom and generic plans, and the planner "
                          "will attempt to choose which is better.  This can be set to override "
@@ -5053,6 +5055,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_setting_acl
+ * entries.
  */
 static const char *const map_old_guc_names[] = {
     "sort_mem", "work_mem",
@@ -5452,25 +5458,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"
@@ -5487,8 +5497,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);
 }

 /*
@@ -7542,6 +7568,24 @@ set_config_option(const char *name, const char *value,
         case PGC_SUSET:
             if (context == PGC_USERSET || context == PGC_BACKEND)
             {
+                /*
+                 * Check whether the current user has granted privilege to set
+                 * this GUC.
+                 */
+                Oid            settingid = get_setting_oid(name, true);
+
+                if (OidIsValid(settingid))
+                {
+                    AclResult    aclresult;
+
+                    aclresult = pg_setting_acl_aclcheck(settingid, GetUserId(),
+                                                        ACL_SET_VALUE);
+
+                    if (aclresult == ACLCHECK_OK)
+                        break;        /* okay */
+                }
+
+                /* No granted privilege */
                 ereport(elevel,
                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                          errmsg("permission denied to set parameter \"%s\"",
@@ -8584,6 +8628,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 {
     char       *name;
     char       *value;
+    Oid            settingId = InvalidOid;
     bool        resetall = false;
     ConfigVariable *head = NULL;
     ConfigVariable *tail = NULL;
@@ -8591,16 +8636,31 @@ 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;
+
+        settingId = get_setting_oid(name, true);
+        if (!OidIsValid(settingId))
+            ereport(ERROR,
+                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                     errmsg("permission denied to set parameter \"%s\"",
+                            name)));
+
+        aclresult = pg_setting_acl_aclcheck(settingId, GetUserId(),
+                                             ACL_ALTER_SYSTEM);
+        if (aclresult != ACLCHECK_OK)
+            aclcheck_error(aclresult, OBJECT_SETTING, name);
+    }
+
     switch (altersysstmt->setstmt->kind)
     {
         case VAR_SET_VALUE:
@@ -8733,6 +8793,21 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
         replace_auto_config_value(&head, &tail, name, value);
     }

+    /*
+     * Invoke the post-alter hook for setting this GUC variable.  Guc variables
+     * do not always have corresponding entries in pg_setting_acl; the hooks
+     * must therefore be prepared to receive settingId = InvalidOid.  We also
+     * abuse the notion of subId to pass the kind of alteration (set vs. alter
+     * system), and auxiliaryId to pass the VariableSetKind.
+     *
+     * We do this here rather than at the end, because ALTER SYSTEM is not
+     * transactional.  If the hook aborts our transaction, it will be cleaner
+     * to do so before we touch any files.
+     */
+    InvokeObjectPostAlterHookArg(SettingAclRelationId, settingId,
+                                 ACL_ALTER_SYSTEM, altersysstmt->setstmt->kind,
+                                 false);
+
     /*
      * To ensure crash safety, first write the new file data to a temp file,
      * then atomically rename it into place.
@@ -8793,6 +8868,9 @@ void
 ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 {
     GucAction    action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET;
+    GucContext    context;
+    AclResult    aclresult;
+    Oid            settingId;

     /*
      * Workers synchronize these parameters at the start of the parallel
@@ -8803,6 +8881,24 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
                 (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
                  errmsg("cannot set parameters during a parallel operation")));

+    /* Get the Oid of this setting, or InvalidOid if none. */
+    settingId = get_setting_oid(stmt->name, true);
+
+    /*
+     * 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(settingId))
+    {
+        aclresult = pg_setting_acl_aclcheck(settingId, GetUserId(),
+                                            ACL_SET_VALUE);
+        if (aclresult == ACLCHECK_OK)
+            context = PGC_SUSET;
+    }
+
     switch (stmt->kind)
     {
         case VAR_SET_VALUE:
@@ -8811,7 +8907,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;
@@ -8896,7 +8992,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;
@@ -8904,6 +9000,16 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
             ResetAllOptions();
             break;
     }
+
+    /*
+     * Invoke the post-alter hook for setting this GUC variable.  Guc variables
+     * do not always have corresponding entries in pg_setting_acl; the hooks
+     * must therefore be prepared to receive settingId = InvalidOid.  We also
+     * abuse the notion of subId to pass the kind of alteration (set vs. alter
+     * system), and auxiliaryId to pass the VariableSetKind.
+     */
+    InvokeObjectPostAlterHookArg(SettingAclRelationId, settingId,
+                                 ACL_SET_VALUE, stmt->kind, false);
 }

 /*
@@ -9664,6 +9770,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/initdb/initdb.c b/src/bin/initdb/initdb.c
index 97f15971e2..3138722c19 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -164,6 +164,7 @@ static char *features_file;
 static char *system_constraints_file;
 static char *system_functions_file;
 static char *system_views_file;
+static char *setting_privileges_file;
 static bool success = false;
 static bool made_new_pgdata = false;
 static bool found_existing_pgdata = false;
@@ -2464,6 +2465,7 @@ setup_data_file_paths(void)
     set_input(&system_constraints_file, "system_constraints.sql");
     set_input(&system_functions_file, "system_functions.sql");
     set_input(&system_views_file, "system_views.sql");
+    set_input(&setting_privileges_file, "setting_privileges.sql");

     if (show_setting || debug)
     {
@@ -2492,6 +2494,7 @@ setup_data_file_paths(void)
     check_input(system_constraints_file);
     check_input(system_functions_file);
     check_input(system_views_file);
+    check_input(setting_privileges_file);
 }


@@ -2839,6 +2842,8 @@ initialize_data_directory(void)

     setup_run_file(cmdfd, system_views_file);

+    setup_run_file(cmdfd, setting_privileges_file);
+
     setup_description(cmdfd);

     setup_collation(cmdfd);
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6086d57cf3..a84efd79e5 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, SETTING or LARGE OBJECT)
  *    acls: the ACL string fetched from the database
  *    baseacls: the initial ACL string for this object
  *    owner: username of object owner (will be passed through fmtId); can be
@@ -501,6 +501,11 @@ do { \
         CONVERT_PRIV('U', "USAGE");
     else if (strcmp(type, "FOREIGN TABLE") == 0)
         CONVERT_PRIV('r', "SELECT");
+    else if (strcmp(type, "SETTING") == 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 d41a99d6ea..bb0badca65 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3435,6 +3435,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
         strcmp(type, "SCHEMA") == 0 ||
         strcmp(type, "EVENT TRIGGER") == 0 ||
         strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+        strcmp(type, "SETTING") == 0 ||
         strcmp(type, "SERVER") == 0 ||
         strcmp(type, "PUBLICATION") == 0 ||
         strcmp(type, "SUBSCRIPTION") == 0 ||
@@ -3618,6 +3619,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, "SETTING") == 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 e69dcf8a48..dea1b89ff8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -14166,6 +14166,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
         case DEFACLOBJ_NAMESPACE:
             type = "SCHEMAS";
             break;
+        case DEFACLOBJ_SETTING:
+            type = "SETTINGS";
+            break;
         default:
             /* shouldn't get here */
             fatal("unrecognized object type in default privileges: %d",
@@ -14209,7 +14212,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, SETTING 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 9c9f7c6d63..b2cf8cbe76 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 dropTablespaces(PGconn *conn);
 static void dumpTablespaces(PGconn *conn);
 static void dropDBs(PGconn *conn);
@@ -585,6 +586,10 @@ main(int argc, char *argv[])

             /* Dump role memberships */
             dumpRoleMembership(conn);
+
+            /* Dump role guc privileges */
+            if (server_version >= 150000)
+                dumpRoleGUCPrivs(conn);
         }

         /* Dump tablespaces */
@@ -1024,6 +1029,72 @@ dropTablespaces(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), "
+                      "set_acl.setting, "
+                      "grantee.rolname AS grantee, "
+                      "acl.is_grantable, "
+                      "grantor.rolname AS grantor "
+                      "FROM pg_catalog.pg_setting_acl set_acl, "
+                      "LATERAL (SELECT * FROM aclexplode(set_acl.setacl)) 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 setting, 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       *setting = 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", setting);
+        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 tablespaces.
  */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..a0c3966da9 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_SETTING,                /* pg_setting_acl */
     OCLASS_CONSTRAINT,            /* pg_constraint */
     OCLASS_CONVERSION,            /* pg_conversion */
     OCLASS_DEFAULT,                /* pg_attrdef */
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 2a79155636..12ec9b0a31 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_SETTING    'c' /* configuration parameter */

 #endif                            /* EXPOSE_TO_CLIENT_CODE */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1..ebae1f4f65 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7207,6 +7207,25 @@
   proname => 'has_type_privilege', provolatile => 's', prorettype => 'bool',
   proargtypes => 'oid text', prosrc => 'has_type_privilege_id' },

+{ oid => '8050', descr => 'user privilege on setting by username, setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name text text', prosrc => 'has_setting_privilege_name_name' },
+{ oid => '8051', descr => 'user privilege on setting by username, setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name oid text', prosrc => 'has_setting_privilege_name_id' },
+{ oid => '8052', descr => 'user privilege on setting by user oid, setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text text', prosrc => 'has_setting_privilege_id_name' },
+{ oid => '8053', descr => 'user privilege on setting by user oid, setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid oid text', prosrc => 'has_setting_privilege_id_id' },
+{ oid => '8054', descr => 'current user privilege on setting by setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'text text', prosrc => 'has_setting_privilege_name' },
+{ oid => '8055', descr => 'current user privilege on setting by setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text', prosrc => 'has_setting_privilege_id' },
+
 { oid => '2705', descr => 'user privilege on role by username, role name',
   proname => 'pg_has_role', provolatile => 's', prorettype => 'bool',
   proargtypes => 'name name text', prosrc => 'pg_has_role_name_name' },
diff --git a/src/include/catalog/pg_setting_acl.h b/src/include/catalog/pg_setting_acl.h
new file mode 100644
index 0000000000..b7dee55e5c
--- /dev/null
+++ b/src/include/catalog/pg_setting_acl.h
@@ -0,0 +1,63 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_setting_acl.h
+ *      definition of the "configuration parameter" system catalog
+ *      (pg_setting_acl).
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_setting_acl.h
+ *
+ * NOTES
+ *      The Catalog.pm module reads this file and derives schema
+ *      information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SETTING_ACL_H
+#define PG_SETTING_ACL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_setting_acl_d.h"
+
+/* ----------------
+ *        pg_setting_acl definition.  cpp turns this into
+ *        typedef struct FormData_pg_setting_acl
+ * ----------------
+ */
+CATALOG(pg_setting_acl,8924,SettingAclRelationId) BKI_SHARED_RELATION
+{
+    Oid            oid;            /* oid */
+    /*
+
+     * Variable-length fields start here, but we allow direct access to
+     * setting.
+     */
+    text        setting BKI_FORCE_NOT_NULL;
+
+#ifdef CATALOG_VARLEN
+    /* Access privileges */
+    aclitem        setacl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_setting_acl;
+
+
+/* ----------------
+ *        Form_pg_setting_acl corresponds to a pointer to a tuple with
+ *        the format of pg_setting_acl relation.
+ * ----------------
+ */
+typedef FormData_pg_setting_acl *Form_pg_setting_acl;
+
+DECLARE_TOAST(pg_setting_acl, 8925, 8926);
+#define PgSettingAclToastTable 8925
+#define PgSettingAclToastIndex 8926
+
+DECLARE_UNIQUE_INDEX(pg_setting_acl_setting_index, 8927, SettingAclSettingIndexId, on pg_setting_acl using
btree(settingtext_ops)); 
+DECLARE_UNIQUE_INDEX_PKEY(pg_setting_acl_oid_index, 8928, SettingAclOidIndexId, on pg_setting_acl using btree(oid
oid_ops));
+
+extern Oid    SettingAclCreate(const char *setting, bool if_not_exists);
+
+#endif                            /* PG_SETTING_ACL_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..5f5e4ae8e1 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_VALUE    (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
@@ -1795,6 +1797,7 @@ typedef enum ObjectType
     OBJECT_CAST,
     OBJECT_COLUMN,
     OBJECT_COLLATION,
+    OBJECT_SETTING,
     OBJECT_CONVERSION,
     OBJECT_DATABASE,
     OBJECT_DEFAULT,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..f5ed8c2082 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/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..e6d7761643 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_VALUE_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_SETTING    (ACL_SET_VALUE|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_setting_oid(const char *setting, 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_setting_acl_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_setting_acl_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 ea774968f0..2d0a1a53f7 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -382,6 +382,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 b8dd27d4a9..7a8ba82770 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_setting_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 9c1a76e8bb..5961921462 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -89,6 +89,8 @@ enum SysCacheIdentifier
     REPLORIGNAME,
     RULERELNAME,
     SEQRELID,
+    SETTINGNAME,
+    SETTINGOID,
     STATEXTDATASTXOID,
     STATEXTNAMENSP,
     STATEXTOID,
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 c73bd37835..7f8984bc45 100644
--- a/src/test/modules/test_pg_dump/t/001_base.pl
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -317,6 +317,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_roleGRANTED 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..c2033e77d1
--- /dev/null
+++ b/src/test/regress/expected/guc_privs.out
@@ -0,0 +1,780 @@
+-- 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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;
+-- Check setting privileges prior to creating any
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+ grantee |               setting               | privilege_type | is_grantable
+---------+-------------------------------------+----------------+--------------
+         | DateStyle                           | SET VALUE      | f
+         | IntervalStyle                       | SET VALUE      | f
+         | TimeZone                            | SET VALUE      | f
+         | application_name                    | SET VALUE      | f
+         | array_nulls                         | SET VALUE      | f
+         | backend_flush_after                 | SET VALUE      | f
+         | backslash_quote                     | SET VALUE      | f
+         | bytea_output                        | SET VALUE      | f
+         | check_function_bodies               | SET VALUE      | f
+         | client_connection_check_interval    | SET VALUE      | f
+         | client_min_messages                 | SET VALUE      | f
+         | commit_siblings                     | SET VALUE      | f
+         | constraint_exclusion                | SET VALUE      | f
+         | cpu_index_tuple_cost                | SET VALUE      | f
+         | cpu_operator_cost                   | SET VALUE      | f
+         | cpu_tuple_cost                      | SET VALUE      | f
+         | cursor_tuple_fraction               | SET VALUE      | f
+         | debug_pretty_print                  | SET VALUE      | f
+         | debug_print_parse                   | SET VALUE      | f
+         | debug_print_plan                    | SET VALUE      | f
+         | debug_print_rewritten               | SET VALUE      | f
+         | default_statistics_target           | SET VALUE      | f
+         | default_table_access_method         | SET VALUE      | f
+         | default_tablespace                  | SET VALUE      | f
+         | default_text_search_config          | SET VALUE      | f
+         | default_toast_compression           | SET VALUE      | f
+         | default_transaction_deferrable      | SET VALUE      | f
+         | default_transaction_isolation       | SET VALUE      | f
+         | default_transaction_read_only       | SET VALUE      | f
+         | default_with_oids                   | SET VALUE      | f
+         | effective_cache_size                | SET VALUE      | f
+         | effective_io_concurrency            | SET VALUE      | f
+         | enable_async_append                 | SET VALUE      | f
+         | enable_bitmapscan                   | SET VALUE      | f
+         | enable_gathermerge                  | SET VALUE      | f
+         | enable_hashagg                      | SET VALUE      | f
+         | enable_hashjoin                     | SET VALUE      | f
+         | enable_incremental_sort             | SET VALUE      | f
+         | enable_indexonlyscan                | SET VALUE      | f
+         | enable_indexscan                    | SET VALUE      | f
+         | enable_material                     | SET VALUE      | f
+         | enable_memoize                      | SET VALUE      | f
+         | enable_mergejoin                    | SET VALUE      | f
+         | enable_nestloop                     | SET VALUE      | f
+         | enable_parallel_append              | SET VALUE      | f
+         | enable_parallel_hash                | SET VALUE      | f
+         | enable_partition_pruning            | SET VALUE      | f
+         | enable_partitionwise_aggregate      | SET VALUE      | f
+         | enable_partitionwise_join           | SET VALUE      | f
+         | enable_seqscan                      | SET VALUE      | f
+         | enable_sort                         | SET VALUE      | f
+         | enable_tidscan                      | SET VALUE      | f
+         | escape_string_warning               | SET VALUE      | f
+         | exit_on_error                       | SET VALUE      | f
+         | extra_float_digits                  | SET VALUE      | f
+         | force_parallel_mode                 | SET VALUE      | f
+         | from_collapse_limit                 | SET VALUE      | f
+         | geqo                                | SET VALUE      | f
+         | geqo_effort                         | SET VALUE      | f
+         | geqo_generations                    | SET VALUE      | f
+         | geqo_pool_size                      | SET VALUE      | f
+         | geqo_seed                           | SET VALUE      | f
+         | geqo_selection_bias                 | SET VALUE      | f
+         | geqo_threshold                      | SET VALUE      | f
+         | gin_fuzzy_search_limit              | SET VALUE      | f
+         | gin_pending_list_limit              | SET VALUE      | f
+         | hash_mem_multiplier                 | SET VALUE      | f
+         | idle_in_transaction_session_timeout | SET VALUE      | f
+         | idle_session_timeout                | SET VALUE      | f
+         | jit                                 | SET VALUE      | f
+         | jit_above_cost                      | SET VALUE      | f
+         | jit_expressions                     | SET VALUE      | f
+         | jit_inline_above_cost               | SET VALUE      | f
+         | jit_optimize_above_cost             | SET VALUE      | f
+         | jit_tuple_deforming                 | SET VALUE      | f
+         | join_collapse_limit                 | SET VALUE      | f
+         | lc_monetary                         | SET VALUE      | f
+         | lc_numeric                          | SET VALUE      | f
+         | lc_time                             | SET VALUE      | f
+         | local_preload_libraries             | SET VALUE      | f
+         | lock_timeout                        | SET VALUE      | f
+         | log_parameter_max_length_on_error   | SET VALUE      | f
+         | logical_decoding_work_mem           | SET VALUE      | f
+         | maintenance_io_concurrency          | SET VALUE      | f
+         | maintenance_work_mem                | SET VALUE      | f
+         | max_parallel_maintenance_workers    | SET VALUE      | f
+         | max_parallel_workers                | SET VALUE      | f
+         | max_parallel_workers_per_gather     | SET VALUE      | f
+         | min_parallel_index_scan_size        | SET VALUE      | f
+         | min_parallel_table_scan_size        | SET VALUE      | f
+         | optimize_bounded_sort               | SET VALUE      | f
+         | parallel_leader_participation       | SET VALUE      | f
+         | parallel_setup_cost                 | SET VALUE      | f
+         | parallel_tuple_cost                 | SET VALUE      | f
+         | password_encryption                 | SET VALUE      | f
+         | plan_cache_mode                     | SET VALUE      | f
+         | quote_all_identifiers               | SET VALUE      | f
+         | random_page_cost                    | SET VALUE      | f
+         | role                                | SET VALUE      | f
+         | row_security                        | SET VALUE      | f
+         | search_path                         | SET VALUE      | f
+         | seed                                | SET VALUE      | f
+         | seq_page_cost                       | SET VALUE      | f
+         | ssl_renegotiation_limit             | SET VALUE      | f
+         | standard_conforming_strings         | SET VALUE      | f
+         | statement_timeout                   | SET VALUE      | f
+         | synchronize_seqscans                | SET VALUE      | f
+         | synchronous_commit                  | SET VALUE      | f
+         | tcp_keepalives_count                | SET VALUE      | f
+         | tcp_keepalives_idle                 | SET VALUE      | f
+         | tcp_keepalives_interval             | SET VALUE      | f
+         | tcp_user_timeout                    | SET VALUE      | f
+         | temp_buffers                        | SET VALUE      | f
+         | temp_tablespaces                    | SET VALUE      | f
+         | timezone_abbreviations              | SET VALUE      | f
+         | trace_notify                        | SET VALUE      | f
+         | trace_sort                          | SET VALUE      | f
+         | trace_syncscan                      | SET VALUE      | f
+         | transaction_deferrable              | SET VALUE      | f
+         | transaction_isolation               | SET VALUE      | f
+         | transaction_read_only               | SET VALUE      | f
+         | transform_null_equals               | SET VALUE      | f
+         | vacuum_cost_delay                   | SET VALUE      | f
+         | vacuum_cost_limit                   | SET VALUE      | f
+         | vacuum_cost_page_dirty              | SET VALUE      | f
+         | vacuum_cost_page_hit                | SET VALUE      | f
+         | vacuum_cost_page_miss               | SET VALUE      | f
+         | vacuum_failsafe_age                 | SET VALUE      | f
+         | vacuum_freeze_min_age               | SET VALUE      | f
+         | vacuum_freeze_table_age             | SET VALUE      | f
+         | vacuum_multixact_failsafe_age       | SET VALUE      | f
+         | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+         | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+         | wal_sender_timeout                  | SET VALUE      | f
+         | wal_skip_threshold                  | SET VALUE      | f
+         | work_mem                            | SET VALUE      | f
+         | xmlbinary                           | SET VALUE      | f
+         | xmloption                           | SET VALUE      | f
+(138 rows)
+
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Check the new role does not yet have privileges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_setting_privilege
+-----------------------
+ f
+(1 row)
+
+-- Check inappropriate and nonsense privilege types
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+-- Grant privileges on settings to the new non-superuser role
+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;
+-- Check setting privileges after creating some
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+           grantee           |               setting               | privilege_type | is_grantable
+-----------------------------+-------------------------------------+----------------+--------------
+ regress_host_resource_admin | autovacuum_work_mem                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | autovacuum_work_mem                 | SET VALUE      | f
+ regress_host_resource_admin | hash_mem_multiplier                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | hash_mem_multiplier                 | SET VALUE      | f
+ regress_host_resource_admin | logical_decoding_work_mem           | ALTER SYSTEM   | f
+ regress_host_resource_admin | logical_decoding_work_mem           | SET VALUE      | f
+ regress_host_resource_admin | maintenance_work_mem                | ALTER SYSTEM   | f
+ regress_host_resource_admin | maintenance_work_mem                | SET VALUE      | f
+ regress_host_resource_admin | max_stack_depth                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | max_stack_depth                     | SET VALUE      | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | ALTER SYSTEM   | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | SET VALUE      | f
+ regress_host_resource_admin | shared_buffers                      | ALTER SYSTEM   | f
+ regress_host_resource_admin | shared_buffers                      | SET VALUE      | f
+ regress_host_resource_admin | temp_buffers                        | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_buffers                        | SET VALUE      | f
+ regress_host_resource_admin | temp_file_limit                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_file_limit                     | SET VALUE      | f
+ regress_host_resource_admin | work_mem                            | ALTER SYSTEM   | f
+ regress_host_resource_admin | work_mem                            | SET VALUE      | f
+                             | DateStyle                           | SET VALUE      | f
+                             | IntervalStyle                       | SET VALUE      | f
+                             | TimeZone                            | SET VALUE      | f
+                             | application_name                    | SET VALUE      | f
+                             | array_nulls                         | SET VALUE      | f
+                             | backend_flush_after                 | SET VALUE      | f
+                             | backslash_quote                     | SET VALUE      | f
+                             | bytea_output                        | SET VALUE      | f
+                             | check_function_bodies               | SET VALUE      | f
+                             | client_connection_check_interval    | SET VALUE      | f
+                             | client_min_messages                 | SET VALUE      | f
+                             | commit_siblings                     | SET VALUE      | f
+                             | constraint_exclusion                | SET VALUE      | f
+                             | cpu_index_tuple_cost                | SET VALUE      | f
+                             | cpu_operator_cost                   | SET VALUE      | f
+                             | cpu_tuple_cost                      | SET VALUE      | f
+                             | cursor_tuple_fraction               | SET VALUE      | f
+                             | debug_pretty_print                  | SET VALUE      | f
+                             | debug_print_parse                   | SET VALUE      | f
+                             | debug_print_plan                    | SET VALUE      | f
+                             | debug_print_rewritten               | SET VALUE      | f
+                             | default_statistics_target           | SET VALUE      | f
+                             | default_table_access_method         | SET VALUE      | f
+                             | default_tablespace                  | SET VALUE      | f
+                             | default_text_search_config          | SET VALUE      | f
+                             | default_toast_compression           | SET VALUE      | f
+                             | default_transaction_deferrable      | SET VALUE      | f
+                             | default_transaction_isolation       | SET VALUE      | f
+                             | default_transaction_read_only       | SET VALUE      | f
+                             | default_with_oids                   | SET VALUE      | f
+                             | effective_cache_size                | SET VALUE      | f
+                             | effective_io_concurrency            | SET VALUE      | f
+                             | enable_async_append                 | SET VALUE      | f
+                             | enable_bitmapscan                   | SET VALUE      | f
+                             | enable_gathermerge                  | SET VALUE      | f
+                             | enable_hashagg                      | SET VALUE      | f
+                             | enable_hashjoin                     | SET VALUE      | f
+                             | enable_incremental_sort             | SET VALUE      | f
+                             | enable_indexonlyscan                | SET VALUE      | f
+                             | enable_indexscan                    | SET VALUE      | f
+                             | enable_material                     | SET VALUE      | f
+                             | enable_memoize                      | SET VALUE      | f
+                             | enable_mergejoin                    | SET VALUE      | f
+                             | enable_nestloop                     | SET VALUE      | f
+                             | enable_parallel_append              | SET VALUE      | f
+                             | enable_parallel_hash                | SET VALUE      | f
+                             | enable_partition_pruning            | SET VALUE      | f
+                             | enable_partitionwise_aggregate      | SET VALUE      | f
+                             | enable_partitionwise_join           | SET VALUE      | f
+                             | enable_seqscan                      | SET VALUE      | f
+                             | enable_sort                         | SET VALUE      | f
+                             | enable_tidscan                      | SET VALUE      | f
+                             | escape_string_warning               | SET VALUE      | f
+                             | exit_on_error                       | SET VALUE      | f
+                             | extra_float_digits                  | SET VALUE      | f
+                             | force_parallel_mode                 | SET VALUE      | f
+                             | from_collapse_limit                 | SET VALUE      | f
+                             | geqo                                | SET VALUE      | f
+                             | geqo_effort                         | SET VALUE      | f
+                             | geqo_generations                    | SET VALUE      | f
+                             | geqo_pool_size                      | SET VALUE      | f
+                             | geqo_seed                           | SET VALUE      | f
+                             | geqo_selection_bias                 | SET VALUE      | f
+                             | geqo_threshold                      | SET VALUE      | f
+                             | gin_fuzzy_search_limit              | SET VALUE      | f
+                             | gin_pending_list_limit              | SET VALUE      | f
+                             | hash_mem_multiplier                 | SET VALUE      | f
+                             | idle_in_transaction_session_timeout | SET VALUE      | f
+                             | idle_session_timeout                | SET VALUE      | f
+                             | jit                                 | SET VALUE      | f
+                             | jit_above_cost                      | SET VALUE      | f
+                             | jit_expressions                     | SET VALUE      | f
+                             | jit_inline_above_cost               | SET VALUE      | f
+                             | jit_optimize_above_cost             | SET VALUE      | f
+                             | jit_tuple_deforming                 | SET VALUE      | f
+                             | join_collapse_limit                 | SET VALUE      | f
+                             | lc_monetary                         | SET VALUE      | f
+                             | lc_numeric                          | SET VALUE      | f
+                             | lc_time                             | SET VALUE      | f
+                             | local_preload_libraries             | SET VALUE      | f
+                             | lock_timeout                        | SET VALUE      | f
+                             | log_parameter_max_length_on_error   | SET VALUE      | f
+                             | logical_decoding_work_mem           | SET VALUE      | f
+                             | maintenance_io_concurrency          | SET VALUE      | f
+                             | maintenance_work_mem                | SET VALUE      | f
+                             | max_parallel_maintenance_workers    | SET VALUE      | f
+                             | max_parallel_workers                | SET VALUE      | f
+                             | max_parallel_workers_per_gather     | SET VALUE      | f
+                             | min_parallel_index_scan_size        | SET VALUE      | f
+                             | min_parallel_table_scan_size        | SET VALUE      | f
+                             | optimize_bounded_sort               | SET VALUE      | f
+                             | parallel_leader_participation       | SET VALUE      | f
+                             | parallel_setup_cost                 | SET VALUE      | f
+                             | parallel_tuple_cost                 | SET VALUE      | f
+                             | password_encryption                 | SET VALUE      | f
+                             | plan_cache_mode                     | SET VALUE      | f
+                             | quote_all_identifiers               | SET VALUE      | f
+                             | random_page_cost                    | SET VALUE      | f
+                             | role                                | SET VALUE      | f
+                             | row_security                        | SET VALUE      | f
+                             | search_path                         | SET VALUE      | f
+                             | seed                                | SET VALUE      | f
+                             | seq_page_cost                       | SET VALUE      | f
+                             | ssl_renegotiation_limit             | SET VALUE      | f
+                             | standard_conforming_strings         | SET VALUE      | f
+                             | statement_timeout                   | SET VALUE      | f
+                             | synchronize_seqscans                | SET VALUE      | f
+                             | synchronous_commit                  | SET VALUE      | f
+                             | tcp_keepalives_count                | SET VALUE      | f
+                             | tcp_keepalives_idle                 | SET VALUE      | f
+                             | tcp_keepalives_interval             | SET VALUE      | f
+                             | tcp_user_timeout                    | SET VALUE      | f
+                             | temp_buffers                        | SET VALUE      | f
+                             | temp_tablespaces                    | SET VALUE      | f
+                             | timezone_abbreviations              | SET VALUE      | f
+                             | trace_notify                        | SET VALUE      | f
+                             | trace_sort                          | SET VALUE      | f
+                             | trace_syncscan                      | SET VALUE      | f
+                             | transaction_deferrable              | SET VALUE      | f
+                             | transaction_isolation               | SET VALUE      | f
+                             | transaction_read_only               | SET VALUE      | f
+                             | transform_null_equals               | SET VALUE      | f
+                             | vacuum_cost_delay                   | SET VALUE      | f
+                             | vacuum_cost_limit                   | SET VALUE      | f
+                             | vacuum_cost_page_dirty              | SET VALUE      | f
+                             | vacuum_cost_page_hit                | SET VALUE      | f
+                             | vacuum_cost_page_miss               | SET VALUE      | f
+                             | vacuum_failsafe_age                 | SET VALUE      | f
+                             | vacuum_freeze_min_age               | SET VALUE      | f
+                             | vacuum_freeze_table_age             | SET VALUE      | f
+                             | vacuum_multixact_failsafe_age       | SET VALUE      | f
+                             | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+                             | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+                             | wal_sender_timeout                  | SET VALUE      | f
+                             | wal_skip_threshold                  | SET VALUE      | f
+                             | work_mem                            | SET VALUE      | f
+                             | xmlbinary                           | SET VALUE      | f
+                             | xmloption                           | SET VALUE      | f
+(158 rows)
+
+-- Check the new role now has privilges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE WITH GRANT OPTION, ALTER SYSTEM
WITHGRANT OPTION'); 
+ has_setting_privilege
+-----------------------
+ f
+(1 row)
+
+-- Check again the inappropriate and nonsense privilege types.  The prior similar check
+-- was performed before any entry for work_mem existed.
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+ERROR:  unrecognized privilege type: "WHATEVER WITH GRANT OPTION"
+-- Check other function signatures
+SELECT has_setting_privilege('regress_host_resource_admin',
+                             (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+                             'SET VALUE');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                             'max_stack_depth',
+                             'SET VALUE');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                             (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+                             'SET VALUE');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('hash_mem_multiplier', 'set value');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'work_mem'),
+                             'alter system with grant option');
+ has_setting_privilege
+-----------------------
+ t
+(1 row)
+
+-- Check setting privileges show up in view
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+           grantee           |               setting               | privilege_type | is_grantable
+-----------------------------+-------------------------------------+----------------+--------------
+ regress_host_resource_admin | autovacuum_work_mem                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | autovacuum_work_mem                 | SET VALUE      | f
+ regress_host_resource_admin | hash_mem_multiplier                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | hash_mem_multiplier                 | SET VALUE      | f
+ regress_host_resource_admin | logical_decoding_work_mem           | ALTER SYSTEM   | f
+ regress_host_resource_admin | logical_decoding_work_mem           | SET VALUE      | f
+ regress_host_resource_admin | maintenance_work_mem                | ALTER SYSTEM   | f
+ regress_host_resource_admin | maintenance_work_mem                | SET VALUE      | f
+ regress_host_resource_admin | max_stack_depth                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | max_stack_depth                     | SET VALUE      | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | ALTER SYSTEM   | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | SET VALUE      | f
+ regress_host_resource_admin | shared_buffers                      | ALTER SYSTEM   | f
+ regress_host_resource_admin | shared_buffers                      | SET VALUE      | f
+ regress_host_resource_admin | temp_buffers                        | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_buffers                        | SET VALUE      | f
+ regress_host_resource_admin | temp_file_limit                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_file_limit                     | SET VALUE      | f
+ regress_host_resource_admin | work_mem                            | ALTER SYSTEM   | f
+ regress_host_resource_admin | work_mem                            | SET VALUE      | f
+                             | DateStyle                           | SET VALUE      | f
+                             | IntervalStyle                       | SET VALUE      | f
+                             | TimeZone                            | SET VALUE      | f
+                             | application_name                    | SET VALUE      | f
+                             | array_nulls                         | SET VALUE      | f
+                             | backend_flush_after                 | SET VALUE      | f
+                             | backslash_quote                     | SET VALUE      | f
+                             | bytea_output                        | SET VALUE      | f
+                             | check_function_bodies               | SET VALUE      | f
+                             | client_connection_check_interval    | SET VALUE      | f
+                             | client_min_messages                 | SET VALUE      | f
+                             | commit_siblings                     | SET VALUE      | f
+                             | constraint_exclusion                | SET VALUE      | f
+                             | cpu_index_tuple_cost                | SET VALUE      | f
+                             | cpu_operator_cost                   | SET VALUE      | f
+                             | cpu_tuple_cost                      | SET VALUE      | f
+                             | cursor_tuple_fraction               | SET VALUE      | f
+                             | debug_pretty_print                  | SET VALUE      | f
+                             | debug_print_parse                   | SET VALUE      | f
+                             | debug_print_plan                    | SET VALUE      | f
+                             | debug_print_rewritten               | SET VALUE      | f
+                             | default_statistics_target           | SET VALUE      | f
+                             | default_table_access_method         | SET VALUE      | f
+                             | default_tablespace                  | SET VALUE      | f
+                             | default_text_search_config          | SET VALUE      | f
+                             | default_toast_compression           | SET VALUE      | f
+                             | default_transaction_deferrable      | SET VALUE      | f
+                             | default_transaction_isolation       | SET VALUE      | f
+                             | default_transaction_read_only       | SET VALUE      | f
+                             | default_with_oids                   | SET VALUE      | f
+                             | effective_cache_size                | SET VALUE      | f
+                             | effective_io_concurrency            | SET VALUE      | f
+                             | enable_async_append                 | SET VALUE      | f
+                             | enable_bitmapscan                   | SET VALUE      | f
+                             | enable_gathermerge                  | SET VALUE      | f
+                             | enable_hashagg                      | SET VALUE      | f
+                             | enable_hashjoin                     | SET VALUE      | f
+                             | enable_incremental_sort             | SET VALUE      | f
+                             | enable_indexonlyscan                | SET VALUE      | f
+                             | enable_indexscan                    | SET VALUE      | f
+                             | enable_material                     | SET VALUE      | f
+                             | enable_memoize                      | SET VALUE      | f
+                             | enable_mergejoin                    | SET VALUE      | f
+                             | enable_nestloop                     | SET VALUE      | f
+                             | enable_parallel_append              | SET VALUE      | f
+                             | enable_parallel_hash                | SET VALUE      | f
+                             | enable_partition_pruning            | SET VALUE      | f
+                             | enable_partitionwise_aggregate      | SET VALUE      | f
+                             | enable_partitionwise_join           | SET VALUE      | f
+                             | enable_seqscan                      | SET VALUE      | f
+                             | enable_sort                         | SET VALUE      | f
+                             | enable_tidscan                      | SET VALUE      | f
+                             | escape_string_warning               | SET VALUE      | f
+                             | exit_on_error                       | SET VALUE      | f
+                             | extra_float_digits                  | SET VALUE      | f
+                             | force_parallel_mode                 | SET VALUE      | f
+                             | from_collapse_limit                 | SET VALUE      | f
+                             | geqo                                | SET VALUE      | f
+                             | geqo_effort                         | SET VALUE      | f
+                             | geqo_generations                    | SET VALUE      | f
+                             | geqo_pool_size                      | SET VALUE      | f
+                             | geqo_seed                           | SET VALUE      | f
+                             | geqo_selection_bias                 | SET VALUE      | f
+                             | geqo_threshold                      | SET VALUE      | f
+                             | gin_fuzzy_search_limit              | SET VALUE      | f
+                             | gin_pending_list_limit              | SET VALUE      | f
+                             | hash_mem_multiplier                 | SET VALUE      | f
+                             | idle_in_transaction_session_timeout | SET VALUE      | f
+                             | idle_session_timeout                | SET VALUE      | f
+                             | jit                                 | SET VALUE      | f
+                             | jit_above_cost                      | SET VALUE      | f
+                             | jit_expressions                     | SET VALUE      | f
+                             | jit_inline_above_cost               | SET VALUE      | f
+                             | jit_optimize_above_cost             | SET VALUE      | f
+                             | jit_tuple_deforming                 | SET VALUE      | f
+                             | join_collapse_limit                 | SET VALUE      | f
+                             | lc_monetary                         | SET VALUE      | f
+                             | lc_numeric                          | SET VALUE      | f
+                             | lc_time                             | SET VALUE      | f
+                             | local_preload_libraries             | SET VALUE      | f
+                             | lock_timeout                        | SET VALUE      | f
+                             | log_parameter_max_length_on_error   | SET VALUE      | f
+                             | logical_decoding_work_mem           | SET VALUE      | f
+                             | maintenance_io_concurrency          | SET VALUE      | f
+                             | maintenance_work_mem                | SET VALUE      | f
+                             | max_parallel_maintenance_workers    | SET VALUE      | f
+                             | max_parallel_workers                | SET VALUE      | f
+                             | max_parallel_workers_per_gather     | SET VALUE      | f
+                             | min_parallel_index_scan_size        | SET VALUE      | f
+                             | min_parallel_table_scan_size        | SET VALUE      | f
+                             | optimize_bounded_sort               | SET VALUE      | f
+                             | parallel_leader_participation       | SET VALUE      | f
+                             | parallel_setup_cost                 | SET VALUE      | f
+                             | parallel_tuple_cost                 | SET VALUE      | f
+                             | password_encryption                 | SET VALUE      | f
+                             | plan_cache_mode                     | SET VALUE      | f
+                             | quote_all_identifiers               | SET VALUE      | f
+                             | random_page_cost                    | SET VALUE      | f
+                             | role                                | SET VALUE      | f
+                             | row_security                        | SET VALUE      | f
+                             | search_path                         | SET VALUE      | f
+                             | seed                                | SET VALUE      | f
+                             | seq_page_cost                       | SET VALUE      | f
+                             | ssl_renegotiation_limit             | SET VALUE      | f
+                             | standard_conforming_strings         | SET VALUE      | f
+                             | statement_timeout                   | SET VALUE      | f
+                             | synchronize_seqscans                | SET VALUE      | f
+                             | synchronous_commit                  | SET VALUE      | f
+                             | tcp_keepalives_count                | SET VALUE      | f
+                             | tcp_keepalives_idle                 | SET VALUE      | f
+                             | tcp_keepalives_interval             | SET VALUE      | f
+                             | tcp_user_timeout                    | SET VALUE      | f
+                             | temp_buffers                        | SET VALUE      | f
+                             | temp_tablespaces                    | SET VALUE      | f
+                             | timezone_abbreviations              | SET VALUE      | f
+                             | trace_notify                        | SET VALUE      | f
+                             | trace_sort                          | SET VALUE      | f
+                             | trace_syncscan                      | SET VALUE      | f
+                             | transaction_deferrable              | SET VALUE      | f
+                             | transaction_isolation               | SET VALUE      | f
+                             | transaction_read_only               | SET VALUE      | f
+                             | transform_null_equals               | SET VALUE      | f
+                             | vacuum_cost_delay                   | SET VALUE      | f
+                             | vacuum_cost_limit                   | SET VALUE      | f
+                             | vacuum_cost_page_dirty              | SET VALUE      | f
+                             | vacuum_cost_page_hit                | SET VALUE      | f
+                             | vacuum_cost_page_miss               | SET VALUE      | f
+                             | vacuum_failsafe_age                 | SET VALUE      | f
+                             | vacuum_freeze_min_age               | SET VALUE      | f
+                             | vacuum_freeze_table_age             | SET VALUE      | f
+                             | vacuum_multixact_failsafe_age       | SET VALUE      | f
+                             | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+                             | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+                             | wal_sender_timeout                  | SET VALUE      | f
+                             | wal_skip_threshold                  | SET VALUE      | f
+                             | work_mem                            | SET VALUE      | f
+                             | xmlbinary                           | SET VALUE      | f
+                             | xmloption                           | SET VALUE      | f
+(158 rows)
+
+-- 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
+SET SESSION AUTHORIZATION regress_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 setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting 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"
+SET SESSION AUTHORIZATION regress_admin;
+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 setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting 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"
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_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 setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting 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"
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_admin;
+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 setting plpgsql.extra_warnings
+privileges for setting 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 291e21d7a6..1c762cea54 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -54,6 +54,33 @@ REVOKE pg_read_all_settings FROM regress_priv_user8;
 DROP USER regress_priv_user10;
 DROP USER regress_priv_user9;
 DROP USER regress_priv_user8;
+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
+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 setting name " "
+GRANT ALTER SYSTEM ON " foo " TO regress_priv_user7; -- bad name
+ERROR:  invalid setting 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;
@@ -2355,10 +2382,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 setting enable_memoize
+privileges for setting 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 setting enable_seqscan
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for table persons2
+privileges for setting no_such_param
+privileges for setting no_such_extension.no_such_param
+privileges for setting 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/rules.out b/src/test/regress/expected/rules.out
index ac468568a1..0d0e3ff410 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1686,6 +1686,19 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
+pg_setting_privileges| SELECT grantor.rolname AS grantor,
+    grantee.rolname AS grantee,
+    set_acl.setting,
+    acl.privilege_type,
+    acl.is_grantable
+   FROM pg_setting_acl set_acl,
+    ((LATERAL ( SELECT aclexplode.grantor,
+            aclexplode.grantee,
+            aclexplode.privilege_type,
+            aclexplode.is_grantable
+           FROM aclexplode(set_acl.setacl) aclexplode(grantor, grantee, privilege_type, is_grantable)) acl
+     LEFT JOIN pg_authid grantee ON ((acl.grantee = grantee.oid)))
+     LEFT JOIN pg_authid grantor ON ((acl.grantor = grantor.oid)));
 pg_settings| SELECT a.name,
     a.setting,
     a.unit,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..4dfa0642c0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan
tidrangescancollate.icu.utf8 incremental_sort create_role 
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan
tidrangescancollate.icu.utf8 incremental_sort create_role guc_privs 

 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
diff --git a/src/test/regress/sql/guc_privs.sql b/src/test/regress/sql/guc_privs.sql
new file mode 100644
index 0000000000..076c136563
--- /dev/null
+++ b/src/test/regress/sql/guc_privs.sql
@@ -0,0 +1,185 @@
+-- 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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;
+-- Check setting privileges prior to creating any
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Check the new role does not yet have privileges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+-- Check inappropriate and nonsense privilege types
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+-- Grant privileges on settings to the new non-superuser role
+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;
+-- Check setting privileges after creating some
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+-- Check the new role now has privilges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE WITH GRANT OPTION, ALTER SYSTEM
WITHGRANT OPTION'); 
+-- Check again the inappropriate and nonsense privilege types.  The prior similar check
+-- was performed before any entry for work_mem existed.
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+-- Check other function signatures
+SELECT has_setting_privilege('regress_host_resource_admin',
+                             (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+                             'SET VALUE');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                             'max_stack_depth',
+                             'SET VALUE');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                             (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+                             'SET VALUE');
+SELECT has_setting_privilege('hash_mem_multiplier', 'set value');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'work_mem'),
+                             'alter system with grant option');
+-- Check setting privileges show up in view
+SELECT grantee, setting, privilege_type, is_grantable
+    FROM pg_catalog.pg_setting_privileges
+    ORDER BY grantee, setting, privilege_type;
+-- 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
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_admin;
+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;
+SET SESSION AUTHORIZATION regress_admin;
+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
+SET SESSION AUTHORIZATION regress_admin;
+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 c8c545b64c..ddbf6afa44 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -66,6 +66,33 @@ DROP USER regress_priv_user10;
 DROP USER regress_priv_user9;
 DROP USER regress_priv_user8;

+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;

@@ -1426,10 +1453,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;

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 6, 2022, at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> 1. If we need to change these two contrib modules, doesn't that imply
> a lot of changes forced on external modules as well?  What are the
> security implications if somebody doesn't make such a change?
>
> 2. It looks to me like if someone installs the updated postgres_fdw.so,
> but doesn't run ALTER EXTENSION UPDATE, they are going to be left with a
> rather broken extension.  This seems not good either, especially if it's
> multiplied by a boatload of third-party extensions requiring updates.
>
> So I think some more thought is needed to see if we can't avoid
> the need to touch extension modules in this patch.  Or at least
> avoid the need for synchronized C-level and SQL-level updates,
> because that is going to create a lot of pain for end users.
>
> I'm also fairly distressed by the number of changes in guc.c,
> mainly because I fear that that means that pending patches that
> add GUC variables will be subtly incorrect/insecure if they're not
> updated to account for this.  Frankly, I also reject the apparent
> position that we need to support forbidding users from touching
> many of these GUCs.  Or, if that's our position, why are there
> per-GUC changes at all, rather than just redefining what the
> context values mean?  (That is, why not redefine USERSET and
> SUSET as simply indicating the default ACL to be applied if there's
> no entry in the catalog.)

To my knowledge, there is no mechanism to revoke an implicit privilege.  You can revoke a privilege explicitly listed
inan aclitem[], but only if the privilege is being tracked that way. 

Userset variables are implicitly settable by any user.  There was a request, off-list as I recall, to make it possible
torevoke the privilege to set variables such as "work_mem".  To make that possible, but not change the default behavior
vis-a-visprior releases, I upgraded most userset variables to suset with a corresponding grant to public on the
variable. Sites which wish to have a more restrictive policy on such variables can revoke that privilege from public
andinstead issue more restrictive grants.  There were a few variables where such treatment didn't seem sensible, such
asones to do with client connections, and I left them alone.  I didn't insist on a defense for why any particular
settingneeded to be revocable in order to apply this treatment.  My assumption was that sites should be allowed to
determinetheir own security policies per setting unless there is a technical difficulty for a given setting that would
makeit overly burdensome to implement. 

It seemed more complete to do the same thing for contrib modules, so I did.

If a third-party module doesn't do the equivalent operation, they would simply continue with their userset variables
workingas before.  I don't see a specific security concern with that, though I'd be happy to consider arguments to the
contrary.

I believe there are also some issues with this patch relating to pg_dump/pg_restore and to pg_upgrade, which EDB is
workingto address.  We intend to repost something soon. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> On Mar 6, 2022, at 2:13 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> ... Or, if that's our position, why are there
>> per-GUC changes at all, rather than just redefining what the
>> context values mean?  (That is, why not redefine USERSET and
>> SUSET as simply indicating the default ACL to be applied if there's
>> no entry in the catalog.)

> To my knowledge, there is no mechanism to revoke an implicit privilege.  You can revoke a privilege explicitly listed
inan aclitem[], but only if the privilege is being tracked that way. 

So?  What I'm suggesting is along the lines of

(1) pg_setting_acl starts out empty, or at least mostly empty (maybe
there are a few GUCs that need custom values).

(2) If there's a pg_setting_acl entry for a GUC that's to be set,
we apply it: either it grants the desired permission or it doesn't.

(3) If there's no entry, then for a USERSET GUC we assume that the
entry would be like "=s/postgres", while for any other context value
we assume the ACL grants nothing.

I don't think this is materially different from what we do with
permissions on (say) functions.  If you want to revoke the public
SET privilege on some USERSET variable, you instantiate the default
and then revoke.  You end up with an empty ACL stored in pg_setting_acl,
and voila.

It'd likely be necessary to refuse to record a grant/revoke on
an unknown GUC, since if we don't know the GUC then we can't know
what the relevant default ACL ought to be.  But I bet your existing
patch has some dubious behavior in that case too.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 6, 2022, at 2:57 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> I don't think this is materially different from what we do with
> permissions on (say) functions.  If you want to revoke the public
> SET privilege on some USERSET variable, you instantiate the default
> and then revoke.  You end up with an empty ACL stored in pg_setting_acl,
> and voila.

I assume you mean the implementation of REVOKE does this, not that the user needs to do both a grant and a revoke.

> It'd likely be necessary to refuse to record a grant/revoke on
> an unknown GUC, since if we don't know the GUC then we can't know
> what the relevant default ACL ought to be.  But I bet your existing
> patch has some dubious behavior in that case too.

The existing patch allows grants on unknown gucs, because it can't know what guc an upgrade script will introduce, and
thegrant statement may need to execute before the guc exists.  That opens a window for granting privileges on
non-existentgucs.  That sounds bad, but I don't know of any actual harm that it does, beyond just being ugly. 

With your proposal, it sounds like we could avoid that ugliness, so I'm inclined to at least try doing it as you
propose.

Thanks for the suggestion!

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> On Mar 6, 2022, at 2:57 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I don't think this is materially different from what we do with
>> permissions on (say) functions.  If you want to revoke the public
>> SET privilege on some USERSET variable, you instantiate the default
>> and then revoke.  You end up with an empty ACL stored in pg_setting_acl,
>> and voila.

> I assume you mean the implementation of REVOKE does this, not that the user needs to do both a grant and a revoke.

Right.  Again, look at what happens when you create a function and
then revoke its default PUBLIC EXECUTE permission.

>> It'd likely be necessary to refuse to record a grant/revoke on
>> an unknown GUC, since if we don't know the GUC then we can't know
>> what the relevant default ACL ought to be.  But I bet your existing
>> patch has some dubious behavior in that case too.

> The existing patch allows grants on unknown gucs, because it can't know what guc an upgrade script will introduce,
andthe grant statement may need to execute before the guc exists. 

Yeah, that's the problematic case.  It might mostly work to assume that
an unknown GUC has an empty default ACL.  This could fail to retain the
default PUBLIC SET permission if it later turns out the GUC is USERSET
... but I suspect in most cases anybody who's messing with the permissions
would've started out by revoking that anyway.  We could make this
definitely work in pg_dump if we teach pg_dump to explicitly grant or
revoke the PUBLIC SET permission anytime it's emitting anything for
a GUC, even if it thinks that would be the default state anyway.
Extension scripts that want to modify permissions for their GUCs
could follow that same principle to be sure.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 6, 2022, at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> The existing patch allows grants on unknown gucs, because it can't know what guc an upgrade script will introduce,
andthe grant statement may need to execute before the guc exists. 
>
> Yeah, that's the problematic case.  It might mostly work to assume that
> an unknown GUC has an empty default ACL.  This could fail to retain the
> default PUBLIC SET permission if it later turns out the GUC is USERSET

On further reflection, I concluded this isn't needed.  No current extension, whether in-core or third party, expects to
beable to create a new GUC and then grant or revoke permissions on it.  They can already specify the guc context
(PGC_USERS,etc).  Introducing a feature that depends on the dubious assumption that unrecognized GUCs will turn out to
beUSERSET doesn't seem warranted. 

The patch attributes all grants of setting privileges to the bootstrap superuser.  Only superusers can grant or revoke
privilegeson settings, and all settings are implicitly owned by the bootstrap superuser because there is no explicit
ownerassociated with settings.  Consequently, select_best_grantor(some_superuser, ..., BOOTSTRAP_SUPERUSERID, ...)
alwayschooses the bootstrap superuser.  I don't see a problem with this, but wouldn't mind a second opinion.  Some
peoplemight find it surprising when viewing the pg_setting_acl.setacl field. 



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/15/22 16:59, Mark Dilger wrote:
>> On Mar 6, 2022, at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>
>> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>>> The existing patch allows grants on unknown gucs, because it can't know what guc an upgrade script will introduce,
andthe grant statement may need to execute before the guc exists.
 
>> Yeah, that's the problematic case.  It might mostly work to assume that
>> an unknown GUC has an empty default ACL.  This could fail to retain the
>> default PUBLIC SET permission if it later turns out the GUC is USERSET
> On further reflection, I concluded this isn't needed.  No current extension, whether in-core or third party, expects
tobe able to create a new GUC and then grant or revoke permissions on it.  They can already specify the guc context
(PGC_USERS,etc).  Introducing a feature that depends on the dubious assumption that unrecognized GUCs will turn out to
beUSERSET doesn't seem warranted.
 

Agreed.


>
> The patch attributes all grants of setting privileges to the bootstrap superuser.  Only superusers can grant or
revokeprivileges on settings, and all settings are implicitly owned by the bootstrap superuser because there is no
explicitowner associated with settings.  Consequently, select_best_grantor(some_superuser, ..., BOOTSTRAP_SUPERUSERID,
...)always chooses the bootstrap superuser.  I don't see a problem with this, but wouldn't mind a second opinion.  Some
peoplemight find it surprising when viewing the pg_setting_acl.setacl field.
 


I think it's OK as long as we document it. An alternative might be to
invent a pseudo-superuser called, say, 'postgres_system', but that seems
like overkill to solve what is in effect a cosmetic problem.

Generally I think this is now in fairly good shape, I've played with it
and it seems to do what I expect in every case, and the things I found
surprising are gone.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Andrew Dunstan <andrew@dunslane.net> writes:
> Generally I think this is now in fairly good shape, I've played with it
> and it seems to do what I expect in every case, and the things I found
> surprising are gone.

Stepping back a bit ... do we really want to institutionalize the
term "setting" for GUC variables?  I realize that the view pg_settings
exists, but the documentation generally prefers the term "configuration
parameters".  Where config.sgml uses "setting" as a noun, it's usually
talking about a specific concrete value for a parameter, and you can
argue that the view's name comports with that meaning.  But you can't
GRANT a parameter's current value.

I don't have a better name to offer offhand --- I surely am not proposing
that we change the syntax to be "GRANT ... ON CONFIGURATION PARAMETER x",
because that's way too wordy.  But now is the time to bikeshed if we're
gonna bikeshed, or else admit that we're not interested in precise
vocabulary.

I'm also fairly allergic to the way that this patch has decided to assign
multi-word names to privilege types (ie SET VALUE, ALTER SYSTEM).  There
is no existing precedent for that, and I think it's going to break
client-side code that we don't need to break.  It's not coincidental that
this forces weird changes in rules about whitespace in the has_privilege
functions, for example; and if you think that isn't going to cause
problems I think you are wrong.  Perhaps we could just use "SET" and
"ALTER", or "SET" and "SYSTEM"?

I also agree with the upthread criticism that this is abusing the
ObjectPostAlterHook API unreasonably much.  In particular, it's
trying to use pg_setting_acl OIDs as identities for GUCs, which
they are not, first because they're unstable and second because
most GUCs won't even have an entry there.  I therefore judge the
hook calls added to ExecSetVariableStmt and AlterSystemSetConfigFile
to be 100% useless, in fact probably counterproductive because they
introduce a boatload of worries about whether the right things happen
if the hook errors out or does something guc.c isn't expecting.

I suggest that what might be saner is to consider that the "objects"
that the hook calls are concerned with are the pg_setting_acl entries,
not the underlying GUCs, and thus that the hooks need be invoked only
when creating, destroying or altering those entries.  If we do have
a need for a hook editorializing on GUC value settings, that would
have to be an independent API --- but I have heard no calls for
the ability to have such a hook, and I don't think that this patch
is the place to introduce one.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 16, 2022, at 11:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Stepping back a bit ... do we really want to institutionalize the
> term "setting" for GUC variables?  I realize that the view pg_settings
> exists, but the documentation generally prefers the term "configuration
> parameters".  Where config.sgml uses "setting" as a noun, it's usually
> talking about a specific concrete value for a parameter, and you can
> argue that the view's name comports with that meaning.  But you can't
> GRANT a parameter's current value.
>
> I don't have a better name to offer offhand --- I surely am not proposing
> that we change the syntax to be "GRANT ... ON CONFIGURATION PARAMETER x",
> because that's way too wordy.  But now is the time to bikeshed if we're
> gonna bikeshed, or else admit that we're not interested in precise
> vocabulary.

Informally, we often use "GUC" on this list, but that isn't used formally, leaving "configuration parameter" and
"setting"as the two obvious choices.  I preferred "configuration parameter" originally and was argued out of it.  My
takeon "setting" was also that it more naturally refers to the choice of setting, not the thing being set, such that
"work_mem= 8192" means the configuration parameter "work_mem" has the setting "8192".  I'm happy to change the patch
alongthese lines, but I vaguely recall it being Robert who liked the "setting" language.  Robert?  (Sorry if I
misrememberthat...) 

> I'm also fairly allergic to the way that this patch has decided to assign
> multi-word names to privilege types (ie SET VALUE, ALTER SYSTEM).  There
> is no existing precedent for that, and I think it's going to break
> client-side code that we don't need to break.  It's not coincidental that
> this forces weird changes in rules about whitespace in the has_privilege
> functions, for example; and if you think that isn't going to cause
> problems I think you are wrong.  Perhaps we could just use "SET" and
> "ALTER", or "SET" and "SYSTEM"?

I chose those particular multi-word names to avoid needing to promote any keywords.  That's been a while ago, and I
don'trecall exactly where the shift-reduce or reduce-reduce errors were coming from.  I'll play with it to see what I
canfind. 

> I also agree with the upthread criticism that this is abusing the
> ObjectPostAlterHook API unreasonably much.  In particular, it's
> trying to use pg_setting_acl OIDs as identities for GUCs, which
> they are not, first because they're unstable and second because
> most GUCs won't even have an entry there.  I therefore judge the
> hook calls added to ExecSetVariableStmt and AlterSystemSetConfigFile
> to be 100% useless, in fact probably counterproductive because they
> introduce a boatload of worries about whether the right things happen
> if the hook errors out or does something guc.c isn't expecting.

I think Joshua was planning to use these hooks for security purposes.  The hooks are supposed to check whether the Oid
isvalid, and if not, still be able to make choices based on the other information.  Joshua, any comment on this? 

> I suggest that what might be saner is to consider that the "objects"
> that the hook calls are concerned with are the pg_setting_acl entries,
> not the underlying GUCs, and thus that the hooks need be invoked only
> when creating, destroying or altering those entries.

That's certainly a thing we could do, but I got the impression that Joshua wanted to hook into SET, RESET, and ALTER
SYSTEMSET, not merely into GRANT and REVOKE. 

>  If we do have
> a need for a hook editorializing on GUC value settings, that would
> have to be an independent API --- but I have heard no calls for
> the ability to have such a hook, and I don't think that this patch
> is the place to introduce one.

Well, the call for it was in this thread, but I'm ok with yanking out that part of the patch if it seems half-baked.

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
<snip>

> I suggest that what might be saner is to consider that the "objects"
> that the hook calls are concerned with are the pg_setting_acl entries,
> not the underlying GUCs, and thus that the hooks need be invoked only
> when creating, destroying or altering those entries.  If we do have
> a need for a hook editorializing on GUC value settings, that would
> have to be an independent API --- but I have heard no calls for
> the ability to have such a hook, and I don't think that this patch
> is the place to introduce one.

I requested it here:
https://www.postgresql.org/message-id/CAGB%2BVh5pVFAqw8YzeXy4xxmEt_4Hq_8pEUHdCQvv3mCjvC-S-w%40mail.gmail.com

with compromises here:
https://www.postgresql.org/message-id/CAGB%2BVh6wLJ3FKsno62fi54pfg0FDrZRWcpuuCJBkHcCj-G1ndw%40mail.gmail.com
https://www.postgresql.org/message-id/0A3D3CBA-6548-4C9E-9F46-59D5C51A1F31%40enterprisedb.com
https://www.postgresql.org/message-id/CAGB%2BVh65R5vKC4rEt7r2_pK3kMZd-VY0n99RJwcP8Bic7xvOxQ%40mail.gmail.com

and the new version here:
https://www.postgresql.org/message-id/C8DF19D8-C15D-4C2D-91CA-391390F1E421%40enterprisedb.com

which I wrote. a toy module to test and was satisfied with it, despite
the limitations.

If we are adding a DAC Grant for a type of object it seems untenable
that it would not come with analogous MAC capable hooks.

Thank you.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> On Mar 16, 2022, at 11:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> ... I therefore judge the
>> hook calls added to ExecSetVariableStmt and AlterSystemSetConfigFile
>> to be 100% useless, in fact probably counterproductive because they
>> introduce a boatload of worries about whether the right things happen
>> if the hook errors out or does something guc.c isn't expecting.

> I think Joshua was planning to use these hooks for security purposes.  The hooks are supposed to check whether the
Oidis valid, and if not, still be able to make choices based on the other information.  Joshua, any comment on this? 

It's going to be hard to do anything useful in a hook that (a) does
not know which GUC is being assigned to and (b) cannot do catalog
accesses for fear that we're not inside a transaction.  (b), in
particular, seems like a rather thorough API break; up to now
ObjectPostAlter hooks could assume that catalog accesses are OK.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Wed, Mar 16, 2022 at 3:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
> > On Mar 16, 2022, at 11:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> ... I therefore judge the
> >> hook calls added to ExecSetVariableStmt and AlterSystemSetConfigFile
> >> to be 100% useless, in fact probably counterproductive because they
> >> introduce a boatload of worries about whether the right things happen
> >> if the hook errors out or does something guc.c isn't expecting.
>
> > I think Joshua was planning to use these hooks for security purposes.  The hooks are supposed to check whether the
Oidis valid, and if not, still be able to make choices based on the other information.  Joshua, any comment on this?
 
>
> It's going to be hard to do anything useful in a hook that (a) does
> not know which GUC is being assigned to and (b) cannot do catalog
> accesses for fear that we're not inside a transaction.  (b), in
> particular, seems like a rather thorough API break; up to now
> ObjectPostAlter hooks could assume that catalog accesses are OK.
>

Can you elaborate on this point? This is perhaps an area where I don't
know the rules of the road, to test this hook I modified the set_user
extension, which normally uses the parse tree to figure out if someone
is trying to SET log_statement or ALTER SYSTEM log_statement and
replaced it with:

                case OAT_POST_ALTER:
                if (classId == SettingAclRelationId)
                {
                        ObjectAddress   object;
                        object.classId = SettingAclRelationId;
                        object.objectId = objectId;
                        object.objectSubId = 0;
                        if (strcmp(getObjectIdentity(&object),
"log_statement") == 0)
                        {
                                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                 errmsg("Setting %s blocked",
getObjectIdentity(&object))));
                        }
                  }

Is that inherently unsafe?



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Joshua Brindle <joshua.brindle@crunchydata.com> writes:
> On Wed, Mar 16, 2022 at 3:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> It's going to be hard to do anything useful in a hook that (a) does
>> not know which GUC is being assigned to and (b) cannot do catalog
>> accesses for fear that we're not inside a transaction.  (b), in
>> particular, seems like a rather thorough API break; up to now
>> ObjectPostAlter hooks could assume that catalog accesses are OK.

> Can you elaborate on this point?

Hmm, I glossed over too many details there perhaps.  I was thinking
about the restrictions on GUC check_hooks, which can be run outside
a transaction altogether.  But that's not quite relevant here.
ExecSetVariableStmt can assume it's inside a transaction, but what
it *can't* assume is that we've set a transaction snapshot as yet
(cf. PlannedStmtRequiresSnapshot).  If we call an ObjectPostAlter hook
there, and it does a catalog access, that's going to break things for
modifications of GUCs that are supposed to be modifiable without
freezing the transaction snapshot.  So the net result is the same:
catalog access not okay, at least not in general.

Between that and the fact that an OID-based API is largely useless here,
I don't think it's sane to try to use the existing ObjectPostAlter API.
Maybe there is a case for inventing a separate hook API that could pass
the GUC name as a string, instead.  I remain of the opinion that this
patch should not concern itself with that, though.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/16/22 14:47, Tom Lane wrote:
> Andrew Dunstan <andrew@dunslane.net> writes:
>> Generally I think this is now in fairly good shape, I've played with it
>> and it seems to do what I expect in every case, and the things I found
>> surprising are gone.
> Stepping back a bit ... do we really want to institutionalize the
> term "setting" for GUC variables?  I realize that the view pg_settings
> exists, but the documentation generally prefers the term "configuration
> parameters".  Where config.sgml uses "setting" as a noun, it's usually
> talking about a specific concrete value for a parameter, and you can
> argue that the view's name comports with that meaning.  But you can't
> GRANT a parameter's current value.
>
> I don't have a better name to offer offhand --- I surely am not proposing
> that we change the syntax to be "GRANT ... ON CONFIGURATION PARAMETER x",
> because that's way too wordy.  But now is the time to bikeshed if we're
> gonna bikeshed, or else admit that we're not interested in precise
> vocabulary.
>
> I'm also fairly allergic to the way that this patch has decided to assign
> multi-word names to privilege types (ie SET VALUE, ALTER SYSTEM).  There
> is no existing precedent for that, and I think it's going to break
> client-side code that we don't need to break.  It's not coincidental that
> this forces weird changes in rules about whitespace in the has_privilege
> functions, for example; and if you think that isn't going to cause
> problems I think you are wrong.  Perhaps we could just use "SET" and
> "ALTER", or "SET" and "SYSTEM"?


That's going to look weird, ISTM. This is less clear about what it's
granting.

     GRANT ALTER ON SOMETHING shared_buffers TO myuser;

If you don't like that maybe ALTER_SYSTEM and SET_VALUE would work,
although mostly we have avoided things like that.

How about MODIFY instead of SET VALUE and CONFIGURE instead of ALTER SYSTEM?

Personally I don't have problem with the use of SETTING. I think the
meaning is pretty plain in context and unlikely to produce any confusion.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Andrew Dunstan <andrew@dunslane.net> writes:
> On 3/16/22 14:47, Tom Lane wrote:
>> I'm also fairly allergic to the way that this patch has decided to assign
>> multi-word names to privilege types (ie SET VALUE, ALTER SYSTEM).  There
>> is no existing precedent for that, and I think it's going to break
>> client-side code that we don't need to break.  It's not coincidental that
>> this forces weird changes in rules about whitespace in the has_privilege
>> functions, for example; and if you think that isn't going to cause
>> problems I think you are wrong.  Perhaps we could just use "SET" and
>> "ALTER", or "SET" and "SYSTEM"?

> That's going to look weird, ISTM. This is less clear about what it's
> granting.
>      GRANT ALTER ON SOMETHING shared_buffers TO myuser;

True.  I think "GRANT SET" is clear enough, and it fits with the custom of
using the name of the SQL statement that the privilege allows you to
invoke.  (I gather from Mark's comments that Bison gave him problems with
that, but maybe that can be dealt with.)  But I concede that "ALTER" by
itself is pretty vague.

> If you don't like that maybe ALTER_SYSTEM and SET_VALUE would work,
> although mostly we have avoided things like that.
> How about MODIFY instead of SET VALUE and CONFIGURE instead of ALTER SYSTEM?

I thought about ALTER_SYSTEM too.  It's not great but maybe the best we
can do.  Not sure that CONFIGURE is better.

> Personally I don't have problem with the use of SETTING. I think the
> meaning is pretty plain in context and unlikely to produce any confusion.

I'm just unhappy about the disconnect with the documentation.  I wonder
if we could get away with s/configuration parameter/setting/g in the docs.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/16/22 16:53, Tom Lane wrote:

>> Personally I don't have problem with the use of SETTING. I think the
>> meaning is pretty plain in context and unlikely to produce any confusion.
> I'm just unhappy about the disconnect with the documentation.  I wonder
> if we could get away with s/configuration parameter/setting/g in the docs.
>
>             


I don't think we need to fix that here though. If you can live with
SETTING for now I will undertake to fix the docs post-feature-freeze if
necessary.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
<snip>

> I remain of the opinion that this
> patch should not concern itself with that, though.

So you are saying that people can add new object types to PG with DAC
permissions and not concern themselves with MAC capable hooks? Is that
an official PG community stance?



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Peter Eisentraut
Дата:
On 16.03.22 19:59, Mark Dilger wrote:
> Informally, we often use "GUC" on this list, but that isn't used formally, leaving "configuration parameter" and
"setting"as the two obvious choices.  I preferred "configuration parameter" originally and was argued out of it.  My
takeon "setting" was also that it more naturally refers to the choice of setting, not the thing being set, such that
"work_mem= 8192" means the configuration parameter "work_mem" has the setting "8192".
 

"The current setting of the work_mem parameter is 8192."

I think something based on "parameter" is good.  We also use that 
language in the protocol (e.g., ParameterStatus).



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Peter Eisentraut
Дата:
On 16.03.22 19:47, Tom Lane wrote:
> I'm also fairly allergic to the way that this patch has decided to assign
> multi-word names to privilege types (ie SET VALUE, ALTER SYSTEM).  There
> is no existing precedent for that, and I think it's going to break
> client-side code that we don't need to break.  It's not coincidental that
> this forces weird changes in rules about whitespace in the has_privilege
> functions, for example; and if you think that isn't going to cause
> problems I think you are wrong.  Perhaps we could just use "SET" and
> "ALTER", or "SET" and "SYSTEM"?

I think Oracle and MS SQL Server have many multi-word privilege names. 
So users are quite used to that.  And if we want to add more complex 
privileges, we might run out of sensible single words eventually.  So I 
would not exclude this approach.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
> On 16.03.22 19:47, Tom Lane wrote:
>> ...  Perhaps we could just use "SET" and
>> "ALTER", or "SET" and "SYSTEM"?

> I think Oracle and MS SQL Server have many multi-word privilege names. 
> So users are quite used to that.  And if we want to add more complex 
> privileges, we might run out of sensible single words eventually.  So I 
> would not exclude this approach.

Well, I still say that "SET" is sufficient for the one privilege name
(unless we really can't make Bison handle that, which I doubt).  But
I'm willing to yield on using "ALTER SYSTEM" for the other.

If we go with s/SETTING/PARAMETER/ as per your other message, then
that would be adequately consistent with the docs I think.  So it'd
be

GRANT { SET | ALTER SYSTEM } ON PARAMETER foo TO ...

and the new catalog would be pg_parameter_acl, and so on.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/17/22 10:47, Tom Lane wrote:
> Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
>> On 16.03.22 19:47, Tom Lane wrote:
>>> ...  Perhaps we could just use "SET" and
>>> "ALTER", or "SET" and "SYSTEM"?
>> I think Oracle and MS SQL Server have many multi-word privilege names. 
>> So users are quite used to that.  And if we want to add more complex 
>> privileges, we might run out of sensible single words eventually.  So I 
>> would not exclude this approach.
> Well, I still say that "SET" is sufficient for the one privilege name
> (unless we really can't make Bison handle that, which I doubt).  But
> I'm willing to yield on using "ALTER SYSTEM" for the other.
>
> If we go with s/SETTING/PARAMETER/ as per your other message, then
> that would be adequately consistent with the docs I think.  So it'd
> be
>
> GRANT { SET | ALTER SYSTEM } ON PARAMETER foo TO ...
>
> and the new catalog would be pg_parameter_acl, and so on.
>
>             



The upside of this is that it avoids the inelegant


    GRANT SET ON SETTING ...


But I was just looking again at the grammar, and the only reason we need
this keyword at all AFAICS is to disambiguate ALL [PRIVILEGES] cases. 
If we abandoned that for this form of GRANT/REVOKE I think we could
probably get away with


    GRANT { SET | ALTER SYSTEM } ON setting_name ...


I haven't tried it, so I could be all wrong.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Thu, Mar 17, 2022 at 9:25 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
> <snip>
>
> > I remain of the opinion that this
> > patch should not concern itself with that, though.
>
> So you are saying that people can add new object types to PG with DAC
> permissions and not concern themselves with MAC capable hooks? Is that
> an official PG community stance?

I don't know that the community has an official position on that
topic, but I do not think it's reasonable to expect everyone who
tinkers with MAC permissions to try to make a corresponding equivalent
for DAC. The number of people using PostgreSQL with DAC is relatively
small, and the topic is extremely complicated, and a lot of hackers
don't really understand it well enough to be sure that whatever they
might do is right. I think it's reasonable to expect people who
understand DAC and care about it to put some energy into the topic,
and not just in terms of telling other people how they have to write
their patches.

I *don't* think it's appropriate for a patch that touches MAC to
deliberately sabotage the existing support we have for DAC or to just
ignore it where the right thing to do is obvious. But maintaining a
million lines of code is a lot of work, and I can't think of any
reason why the burden of maintaining relatively little-used features
should fall entirely on people who don't care about them.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Wed, Mar 16, 2022 at 2:47 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Stepping back a bit ... do we really want to institutionalize the
> term "setting" for GUC variables?  I realize that the view pg_settings
> exists, but the documentation generally prefers the term "configuration
> parameters".  Where config.sgml uses "setting" as a noun, it's usually
> talking about a specific concrete value for a parameter, and you can
> argue that the view's name comports with that meaning.  But you can't
> GRANT a parameter's current value.

I agree that the lack of a good user-friendly term for GUCs is a real
problem. Here at EDB I've observed even relatively non-technical
people using that term, which appears nowhere in the documentation and
is utterly unintelligible to a typical end-user. Somebody gets on the
phone and tells the customer that they need to set a GUC and the
customer is like "what's a guck?" except that they probably don't
actually ask that question but are just confused and fail to
understand that a postgresql.conf change is being proposed. I hate it.
It sucks.

I have sort of been trying to promote the use of the word "setting"
and use it in my own writing, especially to end-users. That is
definitely more intelligible to random users, but it's admittedly also
awkward. "Set a setting" just sounds redundant. But "set a
configuration variable" sounds wordy, so I don't know.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 17, 2022, at 9:04 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>
> not just in terms of telling other people how they have to write
> their patches.

...

>  the burden of maintaining relatively little-used features
> should fall entirely on people who don't care about them.

Joshua helped test the DAC portion of this patch, and answered a number of my questions on the topic, including
in-personback in December.  I take your point, Robert, on the general principle, but the archives should reflect that
Joshuadid contribute to this specific patch. 

Joshua, should we drop the DAC portion for postgres 15 and revisit the issue for postgres 16?  I think it's getting
latein the development cycle to attempt what Tom described upthread, and I'd hate to see the rest of this patch punted. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Thu, Mar 17, 2022 at 12:19 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> Joshua helped test the DAC portion of this patch, and answered a number of my questions on the topic, including
in-personback in December.  I take your point, Robert, on the general principle, but the archives should reflect that
Joshuadid contribute to this specific patch. 

I wasn't intending to say otherwise, and if the changes needed for DAC
here are straightforward and don't make the patch significantly harder
to finish, then I would say Tom is wrong and we should just go ahead
and make them. But if they do, then I think it's perfectly fine to say
that we're going to leave that alone and let someone with
subject-matter expertise sort it out when they have time. We do that
all the time with other things, most notably MSVC builds, where we
just can't expect every hacker or even every committer to understand
exactly what's required to make it all work. I try my best to commit
things that don't break it, but sometimes I do, and then Andrew helps
sort it out, because he understands it and I don't. I think DAC should
fall into that kind of category as well.

--
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Thu, Mar 17, 2022 at 12:04 PM Robert Haas <robertmhaas@gmail.com> wrote:
>
> On Thu, Mar 17, 2022 at 9:25 AM Joshua Brindle
> <joshua.brindle@crunchydata.com> wrote:
> > <snip>
> >
> > > I remain of the opinion that this
> > > patch should not concern itself with that, though.
> >
> > So you are saying that people can add new object types to PG with DAC
> > permissions and not concern themselves with MAC capable hooks? Is that
> > an official PG community stance?
>
> I don't know that the community has an official position on that
> topic, but I do not think it's reasonable to expect everyone who
> tinkers with MAC permissions to try to make a corresponding equivalent
> for DAC. The number of people using PostgreSQL with DAC is relatively
> small, and the topic is extremely complicated, and a lot of hackers
> don't really understand it well enough to be sure that whatever they
> might do is right. I think it's reasonable to expect people who
> understand DAC and care about it to put some energy into the topic,
> and not just in terms of telling other people how they have to write
> their patches.
>
> I *don't* think it's appropriate for a patch that touches MAC to
> deliberately sabotage the existing support we have for DAC or to just
> ignore it where the right thing to do is obvious. But maintaining a
> million lines of code is a lot of work, and I can't think of any
> reason why the burden of maintaining relatively little-used features
> should fall entirely on people who don't care about them.

I agree with this view, outside of the mixup between MAC and DAC (DAC
is in-core, MAC is via hooks)

So there should be some committers or contributors that keep an eye
out and make sure new objects have appropriate hooks, and since an Oid
based hook for GUCs is inappropriate, I hope this patch can be rolled
into the contribution from Mark.

The attached patch adds a set of hooks for strings, and changes the
GUC patch from Mark to use it, I tested with this code:

void set_user_object_access_str(ObjectAccessType access, Oid classId,
const char *objName, int subId, void *arg)
{
if (next_object_access_hook_str)
{
(*next_object_access_hook_str)(access, classId, objName, subId, arg);
}
switch (access)
{
case OAT_POST_ALTER:
if (classId == SettingAclRelationId)
{
Oid userid = GetUserId();
bool is_superuser = superuser_arg(userid);
char *username = GETUSERNAMEFROMID(GetUserId());
const char *setting = "setting value";
const char *altering = "altering system";
const char *unknown = "unknown";
const char *action;
if (subId && ACL_SET_VALUE)
action = setting;
else if (subId && ACL_ALTER_SYSTEM)
action = altering;
else
action = unknown;

elog(WARNING, "%s is %s %s (%d)", username, action, objName, is_superuser);
if (strcmp(objName, "log_statement") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Setting %s blocked", objName)));
}
}
default:
break;
}
}

static void
set_user_object_access (ObjectAccessType access, Oid classId, Oid
objectId, int subId, void *arg)
{
if (next_object_access_hook)
{
(*next_object_access_hook)(access, classId, objectId, subId, arg);
}
switch (access)
{
case OAT_FUNCTION_EXECUTE:
{
/* Update the `set_config` Oid cache if necessary. */
set_user_cache_proc(InvalidOid);

/* Now see if this function is blocked */
set_user_block_set_config(objectId);
break;
}

/* fallthrough */

case OAT_POST_CREATE:
{
if (classId == ProcedureRelationId)
{
set_user_cache_proc(objectId);
}
break;
}
default:
break;
}
}

Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Robert Haas
Дата:
On Thu, Mar 17, 2022 at 12:30 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
> I agree with this view, outside of the mixup between MAC and DAC (DAC
> is in-core, MAC is via hooks)

An excellent point! Exactly why we need expert-level help with this stuff! :-)

> So there should be some committers or contributors that keep an eye
> out and make sure new objects have appropriate hooks, and since an Oid
> based hook for GUCs is inappropriate, I hope this patch can be rolled
> into the contribution from Mark.

I'm not going to take a position on this patch right this minute, but
I appreciate you providing it.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 17, 2022, at 9:29 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
>
> I hope this patch can be rolled
> into the contribution from Mark.

Working on it.... Thanks for the patch!

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Thu, Mar 17, 2022 at 12:36 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
> > On Mar 17, 2022, at 9:29 AM, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
> >
> > I hope this patch can be rolled
> > into the contribution from Mark.
>
> Working on it.... Thanks for the patch!

Great, thanks.

I missed one objectId reference (InvokeObjectDropHookStr), fixed
version attached.

Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 17, 2022, at 8:41 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
> If we abandoned that for this form of GRANT/REVOKE I think we could
> probably get away with
>
>
>     GRANT { SET | ALTER SYSTEM } ON setting_name ...
>
>
> I haven't tried it, so I could be all wrong.

Version 12 of the patch uses SET and ALTER SYSTEM as the names of the privileges, and PARAMETER as the name of the
thingon which the privilege is granted.  The catalog table which tracks these grants is now named pg_parameter_acl, and
variousother parts of the patch have been adjusted to use a "parameter" based, rather than a "setting" based, naming
scheme. One exception to this rule is the "setacl" column in pg_parameter_acl, which is much more compact than the
"parameteracl"name would be, so that remains under the old name. 

The tab completion for grants and revokes of "set" and "alter system" on parameters has been extended compared to the
previouslysubmitted patch.  The tab completion for grants and revokes of other privilege types remains fairly thin, as
Iresisted the urge to fix that generally, given that it is unrelated to this patch.  However, in making tab completion
forgrant/revoke on parameters work, this patch incidentally improves the coverage for other privilege types, in so far
asthe prefixes for those commands match the prefixes of these new commands.  

Version 12 also introduces \dcp (pneumonic, "Describe Configuration Parameter") for listing parameters, with \dcp+ also
showingthe acl, like: 

mark.dilger=# \dcp+ %client%
                               List of configuration parameters
            Parameter             | Context | Setting | Unit |       Access privileges
----------------------------------+---------+---------+------+--------------------------------
 client_connection_check_interval | user    | 0       | ms   |
 client_encoding                  | user    | UTF8    |      | "mark.dilger"=sA/"mark.dilger"
 client_min_messages              | user    | notice  |      | "mark.dilger"=sA/"mark.dilger"+
                                  |         |         |      | =s/"mark.dilger"              +
                                  |         |         |      | alice=A*/"mark.dilger"
(3 rows)

The "Access privileges" column is blank for parameters which have neither been granted nor revoked.  In the example
above,that's true for client_connection_check_interval. 

\dcp[+] only shows "user" and "superuser" parameters:

mark.dilger=# \dcp %wal%
   List of configuration parameters
        Parameter         |  Context
--------------------------+-----------
 track_wal_io_timing      | superuser
 wal_compression          | superuser
 wal_consistency_checking | superuser
 wal_init_zero            | superuser
 wal_recycle              | superuser
 wal_sender_timeout       | user
 wal_skip_threshold       | user
(7 rows)

whereas \dcpS[+] also shows all parameters (so, also "sighup", "backend", "superuser-backend", "postmaster", and
"internal".)

mark.dilger=# \dcpS %wal%
      List of configuration parameters
           Parameter           |  Context
-------------------------------+------------
 max_slot_wal_keep_size        | sighup
 max_wal_senders               | postmaster
 max_wal_size                  | sighup
 min_wal_size                  | sighup
 track_wal_io_timing           | superuser
 wal_block_size                | internal
 wal_buffers                   | postmaster
 wal_compression               | superuser
 wal_consistency_checking      | superuser
 wal_init_zero                 | superuser
 wal_keep_size                 | sighup
 wal_level                     | postmaster
 wal_log_hints                 | postmaster
 wal_receiver_create_temp_slot | sighup
 wal_receiver_status_interval  | sighup
 wal_receiver_timeout          | sighup
 wal_recycle                   | superuser
 wal_retrieve_retry_interval   | sighup
 wal_segment_size              | internal
 wal_sender_timeout            | user
 wal_skip_threshold            | user
 wal_sync_method               | sighup
 wal_writer_delay              | sighup
 wal_writer_flush_after        | sighup
(24 rows)



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/24/22 12:49, Mark Dilger wrote:
>
>> On Mar 17, 2022, at 8:41 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>>
>> If we abandoned that for this form of GRANT/REVOKE I think we could
>> probably get away with
>>
>>
>>     GRANT { SET | ALTER SYSTEM } ON setting_name ...
>>
>>
>> I haven't tried it, so I could be all wrong.
> Version 12 of the patch uses SET and ALTER SYSTEM as the names of the privileges, and PARAMETER as the name of the
thingon which the privilege is granted.  The catalog table which tracks these grants is now named pg_parameter_acl, and
variousother parts of the patch have been adjusted to use a "parameter" based, rather than a "setting" based, naming
scheme. One exception to this rule is the "setacl" column in pg_parameter_acl, which is much more compact than the
"parameteracl"name would be, so that remains under the old name.
 


I can live with it I guess, but it seems perverse to me to have
pg_settings but pg_paramater_acl effectively referring to the same set
of things. If we're going to do this perhaps we should create a
pg_parameters view which is identical to pg_settings and deprecate
pg_settings. I don;t want to hold up this patch, I think this can
probably be managed as a follow up item.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 24, 2022, at 12:06 PM, Andrew Dunstan <andrew@dunslane.net> wrote:
>
>
> On 3/24/22 12:49, Mark Dilger wrote:
>>
>>> On Mar 17, 2022, at 8:41 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
>>>
>>> If we abandoned that for this form of GRANT/REVOKE I think we could
>>> probably get away with
>>>
>>>
>>>    GRANT { SET | ALTER SYSTEM } ON setting_name ...
>>>
>>>
>>> I haven't tried it, so I could be all wrong.
>> Version 12 of the patch uses SET and ALTER SYSTEM as the names of the privileges, and PARAMETER as the name of the
thingon which the privilege is granted.  The catalog table which tracks these grants is now named pg_parameter_acl, and
variousother parts of the patch have been adjusted to use a "parameter" based, rather than a "setting" based, naming
scheme. One exception to this rule is the "setacl" column in pg_parameter_acl, which is much more compact than the
"parameteracl"name would be, so that remains under the old name. 
>
>
> I can live with it I guess, but it seems perverse to me to have
> pg_settings but pg_paramater_acl effectively referring to the same set
> of things. If we're going to do this perhaps we should create a
> pg_parameters view which is identical to pg_settings and deprecate
> pg_settings. I don;t want to hold up this patch, I think this can
> probably be managed as a follow up item.

Right, the version 12 patch was following Peter's and Tom's comments upthread:

> On Mar 17, 2022, at 7:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Well, I still say that "SET" is sufficient for the one privilege name
> (unless we really can't make Bison handle that, which I doubt).  But
> I'm willing to yield on using "ALTER SYSTEM" for the other.
>
> If we go with s/SETTING/PARAMETER/ as per your other message, then
> that would be adequately consistent with the docs I think.  So it'd
> be
>
> GRANT { SET | ALTER SYSTEM } ON PARAMETER foo TO ...
>
> and the new catalog would be pg_parameter_acl, and so on.

We could debate that again, but it seems awfully late in the development cycle.  I'd rather just get this committed,
barringany objections? 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
I've started reviewing this patch in earnest, and almost immediately
hit a serious problem: these regression tests aren't even a little bit
committable.  For one thing, they fail if you do "make installcheck"
twice in a row.  This seems to be because the first run leaves some
cruft behind in pg_parameter_acl that affects the results of
subsequent runs.  But the bigger problem is that it is ABSOLUTELY NOT
OKAY to test ALTER SYSTEM during an "installcheck" run.  That would be
true even if you cleaned up all the settings by the end of the run, as
the patch fails to do (and for extra demerit, it leaves a superuser
role laying around).  Even transient effects on the behavior of
sessions in other DBs aren't acceptable in installcheck mode, IMO.

I think we probably have to trash the core-regression-tests part of
the patch altogether and instead use a TAP test for whatever testing
we want to do.  It might be all right to test SET privileges without
testing ALTER SYSTEM, but I'm not sure there's much point in that.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 28, 2022, at 11:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> I think we probably have to trash the core-regression-tests part of
> the patch altogether and instead use a TAP test for whatever testing
> we want to do.  It might be all right to test SET privileges without
> testing ALTER SYSTEM, but I'm not sure there's much point in that.

How about putting them under src/test/modules/unsafe_tests ?

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> Version 12 also introduces \dcp (pneumonic, "Describe Configuration Parameter") for listing parameters, with \dcp+
alsoshowing the acl, like: 

The fact that that code is not dry behind the ears is painfully obvious.
It's not documented in psql-ref, not tested anywhere AFAICS, and its
handling of the pattern parameter is inconsistent with every other
\d command.  The wildcard character should be * not %.  It only
accidentally fails to dump core if no pattern is given, too.

> \dcp[+] only shows "user" and "superuser" parameters:

Why make that restriction?  Also, I find it astonishing that this doesn't
show the GUC's value by default.  The non-plus form of the command seems
useless as it stands, or at least its intended use-case is so narrow
I can't see it.  If we're to have it at all, it seems like it ought to
be a reasonably useful shortcut for interrogating pg_settings.  I'd
expect the base set of columns to be name, value, and possibly unit;
then add ACL with +.  I'm not sure that GucContext belongs in this at all,
but if it does, it's a + column.

On the whole perhaps this should be taken out again; it's a bit
late in the cycle to be introducing new features, especially ones
as subject to bikeshedding as a \d command is.  My ideas about what
columns to show probably wouldn't match anyone else's ... and we
haven't even gotten to whether \dcp is an okay choice of name.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> On Mar 28, 2022, at 11:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I think we probably have to trash the core-regression-tests part of
>> the patch altogether and instead use a TAP test for whatever testing
>> we want to do.  It might be all right to test SET privileges without
>> testing ALTER SYSTEM, but I'm not sure there's much point in that.

> How about putting them under src/test/modules/unsafe_tests ?

Ah, that could work; I'd forgotten about that subdirectory.

It'd still be a good idea if they didn't fail when run twice in a row.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 28, 2022, at 12:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> Version 12 also introduces \dcp (pneumonic, "Describe Configuration Parameter") for listing parameters, with \dcp+
alsoshowing the acl, like: 
>
> The fact that that code is not dry behind the ears is painfully obvious.

<snip>

> On the whole perhaps this should be taken out again

I added the \dcp command after noticing that doc/src/sgml/ddl.sgml's "Summary of Access Privileges" table had an empty
columnfor the "psql Command" column for the "PARAMETER" row.  Maybe that's ok.  I thought it would be better if I added
acommand for that. 

I don't feel too strongly about it, so I'm removing \dcp from this patch with the intention of introducing it as a
separatepatch for the postgres 16 development cycle.  For now, I'm just putting "none" in that table cell. 


This patch also moves the grant/revoke tests in guc_privs.sql into the unsafe_tests directory, fixes them up to be
repeatable,and drops the superuser role that had been negligently left around in prior patches.  The tests that had
beenadded in privileges.sql are simply removed. 



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
I just came across something odd in v12 that is still there in v13:
ExecGrant_Parameter uses InvokeObjectPostAlterHook not
InvokeObjectPostAlterHookArgStr.  This seems pretty inconsistent.
Is there a good argument for it?

... or, for that matter, why is there any such call at all?
No other GRANT/REVOKE operation calls such a hook.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 28, 2022, at 2:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> I just came across something odd in v12 that is still there in v13:
> ExecGrant_Parameter uses InvokeObjectPostAlterHook not
> InvokeObjectPostAlterHookArgStr.  This seems pretty inconsistent.
> Is there a good argument for it?
>

For SET and ALTER SYSTEM, the target of the action may not have an entry in pg_parameter_acl, nor an assigned Oid
anywhere,so the only consistent way to pass the argument to the hook is by name.  For GRANT/REVOKE, the parameter must
havean Oid, at least by the time the hook gets called.  Upthread there was some discussion of a hook not being able to
assumea snapshot and working transaction, and hence not being able to query the catalogs.  I would think that in a
GRANTor REVOKE that hasn't already errored, the hook would have a transaction and could look up whatever it likes?
Thereis a CommandCounterIncrement() call issued in objectNamesToOids() for new parameters, so by the time the hook is
runningit should be able to see the parameter. 

Am I reasoning about this the wrong way?

> ... or, for that matter, why is there any such call at all?
> No other GRANT/REVOKE operation calls such a hook.

I think ALTER DEFAULT PRIVILEGES does, though that's not quite the same thing.  I don't have a strong opinion on this.
Joshua,what's your take? 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> On Mar 28, 2022, at 2:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I just came across something odd in v12 that is still there in v13:
>> ExecGrant_Parameter uses InvokeObjectPostAlterHook not
>> InvokeObjectPostAlterHookArgStr.  This seems pretty inconsistent.
>> Is there a good argument for it?

> For SET and ALTER SYSTEM, the target of the action may not have an entry
> in pg_parameter_acl, nor an assigned Oid anywhere, so the only
> consistent way to pass the argument to the hook is by name.  For
> GRANT/REVOKE, the parameter must have an Oid, at least by the time the
> hook gets called.

Yeah, I know it's *possible* to make this work.  The question is why is
it good to do it like this rather than to use the string API, now that
we have the latter.  AFAICS this way just guarantees that the hook must
do a catalog lookup in order to figure out what you're talking about.

The core point here is that the actual identity of a GUC is its name.
Any OID that may exist in pg_parameter_acl is just a nonce alias that
means nothing to anybody.  Anyone who's trying to, say, enforce that
Joe Blow can't change shared_buffers is going to need to see the GUC
name.  (I am, btw, busy doing a lot of renaming in the patch to try
to clarify that these OIDs are not identifiers for GUCs; imagining
that they are just risks confusion.)

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 28, 2022, at 2:54 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Yeah, I know it's *possible* to make this work.  The question is why is
> it good to do it like this rather than to use the string API, now that
> we have the latter.  AFAICS this way just guarantees that the hook must
> do a catalog lookup in order to figure out what you're talking about.

Ok, thanks for clarifying.  I took the *HookStr versions of the hooks to be an alternative to be used when no Oid was
present,something of a last resort.  I never thought much about using them under other circumstances. 

> The core point here is that the actual identity of a GUC is its name.
> Any OID that may exist in pg_parameter_acl is just a nonce alias that
> means nothing to anybody.  Anyone who's trying to, say, enforce that
> Joe Blow can't change shared_buffers is going to need to see the GUC
> name.  (I am, btw, busy doing a lot of renaming in the patch to try
> to clarify that these OIDs are not identifiers for GUCs; imagining
> that they are just risks confusion.)

I was about to write another patch using the HookStr form, but if you are already editing, then I'll let you make the
change. I don't see a problem with what you are proposing. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
I'm going to be mostly unavailable till Wednesday, but I'll leave
you with another thing to chew on:

regression=# create user joe;
CREATE ROLE
regression=# grant set on parameter plpgsql.extra_warnings to joe;
ERROR:  unrecognized configuration parameter "plpgsql.extra_warnings"

This is problematic, because once plpgsql is loaded it works:

regression=# load 'plpgsql';
LOAD
regression=# grant set on parameter plpgsql.extra_warnings to joe;
GRANT

If we then do

$ pg_dumpall -g

it falls over:
pg_dumpall: error: query failed: ERROR:  unrecognized configuration parameter "plpgsql.extra_warnings"

apparently because aclparameterdefault() is allergic to being asked about
unknown GUCs, and plpgsql is not loaded in pg_dumpall's session.  But
if pg_dumpall hadn't failed, it'd produce a dump script containing that
same command, which would fail at load time (because, again, plpgsql
isn't going to be loaded in the backend reading the restore script).

This is what I meant by saying that you can't just refuse to GRANT on
unknown GUCs.  It makes custom GUCs into a time bomb for dump/restore.
And that means you need a strategy for dealing with the possibility
that you don't know whether the GUC is USERSET or not.  I think though
that it might work to just assume that it isn't, in which case dumps
on unrecognized GUCs that really are USERSET will end up issuing an
explicit GRANT SET TO PUBLIC that we didn't actually need to do, but it
won't hurt anything.  (Testing that assertion would be a good thing
to do.)

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> I was about to write another patch using the HookStr form, but if you are already editing, then I'll let you make the
change. I don't see a problem with what you are proposing. 

Don't sweat about that, I can easily rebase what I've done so far
over your updates.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 28, 2022, at 3:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> This is what I meant by saying that you can't just refuse to GRANT on
> unknown GUCs.  It makes custom GUCs into a time bomb for dump/restore.
> And that means you need a strategy for dealing with the possibility
> that you don't know whether the GUC is USERSET or not.  I think though
> that it might work to just assume that it isn't, in which case dumps
> on unrecognized GUCs that really are USERSET will end up issuing an
> explicit GRANT SET TO PUBLIC that we didn't actually need to do, but it
> won't hurt anything.  (Testing that assertion would be a good thing
> to do.)

Ok, I returned to the idea upthread for a solution to this problem.  A grant or revoke on an unrecognized custom
parameterwill create a SUSET placeholder, which is not quite right in some cases.  However, the installation scripts
formodules have been updated to manually grant SET privilege on their custom USERSET parameters, which cleans up the
problem,with one exception:  if the user executes a "revoke set on parameter some.such from public" prior to loading
themodule which defines parameter some.such, that revoke won't be retained.  That doesn't seem entirely wrong to me,
sinceno privilege to set the parameter existed when the revoke was performed, but rather was granted along with the
creationof the parameter, but it also doesn't seem entirely right.  Maybe revoke commands (but not grant commands)
shoulderror on unrecognized custom parameters?  I didn't implement that here, but can do so if you think that makes
moresense than this new behavior. 

I changed add_placeholder_variable() to take a GucContext argument.  It previously always used PGC_USERSET, which is
whatall pre-existing call sites now pass into it, but that seems a bit inappropriate where we're creating a placeholder
thatwe intend to treat as a SUSET variable until such time as a module gets installed saying otherwise.  Not changing
add_placeholder_variablein this fashion seems to work just fine.  I just didn't feel comfortable with doing it that
way. But if you feel it generates needless code churn, I could be talked out of doing this. 

I also changed the patch to use the ...HookStr functions for parameters.  I would really like a comment on this from
Joshua,to be sure what I'm doing comports with what he wanted.  In particular, I'm uncertain that simply passing the
AclMode(in other words, the istmt->privileges field) for the grant/revoke to the hook is sufficient.  For one, how does
thehook want to distinguish grants from revokes?  Do we want a bit for that?  And what about distinguishing WITH GRANT
OPTION? I think the hooks are usable right now, but they might be made better. 


—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
"David G. Johnston"
Дата:
On Tue, Mar 29, 2022 at 9:00 PM Mark Dilger <mark.dilger@enterprisedb.com> wrote: 
 A grant or revoke on an unrecognized custom parameter will create a SUSET placeholder,
[...] 
which cleans up the problem, with one exception:  if the user executes a "revoke set on parameter some.such from public" prior to loading the module which defines parameter some.such, that revoke won't be retained.  That doesn't seem entirely wrong to me, since no privilege to set the parameter existed when the revoke was performed, but rather was granted along with the creation of the parameter, but it also doesn't seem entirely right.  Maybe revoke commands (but not grant commands) should error on unrecognized custom parameters?

The only revoke role target that makes sense here is the default grant given to public.  Aside from that technicality the grant system is purely additive and so only the GRANTS end up retained so far as perpetual state is concerned.

For a revoke that doesn't target public we should remove the corresponding unrecognized setting grant if one is present.  We don't generally/always raise a notice if a revoke doesn't actually cause something to be ungranted -  though I'm partial to being so informed.  I suppose we could distinguish the cases where the not-yet-loaded setting name is unrecognized by the system from the one where it is recognized but the grant is actually on a different role (or a revoke entry from public for the setting name is present).

For a revoke of an unknown setting from public we should keep an entry somewhere that tells the system that the default grant to public for that setting has been revoked.  Maybe there isn't the same timing concern here as there is for GRANT, but if only for symmetry it seems like a good thing to implement.

I have the impression I'm missing something in what I wrote above but cannot quite figure out what.  In any case as a first pass at this the behavior described is kinda what I'm expecting.

David J.

P.S.

Skimming the patch we are, to my agreement, not touching the ALTER DEFAULT PRIVILEGES command to work with this feature.  Should the omission be noted explicitly?  At least in the commit message I would think.  Though the sentence in [1] "Also, these default privilege settings can be overridden using the ALTER DEFAULT PRIVILEGES command." is rendered only partially correct.


P.P.S.

+   The default privileges for a <literal>user</literal> parameter allow
+   <literal>PUBLIC</literal> to <command>SET</command> and
+   <command>RESET</command> the assigned value.  By default,
+   <literal>PUBLIC</literal> has no privileges on
+   <literal>postmaster</literal>, <literal>superuser-backend</literal>,
+   <literal>internal</literal>, <literal>backend</literal>,
+   <literal>sighup</literal>, and <literal>superuser</literal> parameters.

Can we rephrase this to something like:

By default, PUBLIC has no privileges on parameters in the postmaster, ..., and superuser contexts.

pg_settings.context exists and those are the values found there.  My initial interpretation of the wording was the postmaster, etc..., were themselves parameters, not containers for many parameters.

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Joshua Brindle
Дата:
On Wed, Mar 30, 2022 at 12:00 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
>
> > On Mar 28, 2022, at 3:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >
> > This is what I meant by saying that you can't just refuse to GRANT on
> > unknown GUCs.  It makes custom GUCs into a time bomb for dump/restore.
> > And that means you need a strategy for dealing with the possibility
> > that you don't know whether the GUC is USERSET or not.  I think though
> > that it might work to just assume that it isn't, in which case dumps
> > on unrecognized GUCs that really are USERSET will end up issuing an
> > explicit GRANT SET TO PUBLIC that we didn't actually need to do, but it
> > won't hurt anything.  (Testing that assertion would be a good thing
> > to do.)
>
> Ok, I returned to the idea upthread for a solution to this problem.  A grant or revoke on an unrecognized custom
parameterwill create a SUSET placeholder, which is not quite right in some cases.  However, the installation scripts
formodules have been updated to manually grant SET privilege on their custom USERSET parameters, which cleans up the
problem,with one exception:  if the user executes a "revoke set on parameter some.such from public" prior to loading
themodule which defines parameter some.such, that revoke won't be retained.  That doesn't seem entirely wrong to me,
sinceno privilege to set the parameter existed when the revoke was performed, but rather was granted along with the
creationof the parameter, but it also doesn't seem entirely right.  Maybe revoke commands (but not grant commands)
shoulderror on unrecognized custom parameters?  I didn't implement that here, but can do so if you think that makes
moresense than this new behavior. 
>
> I changed add_placeholder_variable() to take a GucContext argument.  It previously always used PGC_USERSET, which is
whatall pre-existing call sites now pass into it, but that seems a bit inappropriate where we're creating a placeholder
thatwe intend to treat as a SUSET variable until such time as a module gets installed saying otherwise.  Not changing
add_placeholder_variablein this fashion seems to work just fine.  I just didn't feel comfortable with doing it that
way. But if you feel it generates needless code churn, I could be talked out of doing this. 
>
> I also changed the patch to use the ...HookStr functions for parameters.  I would really like a comment on this from
Joshua,to be sure what I'm doing comports with what he wanted.  In particular, I'm uncertain that simply passing the
AclMode(in other words, the istmt->privileges field) for the grant/revoke to the hook is sufficient.  For one, how does
thehook want to distinguish grants from revokes?  Do we want a bit for that?  And what about distinguishing WITH GRANT
OPTION? I think the hooks are usable right now, but they might be made better. 

I had not even been thinking about hooking grant/revoke TBH. In
SELinux-type MAC systems we don't always try to control DAC permission
changes, and when we do it's not granular, all permission changes just
boil down to setattr permission or something of that nature.

Thank you.



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
After sleeping on it, I have a modest proposal for simplifying
these issues.  Consider this design:

1. In the SET code path, we assume (without any catalog lookup)
that USERSET GUCs can be set.  Only for SUSET GUCs do we perform
a permissions lookup.  (ALTER SYSTEM does a lookup in both cases.)

2. Given this, the default ACL for any GUC can be empty, greatly
simplifying all these management issues.  Superusers could do what
they want anyway, so modeling an "owner's default grant" becomes
unnecessary.

What this loses is the ability to revoke public SET permissions
on USERSET GUCs.  I claim that that is not so valuable as to
justify all the complication needed to deal with it.  (If a GUC
seems to require some defenses, why is it USERSET?)  Avoiding
a permissions lookup in the default SET code path seems like
a pretty important benefit, too.  If we force that to happen
it's going to be a noticeable drag on functions with SET clauses.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 30, 2022, at 6:26 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Consider this design:

Isn't that just the design I had implemented in v8 several months ago?

Subject: [PATCH v8] Allow GRANT of SET and ALTER SYSTEM SET for gucs

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.  Worse, there is no way to
revoke the privilege to SET a userset variable.  To remedy that,
most core userset variables have been changed to suset, with
explicit grants to set the variable to public.


I don't think v9 ever got posted to the list, but v10 has:

Subject: [PATCH v10] Allow grant and revoke of privileges on settings

Allow grant and revoke of privileges to set or alter system set
configuration variables.  Each (role,variable,privilege) triple can
be independently granted or revoked, so a user may be granted
privilege to SET but not to ALTER SYSTEM SET on a variable, or vice
versa.  Privilege to SET a userset variable is implicitly granted to
public, but may be revoked.


If we want to backtrack to v8, that's fine.  I can rebase that, port some of the other changes from v14 to it, and
repostit as v15.  We should review the conversation from December and January which included some arguments for
allowingrevokes of SET on USERSET from PUBLIC.  I don't want to keep going around in circles on this. 


—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Mar 30, 2022, at 6:59 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> We should review the conversation from December and January which included some arguments for allowing revokes of SET
onUSERSET from PUBLIC.  I don't want to keep going around in circles on this. 

Hmm, I guess that conversation was mostly off-list at the PGConn in December.  I made a reference to it upthread:

> On Mar 6, 2022, at 2:40 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> Userset variables are implicitly settable by any user.  There was a request, off-list as I recall, to make it
possibleto revoke the privilege to set variables such as "work_mem".  To make that possible, but not change the default
behaviorvis-a-vis prior releases, I upgraded most userset variables to suset with a corresponding grant to public on
thevariable.  Sites which wish to have a more restrictive policy on such variables can revoke that privilege from
publicand instead issue more restrictive grants.  There were a few variables where such treatment didn't seem sensible,
suchas ones to do with client connections, and I left them alone.  I didn't insist on a defense for why any particular
settingneeded to be revocable in order to apply this treatment.  My assumption was that sites should be allowed to
determinetheir own security policies per setting unless there is a technical difficulty for a given setting that would
makeit overly burdensome to implement. 

Your proposal to just punt on supporting revocation of set on userset from public seems fine.  We could revisit that in
thenext development cycle if anyone really wants to defend it.  In particular, I don't see that committing this feature
withoutthat part would create any additional backward compatibility problems when implementing that later. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 3/30/22 09:26, Tom Lane wrote:
> After sleeping on it, I have a modest proposal for simplifying
> these issues.  Consider this design:
>
> 1. In the SET code path, we assume (without any catalog lookup)
> that USERSET GUCs can be set.  Only for SUSET GUCs do we perform
> a permissions lookup.  (ALTER SYSTEM does a lookup in both cases.)
>
> 2. Given this, the default ACL for any GUC can be empty, greatly
> simplifying all these management issues.  Superusers could do what
> they want anyway, so modeling an "owner's default grant" becomes
> unnecessary.
>
> What this loses is the ability to revoke public SET permissions
> on USERSET GUCs.  I claim that that is not so valuable as to
> justify all the complication needed to deal with it.  (If a GUC
> seems to require some defenses, why is it USERSET?)  Avoiding
> a permissions lookup in the default SET code path seems like
> a pretty important benefit, too.  If we force that to happen
> it's going to be a noticeable drag on functions with SET clauses.
>
>             



The last point is telling, so +1


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
"David G. Johnston"
Дата:
On Wed, Mar 30, 2022 at 8:12 AM Andrew Dunstan <andrew@dunslane.net> wrote:

On 3/30/22 09:26, Tom Lane wrote:

>
> What this loses is the ability to revoke public SET permissions
> on USERSET GUCs.  I claim that that is not so valuable as to
> justify all the complication needed to deal with it.

Agreed, and in line with my thinking from last night.  These default public set grants are indeed the complication and I'm good with the status quo where they are non-revocable.

I'm finding it curious that we are choosing to document every (all 6) context that doesn't have this default privilege instead of saying that only the user context variables are granted this default, and now irrevocable, default set privilege.  This is in addition to making sure we distinguish between parameter and context in my earlier email.
 
> Avoiding
> a permissions lookup in the default SET code path seems like
> a pretty important benefit, too.  If we force that to happen
> it's going to be a noticeable drag on functions with SET clauses.
>
>                       
The last point is telling, so +1

 
Indeed. +1
 
David J.

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> Your proposal to just punt on supporting revocation of set on userset from public seems fine.  We could revisit that
inthe next development cycle if anyone really wants to defend it.  In particular, I don't see that committing this
featurewithout that part would create any additional backward compatibility problems when implementing that later. 

Yeah.  Also, as you noted, we could mark some individual built-in
variables as SUSET and add a default GRANT.  I don't want to do that with
a blunderbuss, but perhaps there's an argument to do it for specific
cases (search_path comes to mind, though the performance cost could be
significant, since I think setting that in function SET clauses is
common).

For now, though, saying that you can't restrict SET for USERSET variables
seems fine --- there's certainly no loss of capability compared to where
we stand today.  I'd prefer to get the feature committed in that form
and then look at whether we want to tighten things around the margins.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
"David G. Johnston"
Дата:
On Wed, Mar 30, 2022 at 8:46 AM Tom Lane <tgl@sss.pgh.pa.us> wrote: 
I don't want to do that with
a blunderbuss, but perhaps there's an argument to do it for specific
cases (search_path comes to mind, though the performance cost could be
significant, since I think setting that in function SET clauses is
common).
 

I suspect it became considerably moreso when we fixed the search_path CVE since we basically told people that doing so, despite the possible performance hit, was the easiest solution to their immediate dump/restore failures.  But ISTM that because that SET has a function invocation context it could bypass any such check.  Though maybe the DO command exposes a flaw in that idea.
David J.

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> If we want to backtrack to v8, that's fine.  I can rebase that, port
> some of the other changes from v14 to it, and repost it as v15.

Are you working on that?  I've set aside time this week to hopefully
get this over the finish line, but I don't want to find out that
I've been duplicating your effort.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Apr 4, 2022, at 8:36 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> If we want to backtrack to v8, that's fine.  I can rebase that, port
>> some of the other changes from v14 to it, and repost it as v15.
>
> Are you working on that?  I've set aside time this week to hopefully
> get this over the finish line, but I don't want to find out that
> I've been duplicating your effort.

Yes, I expect to be posting the latest in maybe an hour?  I believe the latest patch (just reviewing, adjusting code
comments,etc.) that I'm preparing to post has all the changes we've discussed, aside from your parameterId renaming. 

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Apr 4, 2022, at 8:47 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
>
> posting the latest in maybe an hour

Here it is:



—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




Вложения

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> [ v15 patch ]

Thanks.  As I'm working through this, I'm kind of inclined to drop
the has_parameter_privilege() variants that take an OID as object
identifier.  This gets back to the fact that I don't think
pg_parameter_acl OIDs have any outside use; we wouldn't even have
them except that we need a way to track their role dependencies
in pg_shdepend.  AFAICS users will only ever be interested in
looking up a GUC by name.  Any objections?

Another thought here is that I see you're expending some code
to store the canonical name of a GUC in pg_parameter_acl, but
I think that's probably going too far.  In particular, for the
legacy mixed-case names like "DateStyle", what ends up in the
table is the mixed-case name, which seems problematic.  It would
definitely be problematic if an extension used such a name,
because we might or might not be aware of the idiosyncratic
casing at the time a GRANT is issued.  I'm thinking that we
really want to avoid looking up custom GUCs at all during GRANT,
because that can't do anything except create hazards.

So I think that instead of what you've got here, we should
(1) apply the map_old_guc_names[] mapping, which is constant
    (for any one PG release anyway)
(2) smash to lower case
(3) verify validity per valid_variable_name.

This also simplifies life on the lookup side, where it's sufficient
to do steps (1) and (2) before performing a catalog search.

Thoughts?

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Andrew Dunstan
Дата:
On 4/4/22 17:26, Tom Lane wrote:
> Mark Dilger <mark.dilger@enterprisedb.com> writes:
>> [ v15 patch ]
> Thanks.  As I'm working through this, I'm kind of inclined to drop
> the has_parameter_privilege() variants that take an OID as object
> identifier.  This gets back to the fact that I don't think
> pg_parameter_acl OIDs have any outside use; we wouldn't even have
> them except that we need a way to track their role dependencies
> in pg_shdepend.  AFAICS users will only ever be interested in
> looking up a GUC by name.  Any objections?


Not from me


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Apr 4, 2022, at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Thanks.  As I'm working through this, I'm kind of inclined to drop
> the has_parameter_privilege() variants that take an OID as object
> identifier.  This gets back to the fact that I don't think
> pg_parameter_acl OIDs have any outside use; we wouldn't even have
> them except that we need a way to track their role dependencies
> in pg_shdepend.  AFAICS users will only ever be interested in
> looking up a GUC by name.  Any objections?

None.

> Another thought here is that I see you're expending some code
> to store the canonical name of a GUC in pg_parameter_acl, but
> I think that's probably going too far.  In particular, for the
> legacy mixed-case names like "DateStyle", what ends up in the
> table is the mixed-case name, which seems problematic.  It would
> definitely be problematic if an extension used such a name,
> because we might or might not be aware of the idiosyncratic
> casing at the time a GRANT is issued.  I'm thinking that we
> really want to avoid looking up custom GUCs at all during GRANT,
> because that can't do anything except create hazards.

Yikes.  It took a few tries to see what you mean.  Yes, if the GRANT happens before the LOAD, that can have bad
consequences. So I agree something should be changed. 

> So I think that instead of what you've got here, we should
> (1) apply the map_old_guc_names[] mapping, which is constant
>    (for any one PG release anyway)
> (2) smash to lower case
> (3) verify validity per valid_variable_name.
>
> This also simplifies life on the lookup side, where it's sufficient
> to do steps (1) and (2) before performing a catalog search.
>
> Thoughts?

That sounds right.  Do you already have something like that coded, or would you like me to post a patch?

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
> On Apr 4, 2022, at 2:26 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> So I think that instead of what you've got here, we should
>> (1) apply the map_old_guc_names[] mapping, which is constant
>> (for any one PG release anyway)
>> (2) smash to lower case
>> (3) verify validity per valid_variable_name.
>>
>> This also simplifies life on the lookup side, where it's sufficient
>> to do steps (1) and (2) before performing a catalog search.
>>
>> Thoughts?

> That sounds right.  Do you already have something like that coded, or would you like me to post a patch?

Wrote it already, no need for you to do it.

            regards, tom lane



Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Mark Dilger
Дата:

> On Apr 4, 2022, at 5:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Wrote it already, no need for you to do it.

Thanks!

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company






Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
Here's a v16, which I have editorialized on rather heavily.
I have not yet really looked at the docs, but I'm satisfied
with the code and tests now.

The main non-cosmetic changes I made:

* I got rid of the mechanisms that tried to make has_parameter_privilege()
act like USERSET GUCs have a forced public SET grant.  I think that's
fundamentally unworkable for custom GUCs, and it's just going to create
confusion and inconsistency.  As the code stands, it'll report SET
privilege if you're superuser or have been explicitly granted that
privilege, but having that privilege only matters if the GUC is SUSET.
Need to craft some documentation wording to explain that, but I'd rather
explain that than say "it might or might not tell you that SET privileges
exist".  In any case I think this is perfectly symmetrical with the
ALTER SYSTEM privilege: you can grant that all you want, but it still
won't let you set variables whose context value forbids it.

* That meant we have only one default ACL setting, so I was able to
get rid of aclparameterdefault() altogether.

* I took out the post-alter hook call in ExecGrant_Parameter.  I'm
prepared to listen to arguments why it should be put back in, but
said arguments would have to explain why GRANT ON PARAMETER should
do that when no other form of GRANT does.  (Or, possibly, it'd be
sane for all the forms to call that hook; but that would be material
for a different patch IMV.)

* I took out the pg_parameter_privileges view, which was undocumented
anyway, and which I think people would not find useful.  The fact that
it's missing entries for most GUCs and might contain bogus entries
for nonexistent GUCs seems like a strong reason why it's not very
useful in this form.  (But see below.)

* I thought the tab-completion stuff was completely excessive.
I got rid of the support for tab completion with both privileges
spelled out, because I think people are just going to write ALL
instead.  We don't have tab completion support for commands listing
multiple privileges of other object types, and I don't see why
PARAMETER is the place to start.  I also merged a bunch of duplicative
rules by relying on MatchAny where possible.


Aside from documentation, there is one loose end that perhaps would
be best done in a follow-up patch: I'm somewhat of the opinion that
we ought to reinvent a \dcp command for psql.  I'd define it as being
a compact version of the pg_settings view, showing the name, value,
and unit columns and probably not much else by default.  With "+",
we could add on the privileges if any, and perhaps the context since
you need that to interpret the privileges.  I think this'd be more
useful than the pg_parameter_privileges view, and it'd be handier
than pg_settings itself since you could do things like
    \dcp autovac*
with a lot less typing than it takes to get the same info
out of pg_settings directly.

Anyway, I'm out of patience with this for today, and I'm throwing
it up for the cfbot to have a look at.  I'll hit the docs tomorrow.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7f4f79d1b5..6fab8714b5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -220,6 +220,11 @@
       <entry>access method operator families</entry>
      </row>

+     <row>
+      <entry><link linkend="catalog-pg-parameter-acl"><structname>pg_parameter_acl</structname></link></entry>
+      <entry>configuration parameters for which privileges have been granted</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
       <entry>information about partition key of tables</entry>
@@ -5450,6 +5455,73 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


+ <sect1 id="catalog-pg-parameter-acl">
+  <title><structname>pg_parameter_acl</structname></title>
+
+  <indexterm zone="catalog-pg-parameter-acl">
+   <primary>pg_parameter_acl</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_parameter_acl</structname> records configuration
+   parameters for which privileges have been granted to one or more roles.
+  </para>
+
+  <para>
+   Unlike most system catalogs, <structname>pg_parameter_acl</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_parameter_acl</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_parameter_acl</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>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>parname</structfield> <type>text</type>
+      </para>
+      <para>
+       The name of a configuration parameter for which privileges are granted
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>paracl</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-partitioned-table">
   <title><structname>pg_partitioned_table</structname></title>

@@ -12780,7 +12852,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
     <listitem>
      <para>
       These settings can be set from <filename>postgresql.conf</filename>,
-      or within a session via the <command>SET</command> command; but only superusers
+      or within a session via the <command>SET</command> command; but only
+      superusers and users with <literal>SET</literal> privilege granted
+      on the specific setting
       can change them via <command>SET</command>.  Changes in
       <filename>postgresql.conf</filename> will affect existing sessions
       only if no session-local value has been established with <command>SET</command>.
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 166b7a352d..84771ab6c6 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1691,7 +1691,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</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.
@@ -1959,6 +1960,26 @@ REVOKE ALL ON accounts FROM PUBLIC;
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET</literal></term>
+     <listitem>
+      <para>
+       Allows run-time configuration parameters to be set to a new value or
+       reset to the default value.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER SYSTEM</literal></term>
+     <listitem>
+      <para>
+       Allows server configuration parameters to be configured to a new value
+       or reset to the default configuration value.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>

    The privileges required by other commands are listed on the
@@ -1977,6 +1998,10 @@ REVOKE ALL ON accounts FROM PUBLIC;
    large objects,
    schemas,
    or tablespaces.
+   Some parameters are inherently settable by <literal>PUBLIC</literal>, as if
+   <literal>SET</literal> were granted, but this is not implemented as a grant,
+   nor can the ability for <literal>PUBLIC</literal> to set these parameters be
+   revoked.
    For other types of objects, the default privileges
    granted to <literal>PUBLIC</literal> are as follows:
    <literal>CONNECT</literal> and <literal>TEMPORARY</literal> (create
@@ -2097,6 +2122,16 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>TYPE</literal>
       </entry>
      </row>
+     <row>
+      <entry><literal>SET</literal></entry>
+      <entry><literal>s</literal></entry>
+      <entry><literal>PARAMETER</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ALTER SYSTEM</literal></entry>
+      <entry><literal>A</literal></entry>
+      <entry><literal>PARAMETER</literal></entry>
+     </row>
      </tbody>
    </tgroup>
   </table>
@@ -2167,6 +2202,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry>none</entry>
       <entry><literal>\dl+</literal></entry>
      </row>
+     <row>
+      <entry><literal>PARAMETER</literal></entry>
+      <entry><literal>sA</literal></entry>
+      <entry>none</entry>
+      <entry>none</entry>
+     </row>
      <row>
       <entry><literal>SCHEMA</literal></entry>
       <entry><literal>UC</literal></entry>
@@ -2274,6 +2315,14 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
    access privileges display.  A <literal>*</literal> will appear only when
    grant options have been explicitly granted to someone.
   </para>
+
+  <para>
+   No privileges are required for <literal>PUBLIC</literal> to
+   <command>SET</command> and <command>RESET</command> a
+   <literal>user</literal> parameter.  Parameters are not explicitly owned.
+   All parameters, including those added by extensions, implicitly belong to
+   the bootstrap superuser.
+  </para>
  </sect1>

  <sect1 id="ddl-rowsecurity">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4001cb2bda..22ea39b842 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22701,8 +22701,7 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
    privilege is held with grant option. Also, multiple privilege types can be
    listed separated by commas, in which case the result will be true if any of
    the listed privileges is held. (Case of the privilege string is not
-   significant, and extra whitespace is allowed between but not within
-   privilege names.)
+   significant, and extra whitespace is allowed between privilege names.)
    Some examples:
 <programlisting>
 SELECT has_table_privilege('myschema.mytable', 'select');
@@ -22836,6 +22835,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>

+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_parameter_privilege</primary>
+        </indexterm>
+        <function>has_parameter_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>parameter</parameter> <type>text</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for configuration parameter?
+        Allowable privilege types are <literal>SET</literal>
+        and <literal>ALTER SYSTEM</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/ref/alter_system.sgml b/doc/src/sgml/ref/alter_system.sgml
index 5e41f7f644..6f8bd39eaf 100644
--- a/doc/src/sgml/ref/alter_system.sgml
+++ b/doc/src/sgml/ref/alter_system.sgml
@@ -55,7 +55,8 @@ ALTER SYSTEM RESET ALL
   </para>

   <para>
-   Only superusers can use <command>ALTER SYSTEM</command>.  Also, since
+   Only superusers and users granted <literal>ALTER SYSTEM</literal> privilege
+   on a parameter can change it using <command>ALTER SYSTEM</command>.  Also, since
    this command acts directly on the file system and cannot be rolled back,
    it is not allowed inside a transaction block or function.
   </para>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 8c4edd9b0a..f86b2072f5 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 | ALTER SYSTEM } [, ... ] | ALL [ PRIVILEGES ] }
+    ON PARAMETER <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</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/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 3014c864ea..58455c519e 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -118,6 +118,13 @@ REVOKE [ GRANT OPTION FOR ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
     [ CASCADE | RESTRICT ]

+REVOKE [ GRANT OPTION FOR ]
+    { { SET | ALTER SYSTEM } [, ...] | ALL [ PRIVILEGES ] }
+    ON PARAMETER <replaceable class="parameter">configuration_parameter</replaceable> [, ...]
+    FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ ADMIN OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable
class="parameter">role_specification</replaceable>[, ...] 
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 339ee9eec9..465edcf4fd 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -34,10 +34,10 @@ 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.)
-   <command>SET</command> only affects the value used by the current
-   session.
+   (But some require either superuser privileges or granted
+   <literal>SET</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>

   <para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 87d7386e01..89a0221ec9 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,6 +38,7 @@ OBJS = \
     pg_largeobject.o \
     pg_namespace.o \
     pg_operator.o \
+    pg_parameter_acl.o \
     pg_proc.o \
     pg_publication.o \
     pg_range.o \
@@ -68,7 +69,8 @@ CATALOG_HEADERS := \
     pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
     pg_foreign_table.h pg_policy.h pg_replication_origin.h \
     pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
-    pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
+    pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \
+    pg_range.h pg_transform.h \
     pg_sequence.h pg_publication.h pg_publication_namespace.h \
     pg_publication_rel.h pg_subscription.h pg_subscription_rel.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd03a8e51..5f1726c095 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
@@ -112,11 +113,13 @@ 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_Parameter(InternalGrant *grantStmt);

 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);

-static List *objectNamesToOids(ObjectType objtype, List *objnames);
+static List *objectNamesToOids(ObjectType objtype, List *objnames,
+                               bool is_grant);
 static List *objectsInSchemaToOids(ObjectType objtype, List *nspnames);
 static List *getRelationsInNamespace(Oid namespaceId, char relkind);
 static void expand_col_privileges(List *colnames, Oid table_oid,
@@ -259,6 +262,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_PARAMETER_ACL:
+            whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            break;
         default:
             elog(ERROR, "unrecognized object type: %d", objtype);
             /* not reached, but keep compiler quiet */
@@ -390,7 +396,8 @@ ExecuteGrantStmt(GrantStmt *stmt)
     switch (stmt->targtype)
     {
         case ACL_TARGET_OBJECT:
-            istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects);
+            istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects,
+                                              stmt->is_grant);
             break;
         case ACL_TARGET_ALL_IN_SCHEMA:
             istmt.objects = objectsInSchemaToOids(stmt->objtype, stmt->objects);
@@ -498,6 +505,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
             all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
             errormsg = gettext_noop("invalid privilege type %s for foreign server");
             break;
+        case OBJECT_PARAMETER_ACL:
+            all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            errormsg = gettext_noop("invalid privilege type %s for parameter");
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) stmt->objtype);
@@ -600,6 +611,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
         case OBJECT_TABLESPACE:
             ExecGrant_Tablespace(istmt);
             break;
+        case OBJECT_PARAMETER_ACL:
+            ExecGrant_Parameter(istmt);
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) istmt->objtype);
@@ -626,7 +640,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
  * to fail.
  */
 static List *
-objectNamesToOids(ObjectType objtype, List *objnames)
+objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 {
     List       *objects = NIL;
     ListCell   *cell;
@@ -759,6 +773,37 @@ objectNamesToOids(ObjectType objtype, List *objnames)
                 objects = lappend_oid(objects, srvid);
             }
             break;
+        case OBJECT_PARAMETER_ACL:
+            foreach(cell, objnames)
+            {
+                /*
+                 * In this code we represent a GUC by the OID of its entry in
+                 * pg_parameter_acl, which we have to manufacture here if it
+                 * doesn't exist yet.  (That's a hack for sure, but it avoids
+                 * messing with all the GRANT/REVOKE infrastructure that
+                 * expects to use OIDs for object identities.)  However, if
+                 * this is a REVOKE, we can instead just ignore any GUCs that
+                 * don't have such an entry, as they must not have any
+                 * privileges needing removal.
+                 */
+                char       *parameter = strVal(lfirst(cell));
+                Oid            parameterId = ParameterAclLookup(parameter, true);
+
+                if (!OidIsValid(parameterId) && is_grant)
+                {
+                    parameterId = ParameterAclCreate(parameter);
+
+                    /*
+                     * Prevent error when processing duplicate objects, and
+                     * make this new entry visible so that ExecGrant_Parameter
+                     * can update it.
+                     */
+                    CommandCounterIncrement();
+                }
+                if (OidIsValid(parameterId))
+                    objects = lappend_oid(objects, parameterId);
+            }
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) objtype);
@@ -1494,6 +1539,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
             case ForeignDataWrapperRelationId:
                 istmt.objtype = OBJECT_FDW;
                 break;
+            case ParameterAclRelationId:
+                istmt.objtype = OBJECT_PARAMETER_ACL;
+                break;
             default:
                 elog(ERROR, "unexpected object class %u", classid);
                 break;
@@ -3225,6 +3273,154 @@ ExecGrant_Type(InternalGrant *istmt)
     table_close(relation, RowExclusiveLock);
 }

+static void
+ExecGrant_Parameter(InternalGrant *istmt)
+{
+    Relation    relation;
+    ListCell   *cell;
+
+    if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+        istmt->privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
+
+    relation = table_open(ParameterAclRelationId, RowExclusiveLock);
+
+    foreach(cell, istmt->objects)
+    {
+        Oid            parameterId = lfirst_oid(cell);
+        Datum        nameDatum;
+        const char *parname;
+        Datum        aclDatum;
+        bool        isNull;
+        AclMode        avail_goptions;
+        AclMode        this_privileges;
+        Acl           *old_acl;
+        Acl           *new_acl;
+        Oid            grantorId;
+        Oid            ownerId;
+        HeapTuple    tuple;
+        int            noldmembers;
+        int            nnewmembers;
+        Oid           *oldmembers;
+        Oid           *newmembers;
+
+        tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(parameterId));
+        if (!HeapTupleIsValid(tuple))
+            elog(ERROR, "cache lookup failed for parameter ACL %u",
+                 parameterId);
+
+        /* We'll need the GUC's name */
+        nameDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                                    Anum_pg_parameter_acl_parname,
+                                    &isNull);
+        Assert(!isNull);
+        parname = TextDatumGetCString(nameDatum);
+
+        /* Treat all parameters as belonging to the bootstrap superuser. */
+        ownerId = BOOTSTRAP_SUPERUSERID;
+
+        /*
+         * Get working copy of existing ACL. If there's no ACL, substitute the
+         * proper default.
+         */
+        aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                                   Anum_pg_parameter_acl_paracl,
+                                   &isNull);
+
+        if (isNull)
+        {
+            old_acl = acldefault(istmt->objtype, ownerId);
+            /* 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, ownerId,
+                            &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,
+                                     parameterId, grantorId,
+                                     OBJECT_PARAMETER_ACL,
+                                     parname,
+                                     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, ownerId);
+
+        /*
+         * We need the members of both old and new ACLs so we can correct the
+         * shared dependency information.
+         */
+        nnewmembers = aclmembers(new_acl, &newmembers);
+
+        /*
+         * If the new ACL is equal to the default, we don't need the catalog
+         * entry any longer.  Delete it rather than updating it, to avoid
+         * leaving a degenerate entry.
+         */
+        if (aclequal(new_acl, acldefault(istmt->objtype, ownerId)))
+        {
+            CatalogTupleDelete(relation, &tuple->t_self);
+        }
+        else
+        {
+            /* finished building new ACL value, now insert it */
+            HeapTuple    newtuple;
+            Datum        values[Natts_pg_parameter_acl];
+            bool        nulls[Natts_pg_parameter_acl];
+            bool        replaces[Natts_pg_parameter_acl];
+
+            MemSet(values, 0, sizeof(values));
+            MemSet(nulls, false, sizeof(nulls));
+            MemSet(replaces, false, sizeof(replaces));
+
+            replaces[Anum_pg_parameter_acl_paracl - 1] = true;
+            values[Anum_pg_parameter_acl_paracl - 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(parameterId, ParameterAclRelationId, 0,
+                                new_acl);
+
+        /* Update the shared dependency ACL info */
+        updateAclDependencies(ParameterAclRelationId, parameterId, 0,
+                              ownerId,
+                              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 +3451,10 @@ string_to_privilege(const char *privname)
         return ACL_CREATE_TEMP;
     if (strcmp(privname, "connect") == 0)
         return ACL_CONNECT;
+    if (strcmp(privname, "set") == 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 +3492,10 @@ privilege_to_string(AclMode privilege)
             return "TEMP";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET:
+            return "SET";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized privilege: %d", (int) privilege);
     }
@@ -3376,6 +3580,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_OPFAMILY:
                         msg = gettext_noop("permission denied for operator family %s");
                         break;
+                    case OBJECT_PARAMETER_ACL:
+                        msg = gettext_noop("permission denied for parameter %s");
+                        break;
                     case OBJECT_POLICY:
                         msg = gettext_noop("permission denied for policy %s");
                         break;
@@ -3567,6 +3774,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_DEFAULT:
                     case OBJECT_DEFACL:
                     case OBJECT_DOMCONSTRAINT:
+                    case OBJECT_PARAMETER_ACL:
                     case OBJECT_PUBLICATION_NAMESPACE:
                     case OBJECT_PUBLICATION_REL:
                     case OBJECT_ROLE:
@@ -3653,6 +3861,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
         case OBJECT_LARGEOBJECT:
             return pg_largeobject_aclmask_snapshot(table_oid, roleid,
                                                    mask, how, NULL);
+        case OBJECT_PARAMETER_ACL:
+            return pg_parameter_acl_aclmask(table_oid, roleid, mask, how);
         case OBJECT_SCHEMA:
             return pg_namespace_aclmask(table_oid, roleid, mask, how);
         case OBJECT_STATISTIC_EXT:
@@ -4000,6 +4210,121 @@ pg_database_aclmask(Oid db_oid, Oid roleid,
     return result;
 }

+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC), identified by GUC name.
+ */
+AclMode
+pg_parameter_aclmask(const char *name, Oid roleid, AclMode mask, AclMaskHow how)
+{
+    AclMode        result;
+    char       *parname;
+    text       *partext;
+    HeapTuple    tuple;
+
+    /* Superusers bypass all permission checking. */
+    if (superuser_arg(roleid))
+        return mask;
+
+    /* Convert name to the form it should have in pg_parameter_acl... */
+    parname = convert_GUC_name_for_parameter_acl(name);
+    partext = cstring_to_text(parname);
+
+    /* ... and look it up */
+    tuple = SearchSysCache1(PARAMETERACLNAME, PointerGetDatum(partext));
+
+    if (!HeapTupleIsValid(tuple))
+    {
+        /* If no entry, GUC has no permissions for non-superusers */
+        result = ACL_NO_RIGHTS;
+    }
+    else
+    {
+        Datum        aclDatum;
+        bool        isNull;
+        Acl           *acl;
+
+        aclDatum = SysCacheGetAttr(PARAMETERACLNAME, tuple,
+                                   Anum_pg_parameter_acl_paracl,
+                                   &isNull);
+        if (isNull)
+        {
+            /* No ACL, so build default ACL */
+            acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID);
+            aclDatum = (Datum) 0;
+        }
+        else
+        {
+            /* detoast ACL if necessary */
+            acl = DatumGetAclP(aclDatum);
+        }
+
+        result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, mask, how);
+
+        /* if we have a detoasted copy, free it */
+        if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+            pfree(acl);
+
+        ReleaseSysCache(tuple);
+    }
+
+    pfree(parname);
+    pfree(partext);
+
+    return result;
+}
+
+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC), identified by the OID of its pg_parameter_acl entry.
+ */
+AclMode
+pg_parameter_acl_aclmask(Oid acl_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 ACL from pg_parameter_acl */
+    tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(acl_oid));
+    if (!HeapTupleIsValid(tuple))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("parameter ACL with OID %u does not exist",
+                        acl_oid)));
+
+    aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                               Anum_pg_parameter_acl_paracl,
+                               &isNull);
+    if (isNull)
+    {
+        /* No ACL, so build default ACL */
+        acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID);
+        aclDatum = (Datum) 0;
+    }
+    else
+    {
+        /* detoast ACL if necessary */
+        acl = DatumGetAclP(aclDatum);
+    }
+
+    result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, 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 +5038,32 @@ 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 (GUC), identified by GUC name.
+ */
+AclResult
+pg_parameter_aclcheck(const char *name, Oid roleid, AclMode mode)
+{
+    if (pg_parameter_aclmask(name, roleid, mode, ACLMASK_ANY) != 0)
+        return ACLCHECK_OK;
+    else
+        return ACLCHECK_NO_PRIV;
+}
+
+/*
+ * Exported routine for checking a user's access privileges to a configuration
+ * parameter (GUC), identified by the OID of its pg_parameter_acl entry.
+ */
+AclResult
+pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, AclMode mode)
+{
+    if (pg_parameter_acl_aclmask(acl_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 dfd5fb669e..520f77971b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -247,32 +248,35 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == SharedDescriptionRelationId ||
-        relationId == SharedDependRelationId ||
-        relationId == SharedSecLabelRelationId ||
-        relationId == TableSpaceRelationId ||
         relationId == DbRoleSettingRelationId ||
+        relationId == ParameterAclRelationId ||
         relationId == ReplicationOriginRelationId ||
-        relationId == SubscriptionRelationId)
+        relationId == SharedDependRelationId ||
+        relationId == SharedDescriptionRelationId ||
+        relationId == SharedSecLabelRelationId ||
+        relationId == SubscriptionRelationId ||
+        relationId == TableSpaceRelationId)
         return true;
     /* These are their indexes */
-    if (relationId == AuthIdRolnameIndexId ||
-        relationId == AuthIdOidIndexId ||
-        relationId == AuthMemRoleMemIndexId ||
+    if (relationId == AuthIdOidIndexId ||
+        relationId == AuthIdRolnameIndexId ||
         relationId == AuthMemMemRoleIndexId ||
+        relationId == AuthMemRoleMemIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == SharedDescriptionObjIndexId ||
-        relationId == SharedDependDependerIndexId ||
-        relationId == SharedDependReferenceIndexId ||
-        relationId == SharedSecLabelObjectIndexId ||
-        relationId == TablespaceOidIndexId ||
-        relationId == TablespaceNameIndexId ||
         relationId == DbRoleSettingDatidRolidIndexId ||
+        relationId == ParameterAclOidIndexId ||
+        relationId == ParameterAclParnameIndexId ||
         relationId == ReplicationOriginIdentIndex ||
         relationId == ReplicationOriginNameIndex ||
+        relationId == SharedDependDependerIndexId ||
+        relationId == SharedDependReferenceIndexId ||
+        relationId == SharedDescriptionObjIndexId ||
+        relationId == SharedSecLabelObjectIndexId ||
+        relationId == SubscriptionNameIndexId ||
         relationId == SubscriptionObjectIndexId ||
-        relationId == SubscriptionNameIndexId)
+        relationId == TablespaceNameIndexId ||
+        relationId == TablespaceOidIndexId)
         return true;
     /* These are their toast tables and toast indexes */
     if (relationId == PgAuthidToastTable ||
@@ -281,6 +285,8 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
+        relationId == PgParameterAclToastTable ||
+        relationId == PgParameterAclToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 25fe56d310..de10923391 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
     DefaultAclRelationId,        /* OCLASS_DEFACL */
     ExtensionRelationId,        /* OCLASS_EXTENSION */
     EventTriggerRelationId,        /* OCLASS_EVENT_TRIGGER */
+    ParameterAclRelationId,        /* OCLASS_PARAMETER_ACL */
     PolicyRelationId,            /* OCLASS_POLICY */
     PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
     PublicationRelationId,        /* OCLASS_PUBLICATION */
@@ -1507,6 +1509,7 @@ doDeletion(const ObjectAddress *object, int flags)
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
         case OCLASS_SUBSCRIPTION:
+        case OCLASS_PARAMETER_ACL:
             elog(ERROR, "global objects cannot be deleted by doDeletion");
             break;

@@ -2861,6 +2864,9 @@ getObjectClass(const ObjectAddress *object)
         case EventTriggerRelationId:
             return OCLASS_EVENT_TRIGGER;

+        case ParameterAclRelationId:
+            return OCLASS_PARAMETER_ACL;
+
         case PolicyRelationId:
             return OCLASS_POLICY;

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 3fd17ea64f..31c80f7209 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -818,6 +819,10 @@ static const struct object_type_map
     {
         "event trigger", OBJECT_EVENT_TRIGGER
     },
+    /* OCLASS_PARAMETER_ACL */
+    {
+        "parameter ACL", OBJECT_PARAMETER_ACL
+    },
     /* OCLASS_POLICY */
     {
         "policy", OBJECT_POLICY
@@ -1014,6 +1019,7 @@ get_object_address(ObjectType objtype, Node *object,
             case OBJECT_FDW:
             case OBJECT_FOREIGN_SERVER:
             case OBJECT_EVENT_TRIGGER:
+            case OBJECT_PARAMETER_ACL:
             case OBJECT_ACCESS_METHOD:
             case OBJECT_PUBLICATION:
             case OBJECT_SUBSCRIPTION:
@@ -1315,6 +1321,11 @@ get_object_address_unqualified(ObjectType objtype,
             address.objectId = get_event_trigger_oid(name, missing_ok);
             address.objectSubId = 0;
             break;
+        case OBJECT_PARAMETER_ACL:
+            address.classId = ParameterAclRelationId;
+            address.objectId = ParameterAclLookup(name, missing_ok);
+            address.objectSubId = 0;
+            break;
         case OBJECT_PUBLICATION:
             address.classId = PublicationRelationId;
             address.objectId = get_publication_oid(name, missing_ok);
@@ -2307,6 +2318,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
         case OBJECT_FDW:
         case OBJECT_FOREIGN_SERVER:
         case OBJECT_LANGUAGE:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_PUBLICATION:
         case OBJECT_ROLE:
         case OBJECT_SCHEMA:
@@ -2597,6 +2609,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
         case OBJECT_TSPARSER:
         case OBJECT_TSTEMPLATE:
         case OBJECT_ACCESS_METHOD:
+        case OBJECT_PARAMETER_ACL:
             /* We treat these object types as being owned by superusers */
             if (!superuser_arg(roleid))
                 ereport(ERROR,
@@ -3880,6 +3893,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
                 break;
             }

+        case OCLASS_PARAMETER_ACL:
+            {
+                HeapTuple    tup;
+                Datum        nameDatum;
+                bool        isNull;
+                char       *parname;
+
+                tup = SearchSysCache1(PARAMETERACLOID,
+                                      ObjectIdGetDatum(object->objectId));
+                if (!HeapTupleIsValid(tup))
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for parameter ACL %u",
+                             object->objectId);
+                    break;
+                }
+                nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup,
+                                            Anum_pg_parameter_acl_parname,
+                                            &isNull);
+                Assert(!isNull);
+                parname = TextDatumGetCString(nameDatum);
+                appendStringInfo(&buffer, _("parameter %s"), parname);
+                ReleaseSysCache(tup);
+                break;
+            }
+
         case OCLASS_POLICY:
             {
                 Relation    policy_rel;
@@ -4547,6 +4586,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
             appendStringInfoString(&buffer, "event trigger");
             break;

+        case OCLASS_PARAMETER_ACL:
+            appendStringInfoString(&buffer, "parameter ACL");
+            break;
+
         case OCLASS_POLICY:
             appendStringInfoString(&buffer, "policy");
             break;
@@ -5693,6 +5736,34 @@ getObjectIdentityParts(const ObjectAddress *object,
                 break;
             }

+        case OCLASS_PARAMETER_ACL:
+            {
+                HeapTuple    tup;
+                Datum        nameDatum;
+                bool        isNull;
+                char       *parname;
+
+                tup = SearchSysCache1(PARAMETERACLOID,
+                                      ObjectIdGetDatum(object->objectId));
+                if (!HeapTupleIsValid(tup))
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for parameter ACL %u",
+                             object->objectId);
+                    break;
+                }
+                nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup,
+                                            Anum_pg_parameter_acl_parname,
+                                            &isNull);
+                Assert(!isNull);
+                parname = TextDatumGetCString(nameDatum);
+                appendStringInfoString(&buffer, parname);
+                if (objname)
+                    *objname = list_make1(parname);
+                ReleaseSysCache(tup);
+                break;
+            }
+
         case OCLASS_POLICY:
             {
                 Relation    polDesc;
diff --git a/src/backend/catalog/pg_parameter_acl.c b/src/backend/catalog/pg_parameter_acl.c
new file mode 100644
index 0000000000..2decee909b
--- /dev/null
+++ b/src/backend/catalog/pg_parameter_acl.c
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_parameter_acl.c
+ *      routines to support manipulation of the pg_parameter_acl relation
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *      src/backend/catalog/pg_parameter_acl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_parameter_acl.h"
+#include "utils/builtins.h"
+#include "utils/pg_locale.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * ParameterAclLookup - Given a configuration parameter name,
+ * look up the associated configuration parameter ACL's OID.
+ *
+ * If missing_ok is false, throw an error if ACL entry not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+ParameterAclLookup(const char *parameter, bool missing_ok)
+{
+    Oid            oid;
+    char       *parname;
+
+    /* Convert name to the form it should have in pg_parameter_acl... */
+    parname = convert_GUC_name_for_parameter_acl(parameter);
+
+    /* ... and look it up */
+    oid = GetSysCacheOid1(PARAMETERACLNAME, Anum_pg_parameter_acl_oid,
+                          PointerGetDatum(cstring_to_text(parname)));
+
+    if (!OidIsValid(oid) && !missing_ok)
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("parameter ACL \"%s\" does not exist", parameter)));
+
+    pfree(parname);
+
+    return oid;
+}
+
+/*
+ * ParameterAclCreate
+ *
+ * Add a new tuple to pg_parameter_acl.
+ *
+ * parameter: the parameter name to create an entry for.
+ * Caller should have verified that there's no such entry already.
+ *
+ * Returns the new entry's OID.
+ */
+Oid
+ParameterAclCreate(const char *parameter)
+{
+    Oid            parameterId;
+    char       *parname;
+    Relation    rel;
+    TupleDesc    tupDesc;
+    HeapTuple    tuple;
+    Datum        values[Natts_pg_parameter_acl];
+    bool        nulls[Natts_pg_parameter_acl];
+
+    /*
+     * To prevent cluttering pg_parameter_acl with useless entries, insist
+     * that the name be valid.
+     */
+    if (!check_GUC_name_for_parameter_acl(parameter))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_NAME),
+                 errmsg("invalid parameter name \"%s\"",
+                        parameter)));
+
+    /* Convert name to the form it should have in pg_parameter_acl. */
+    parname = convert_GUC_name_for_parameter_acl(parameter);
+
+    /*
+     * Create and insert a new record containing a null ACL.
+     *
+     * We don't take a strong enough lock to prevent concurrent insertions,
+     * relying instead on the unique index.
+     */
+    rel = table_open(ParameterAclRelationId, RowExclusiveLock);
+    tupDesc = RelationGetDescr(rel);
+    MemSet(values, 0, sizeof(values));
+    MemSet(nulls, false, sizeof(nulls));
+    parameterId = GetNewOidWithIndex(rel,
+                                     ParameterAclOidIndexId,
+                                     Anum_pg_parameter_acl_oid);
+    values[Anum_pg_parameter_acl_oid - 1] = ObjectIdGetDatum(parameterId);
+    values[Anum_pg_parameter_acl_parname - 1] =
+        PointerGetDatum(cstring_to_text(parname));
+    nulls[Anum_pg_parameter_acl_paracl - 1] = true;
+    tuple = heap_form_tuple(tupDesc, values, nulls);
+    CatalogTupleInsert(rel, tuple);
+
+    /* Close pg_parameter_acl, but keep lock till commit. */
+    heap_freetuple(tuple);
+    table_close(rel, NoLock);
+
+    return parameterId;
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1f64c8aa51..5456b8222b 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -658,6 +658,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
         case OCLASS_DEFACL:
         case OCLASS_EXTENSION:
         case OCLASS_EVENT_TRIGGER:
+        case OCLASS_PARAMETER_ACL:
         case OCLASS_POLICY:
         case OCLASS_PUBLICATION:
         case OCLASS_PUBLICATION_NAMESPACE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 3c3fc2515b..4642527881 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -940,6 +940,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
         case OBJECT_DATABASE:
         case OBJECT_TABLESPACE:
         case OBJECT_ROLE:
+        case OBJECT_PARAMETER_ACL:
             /* no support for global objects */
             return false;
         case OBJECT_EVENT_TRIGGER:
@@ -1015,6 +1016,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
         case OCLASS_ROLE:
+        case OCLASS_PARAMETER_ACL:
             /* no support for global objects */
             return false;
         case OCLASS_EVENT_TRIGGER:
@@ -2042,6 +2044,8 @@ stringify_grant_objtype(ObjectType objtype)
             return "LARGE OBJECT";
         case OBJECT_SCHEMA:
             return "SCHEMA";
+        case OBJECT_PARAMETER_ACL:
+            return "PARAMETER";
         case OBJECT_PROCEDURE:
             return "PROCEDURE";
         case OBJECT_ROUTINE:
@@ -2153,6 +2157,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
         case OBJECT_OPCLASS:
         case OBJECT_OPERATOR:
         case OBJECT_OPFAMILY:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_POLICY:
         case OBJECT_PUBLICATION:
         case OBJECT_PUBLICATION_NAMESPACE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7a62d547e2..7ae19b98bb 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -78,6 +78,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
         case OBJECT_OPCLASS:
         case OBJECT_OPERATOR:
         case OBJECT_OPFAMILY:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_POLICY:
         case OBJECT_PUBLICATION_NAMESPACE:
         case OBJECT_PUBLICATION_REL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7febb5018f..a241b44497 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12655,6 +12655,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
             case OCLASS_DEFACL:
             case OCLASS_EXTENSION:
             case OCLASS_EVENT_TRIGGER:
+            case OCLASS_PARAMETER_ACL:
             case OCLASS_PUBLICATION:
             case OCLASS_PUBLICATION_NAMESPACE:
             case OCLASS_PUBLICATION_REL:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e5a3c528aa..af243dc2b9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -371,8 +371,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 parameter_name
+%type <list>    OptSchemaEltList parameter_name_list

 %type <chr>        am_type

@@ -799,8 +799,8 @@ 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
-    POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+    PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS
+    POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
     PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION

     QUOTE QUOTES
@@ -7165,6 +7165,13 @@ privilege:    SELECT opt_column_list
                 n->cols = $2;
                 $$ = n;
             }
+        | ALTER SYSTEM_P
+            {
+                AccessPriv *n = makeNode(AccessPriv);
+                n->priv_name = pstrdup("alter system");
+                n->cols = NIL;
+                $$ = n;
+            }
         | ColId opt_column_list
             {
                 AccessPriv *n = makeNode(AccessPriv);
@@ -7174,6 +7181,28 @@ privilege:    SELECT opt_column_list
             }
         ;

+parameter_name_list:
+        parameter_name
+            {
+                $$ = list_make1(makeString($1));
+            }
+        | parameter_name_list ',' parameter_name
+            {
+                $$ = lappend($1, makeString($3));
+            }
+        ;
+
+parameter_name:
+        ColId
+            {
+                $$ = $1;
+            }
+        | parameter_name '.' ColId
+            {
+                $$ = psprintf("%s.%s", $1, $3);
+            }
+        ;
+

 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
@@ -7275,6 +7304,14 @@ privilege_target:
                     n->objs = $3;
                     $$ = n;
                 }
+            | PARAMETER parameter_name_list
+                {
+                    PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+                    n->targtype = ACL_TARGET_OBJECT;
+                    n->objtype = OBJECT_PARAMETER_ACL;
+                    n->objs = $2;
+                    $$ = n;
+                }
             | SCHEMA name_list
                 {
                     PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -16761,6 +16798,7 @@ unreserved_keyword:
             | OWNED
             | OWNER
             | PARALLEL
+            | PARAMETER
             | PARSER
             | PARTIAL
             | PARTITION
@@ -17373,6 +17411,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 83cf7ac9ff..9263fa2901 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/proclang.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"
@@ -109,6 +111,7 @@ static Oid    convert_tablespace_name(text *tablespacename);
 static AclMode convert_tablespace_priv_string(text *priv_type_text);
 static Oid    convert_type_name(text *typename);
 static AclMode convert_type_priv_string(text *priv_type_text);
+static AclMode convert_parameter_priv_string(text *priv_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);

@@ -306,6 +309,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 +803,10 @@ acldefault(ObjectType objtype, Oid ownerId)
             world_default = ACL_USAGE;
             owner_default = ACL_ALL_RIGHTS_TYPE;
             break;
+        case OBJECT_PARAMETER_ACL:
+            world_default = ACL_NO_RIGHTS;
+            owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            break;
         default:
             elog(ERROR, "unrecognized objtype: %d", (int) objtype);
             world_default = ACL_NO_RIGHTS;    /* keep compiler quiet */
@@ -873,6 +886,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
         case 'L':
             objtype = OBJECT_LARGEOBJECT;
             break;
+        case 'p':
+            objtype = OBJECT_PARAMETER_ACL;
+            break;
         case 'n':
             objtype = OBJECT_SCHEMA;
             break;
@@ -1602,6 +1618,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") == 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 +1718,10 @@ convert_aclright_to_string(int aclright)
             return "TEMPORARY";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET:
+            return "SET";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized aclright: %d", aclright);
             return NULL;
@@ -4429,6 +4453,96 @@ convert_type_priv_string(text *priv_type_text)
     return convert_any_priv_string(priv_type_text, type_priv_map);
 }

+/*
+ * has_parameter_privilege variants
+ *        These are all named "has_parameter_privilege" at the SQL level.
+ *        They take various combinations of parameter name with
+ *        user name, user OID, or implicit user = current_user.
+ *
+ *        The result is a boolean value: true if user has been granted
+ *        the indicated privilege or false if not.
+ */
+
+/*
+ * has_param_priv_byname
+ *
+ *        Helper function to check user privileges on a parameter given the
+ *        role by Oid, parameter by text name, and privileges as AclMode.
+ */
+static bool
+has_param_priv_byname(Oid roleid, const text *parameter, AclMode priv)
+{
+    char       *paramstr = text_to_cstring(parameter);
+
+    return pg_parameter_aclcheck(paramstr, roleid, priv) == ACLCHECK_OK;
+}
+
+/*
+ * has_parameter_privilege_name_name
+ *        Check user privileges on a parameter given name username, text
+ *        parameter, and text priv name.
+ */
+Datum
+has_parameter_privilege_name_name(PG_FUNCTION_ARGS)
+{
+    Name        username = PG_GETARG_NAME(0);
+    text       *parameter = PG_GETARG_TEXT_PP(1);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+    Oid            roleid = get_role_oid_or_public(NameStr(*username));
+
+    PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_name
+ *        Check user privileges on a parameter given text parameter and text priv
+ *        name.  current_user is assumed
+ */
+Datum
+has_parameter_privilege_name(PG_FUNCTION_ARGS)
+{
+    text       *parameter = PG_GETARG_TEXT_PP(0);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(1));
+
+    PG_RETURN_BOOL(has_param_priv_byname(GetUserId(), parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_id_name
+ *        Check user privileges on a parameter given roleid, text parameter, and
+ *        text priv name.
+ */
+Datum
+has_parameter_privilege_id_name(PG_FUNCTION_ARGS)
+{
+    Oid            roleid = PG_GETARG_OID(0);
+    text       *parameter = PG_GETARG_TEXT_PP(1);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+
+    PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ *        Support routines for has_parameter_privilege family.
+ */
+
+/*
+ * convert_parameter_priv_string
+ *        Convert text string to AclMode value.
+ */
+static AclMode
+convert_parameter_priv_string(text *priv_text)
+{
+    static const priv_map parameter_priv_map[] = {
+        {"SET", ACL_SET},
+        {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)},
+        {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+        {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
+        {NULL, 0}
+    };
+
+    return convert_any_priv_string(priv_text, parameter_priv_map);
+}

 /*
  * pg_has_role variants
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a675877d19..8d265f2d23 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -574,6 +575,28 @@ static const struct cachedesc cacheinfo[] = {
         },
         8
     },
+    {ParameterAclRelationId,    /* PARAMETERACLNAME */
+        ParameterAclParnameIndexId,
+        1,
+        {
+            Anum_pg_parameter_acl_parname,
+            0,
+            0,
+            0
+        },
+        4
+    },
+    {ParameterAclRelationId,    /* PARAMETERACLOID */
+        ParameterAclOidIndexId,
+        1,
+        {
+            Anum_pg_parameter_acl_oid,
+            0,
+            0,
+            0
+        },
+        4
+    },
     {PartitionedRelationId,        /* PARTRELID */
         PartitionedRelidIndexId,
         1,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9e8ab1420d..265008bdc6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -45,6 +45,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
@@ -5713,6 +5714,65 @@ guc_name_compare(const char *namea, const char *nameb)
 }


+/*
+ * Convert a GUC name to the form that should be used in pg_parameter_acl.
+ *
+ * We need to canonicalize entries since, for example, case should not be
+ * significant.  In addition, we apply the map_old_guc_names[] mapping so that
+ * any obsolete names will be converted when stored in a new PG version.
+ * Note however that this function does not verify legality of the name.
+ *
+ * The result is a palloc'd string.
+ */
+char *
+convert_GUC_name_for_parameter_acl(const char *name)
+{
+    char       *result;
+
+    /* Apply old-GUC-name mapping. */
+    for (int i = 0; map_old_guc_names[i] != NULL; i += 2)
+    {
+        if (guc_name_compare(name, map_old_guc_names[i]) == 0)
+        {
+            name = map_old_guc_names[i + 1];
+            break;
+        }
+    }
+
+    /* Apply case-folding that matches guc_name_compare(). */
+    result = pstrdup(name);
+    for (char *ptr = result; *ptr != '\0'; ptr++)
+    {
+        char        ch = *ptr;
+
+        if (ch >= 'A' && ch <= 'Z')
+        {
+            ch += 'a' - 'A';
+            *ptr = ch;
+        }
+    }
+
+    return result;
+}
+
+/*
+ * Check whether we should allow creation of a pg_parameter_acl entry
+ * for the given name.  (This can be applied either before or after
+ * canonicalizing it.)
+ */
+bool
+check_GUC_name_for_parameter_acl(const char *name)
+{
+    /* OK if the GUC exists. */
+    if (find_option(name, false, true, DEBUG1) != NULL)
+        return true;
+    /* Otherwise, it'd better be a valid custom GUC name. */
+    if (valid_custom_variable_name(name))
+        return true;
+    return false;
+}
+
+
 /*
  * Initialize GUC options during program startup.
  *
@@ -7568,6 +7628,17 @@ set_config_option(const char *name, const char *value,
         case PGC_SUSET:
             if (context == PGC_USERSET || context == PGC_BACKEND)
             {
+                /*
+                 * Check whether the current user has been granted privilege
+                 * to set this GUC.
+                 */
+                AclResult    aclresult;
+
+                aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
+                if (aclresult == ACLCHECK_OK)
+                    break;        /* okay */
+
+                /* No granted privilege */
                 ereport(elevel,
                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                          errmsg("permission denied to set parameter \"%s\"",
@@ -8617,11 +8688,6 @@ 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
      */
@@ -8649,6 +8715,29 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
             break;
     }

+    /*
+     * Check permission to run ALTER SYSTEM on the target variable
+     */
+    if (!superuser())
+    {
+        if (resetall)
+            ereport(ERROR,
+                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                     errmsg("permission denied to perform ALTER SYSTEM RESET ALL")));
+        else
+        {
+            AclResult    aclresult;
+
+            aclresult = pg_parameter_aclcheck(name, GetUserId(),
+                                              ACL_ALTER_SYSTEM);
+            if (aclresult != ACLCHECK_OK)
+                ereport(ERROR,
+                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                         errmsg("permission denied to set parameter \"%s\"",
+                                name)));
+        }
+    }
+
     /*
      * Unless it's RESET_ALL, validate the target variable and value
      */
@@ -8760,13 +8849,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
     }

     /*
-     * Invoke the post-alter hook for altering this GUC variable.
+     * Invoke the post-alter hook for setting this GUC variable.  GUCs
+     * typically do not have corresponding entries in pg_parameter_acl, so we
+     * call the hook using the name rather than a potentially-non-existent
+     * OID.  Nonetheless, we pass ParameterAclRelationId so that this call
+     * context can be distinguished from others.  (Note that "name" will be
+     * NULL in the RESET ALL case.)
      *
      * We do this here rather than at the end, because ALTER SYSTEM is not
      * transactional.  If the hook aborts our transaction, it will be cleaner
      * to do so before we touch any files.
      */
-    InvokeObjectPostAlterHookArgStr(InvalidOid, name,
+    InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name,
                                     ACL_ALTER_SYSTEM,
                                     altersysstmt->setstmt->kind,
                                     false);
@@ -8943,9 +9037,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
             break;
     }

-    /* Invoke the post-alter hook for setting this GUC variable. */
-    InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
-                                    ACL_SET_VALUE, stmt->kind, false);
+    /* Invoke the post-alter hook for setting this GUC variable, by name. */
+    InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name,
+                                    ACL_SET, stmt->kind, false);
 }

 /*
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6086d57cf3..3e68dfc78f 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, PARAMETER or LARGE OBJECT)
  *    acls: the ACL string fetched from the database
  *    baseacls: the initial ACL string for this object
  *    owner: username of object owner (will be passed through fmtId); can be
@@ -501,6 +501,11 @@ do { \
         CONVERT_PRIV('U', "USAGE");
     else if (strcmp(type, "FOREIGN TABLE") == 0)
         CONVERT_PRIV('r', "SELECT");
+    else if (strcmp(type, "PARAMETER") == 0)
+    {
+        CONVERT_PRIV('s', "SET");
+        CONVERT_PRIV('A', "ALTER SYSTEM");
+    }
     else if (strcmp(type, "LARGE OBJECT") == 0)
     {
         CONVERT_PRIV('r', "SELECT");
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..2dc3362763 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -18,6 +18,7 @@
 #include <time.h>
 #include <unistd.h>

+#include "catalog/pg_authid_d.h"
 #include "common/connect.h"
 #include "common/file_utils.h"
 #include "common/logging.h"
@@ -36,6 +37,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 dropTablespaces(PGconn *conn);
 static void dumpTablespaces(PGconn *conn);
 static void dropDBs(PGconn *conn);
@@ -585,6 +587,10 @@ main(int argc, char *argv[])

             /* Dump role memberships */
             dumpRoleMembership(conn);
+
+            /* Dump role GUC privileges */
+            if (server_version >= 150000 && !skip_acls)
+                dumpRoleGUCPrivs(conn);
         }

         /* Dump tablespaces */
@@ -989,6 +995,65 @@ dumpRoleMembership(PGconn *conn)
 }


+/*
+ * 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)
+{
+    PGresult   *res;
+    int            i;
+
+    /*
+     * Get all parameters that have non-default acls defined.
+     */
+    res = executeQuery(conn, "SELECT parname, "
+                       "pg_catalog.pg_get_userbyid(" CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS parowner, "
+                       "paracl, "
+                       "pg_catalog.acldefault('p', " CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS acldefault "
+                       "FROM pg_catalog.pg_parameter_acl "
+                       "ORDER BY 1");
+
+    if (PQntuples(res) > 0)
+        fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n");
+
+    for (i = 0; i < PQntuples(res); i++)
+    {
+        PQExpBuffer buf = createPQExpBuffer();
+        char       *parname = PQgetvalue(res, i, 0);
+        char       *parowner = PQgetvalue(res, i, 1);
+        char       *paracl = PQgetvalue(res, i, 2);
+        char       *acldefault = PQgetvalue(res, i, 3);
+        char       *fparname;
+
+        /* needed for buildACLCommands() */
+        fparname = pg_strdup(fmtId(parname));
+
+        if (!buildACLCommands(fparname, NULL, NULL, "PARAMETER",
+                              paracl, acldefault,
+                              parowner, "", server_version, buf))
+        {
+            pg_log_error("could not parse ACL list (%s) for parameter \"%s\"",
+                         paracl, parname);
+            PQfinish(conn);
+            exit_nicely(1);
+        }
+
+        fprintf(OPF, "%s", buf->data);
+
+        free(fparname);
+        destroyPQExpBuffer(buf);
+    }
+
+    PQclear(res);
+    fprintf(OPF, "\n\n");
+}
+
+
 /*
  * Drop tablespaces.
  */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6f040674a2..39e091b1fb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3737,7 +3737,8 @@ psql_completion(const char *text, int start, int end)
  * ALTER DEFAULT PRIVILEGES, so use TailMatches
  */
     /* Complete GRANT/REVOKE with a list of roles and privileges */
-    else if (TailMatches("GRANT|REVOKE"))
+    else if (TailMatches("GRANT|REVOKE") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR"))
     {
         /*
          * With ALTER DEFAULT PRIVILEGES, restrict completion to grantable
@@ -3749,6 +3750,7 @@ psql_completion(const char *text, int start, int end)
                           "EXECUTE", "USAGE", "ALL");
         else
             COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+                                     "GRANT",
                                      "SELECT",
                                      "INSERT",
                                      "UPDATE",
@@ -3761,14 +3763,48 @@ psql_completion(const char *text, int start, int end)
                                      "TEMPORARY",
                                      "EXECUTE",
                                      "USAGE",
+                                     "SET",
+                                     "ALTER SYSTEM",
                                      "ALL");
     }

+    else if (TailMatches("REVOKE", "GRANT"))
+        COMPLETE_WITH("OPTION FOR");
+    else if (TailMatches("REVOKE", "GRANT", "OPTION"))
+        COMPLETE_WITH("FOR");
+
+    else if (TailMatches("GRANT|REVOKE", "ALTER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER"))
+        COMPLETE_WITH("SYSTEM");
+
+    else if (TailMatches("GRANT|REVOKE", "SET") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "SET") ||
+             TailMatches("GRANT|REVOKE", "ALTER", "SYSTEM") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER", "SYSTEM"))
+        COMPLETE_WITH("ON PARAMETER");
+
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "PARAMETER") ||
+             TailMatches("GRANT|REVOKE", MatchAny, MatchAny, "ON", "PARAMETER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER"))
+        COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+
+    else if (TailMatches("GRANT", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("GRANT", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny))
+        COMPLETE_WITH("TO");
+
+    else if (TailMatches("REVOKE", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny))
+        COMPLETE_WITH("FROM");
+
     /*
      * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
      * TO/FROM
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
     {
         if
(TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
             COMPLETE_WITH("ON");
@@ -3785,7 +3821,8 @@ psql_completion(const char *text, int start, int end)
      * here will only work if the privilege list contains exactly one
      * privilege.
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON"))
     {
         /*
          * With ALTER DEFAULT PRIVILEGES, restrict completion to the kinds of
@@ -3807,6 +3844,7 @@ psql_completion(const char *text, int start, int end)
                                             "FUNCTION",
                                             "LANGUAGE",
                                             "LARGE OBJECT",
+                                            "PARAMETER",
                                             "PROCEDURE",
                                             "ROUTINE",
                                             "SCHEMA",
@@ -3815,13 +3853,15 @@ psql_completion(const char *text, int start, int end)
                                             "TABLESPACE",
                                             "TYPE");
     }
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL"))
         COMPLETE_WITH("FUNCTIONS IN SCHEMA",
                       "PROCEDURES IN SCHEMA",
                       "ROUTINES IN SCHEMA",
                       "SEQUENCES IN SCHEMA",
                       "TABLES IN SCHEMA");
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN"))
         COMPLETE_WITH("DATA WRAPPER", "SERVER");

     /*
@@ -3830,7 +3870,8 @@ psql_completion(const char *text, int start, int end)
      *
      * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", MatchAny))
     {
         if (TailMatches("DATABASE"))
             COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -3868,6 +3909,22 @@ psql_completion(const char *text, int start, int end)
              (HeadMatches("REVOKE") && TailMatches("FROM")))
         COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
                                  Keywords_for_list_of_grant_roles);
+
+    /*
+     * Offer grant options after that.
+     */
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny))
+        COMPLETE_WITH("WITH ADMIN OPTION",
+                      "WITH GRANT OPTION",
+                      "GRANTED BY");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH"))
+        COMPLETE_WITH("ADMIN OPTION",
+                      "GRANT OPTION");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION"))
+        COMPLETE_WITH("GRANTED BY");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION", "GRANTED", "BY"))
+        COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+                                 Keywords_for_list_of_grant_roles);
     /* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
     else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM"))
         COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
@@ -3879,7 +3936,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("FROM");

     /* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA",
MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
@@ -3888,7 +3946,8 @@ psql_completion(const char *text, int start, int end)
     }

     /* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
@@ -3897,7 +3956,8 @@ psql_completion(const char *text, int start, int end)
     }

     /* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..d027075a4c 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -120,6 +120,7 @@ typedef enum ObjectClass
     OCLASS_DEFACL,                /* pg_default_acl */
     OCLASS_EXTENSION,            /* pg_extension */
     OCLASS_EVENT_TRIGGER,        /* pg_event_trigger */
+    OCLASS_PARAMETER_ACL,        /* pg_parameter_acl */
     OCLASS_POLICY,                /* pg_policy */
     OCLASS_PUBLICATION,            /* pg_publication */
     OCLASS_PUBLICATION_NAMESPACE,    /* pg_publication_namespace */
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 4d54ae2a7d..ac6adcb730 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -239,7 +239,7 @@ extern void RunFunctionExecuteHookStr(const char *objectStr);
             RunObjectTruncateHookStr(objectName);                        \
     } while(0)

-#define InvokeObjectPostAlterHookStr(className,objectName,subId)            \
+#define InvokeObjectPostAlterHookStr(classId,objectName,subId)            \
     InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId),        \
                                  InvalidOid,false)
 #define InvokeObjectPostAlterHookArgStr(classId,objectName,subId,        \
diff --git a/src/include/catalog/pg_parameter_acl.h b/src/include/catalog/pg_parameter_acl.h
new file mode 100644
index 0000000000..8316391e51
--- /dev/null
+++ b/src/include/catalog/pg_parameter_acl.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_parameter_acl.h
+ *      definition of the "configuration parameter ACL" system catalog
+ *      (pg_parameter_acl).
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_parameter_acl.h
+ *
+ * NOTES
+ *      The Catalog.pm module reads this file and derives schema
+ *      information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARAMETER_ACL_H
+#define PG_PARAMETER_ACL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_parameter_acl_d.h"
+
+/* ----------------
+ *        pg_parameter_acl definition.  cpp turns this into
+ *        typedef struct FormData_pg_parameter_acl
+ * ----------------
+ */
+CATALOG(pg_parameter_acl,8924,ParameterAclRelationId) BKI_SHARED_RELATION
+{
+    Oid            oid;            /* oid */
+
+#ifdef CATALOG_VARLEN            /* variable-length fields start here */
+    /* name of parameter */
+    text        parname BKI_FORCE_NOT_NULL;
+
+    /* access permissions */
+    aclitem        paracl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_parameter_acl;
+
+
+/* ----------------
+ *        Form_pg_parameter_acl corresponds to a pointer to a tuple with
+ *        the format of pg_parameter_acl relation.
+ * ----------------
+ */
+typedef FormData_pg_parameter_acl *Form_pg_parameter_acl;
+
+DECLARE_TOAST(pg_parameter_acl, 8925, 8926);
+#define PgParameterAclToastTable 8925
+#define PgParameterAclToastIndex 8926
+
+DECLARE_UNIQUE_INDEX(pg_parameter_acl_parname_index, 8927, ParameterAclParnameIndexId, on pg_parameter_acl using
btree(parnametext_ops)); 
+DECLARE_UNIQUE_INDEX_PKEY(pg_parameter_acl_oid_index, 8928, ParameterAclOidIndexId, on pg_parameter_acl using
btree(oidoid_ops)); 
+
+
+extern Oid    ParameterAclLookup(const char *parameter, bool missing_ok);
+extern Oid    ParameterAclCreate(const char *parameter);
+
+#endif                            /* PG_PARAMETER_ACL_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 25304430f4..4d285ece8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7213,6 +7213,22 @@
   proname => 'has_type_privilege', provolatile => 's', prorettype => 'bool',
   proargtypes => 'oid text', prosrc => 'has_type_privilege_id' },

+{ oid => '8050',
+  descr => 'user privilege on parameter by username, parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'name text text',
+  prosrc => 'has_parameter_privilege_name_name' },
+{ oid => '8051',
+  descr => 'user privilege on parameter by user oid, parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid text text',
+  prosrc => 'has_parameter_privilege_id_name' },
+{ oid => '8052',
+  descr => 'current user privilege on parameter by parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'text text',
+  prosrc => 'has_parameter_privilege_name' },
+
 { oid => '2705', descr => 'user privilege on role by username, role name',
   proname => 'pg_has_role', provolatile => 's', prorettype => 'bool',
   proargtypes => 'name name text', prosrc => 'pg_has_role_name_name' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8a9ccf6221..cf5a967694 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,8 +92,8 @@ 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 ACL_SET_VALUE    (1<<12) /* for configuration parameters */
-#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#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 */
@@ -2072,6 +2072,7 @@ typedef enum ObjectType
     OBJECT_OPCLASS,
     OBJECT_OPERATOR,
     OBJECT_OPFAMILY,
+    OBJECT_PARAMETER_ACL,
     OBJECT_POLICY,
     OBJECT_PROCEDURE,
     OBJECT_PUBLICATION,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a73032b319..836f984406 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -327,6 +327,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/utils/acl.h b/src/include/utils/acl.h
index 91ce3d8e9c..48f7d72add 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,7 +146,7 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR            'C'
 #define ACL_CREATE_TEMP_CHR        'T'
 #define ACL_CONNECT_CHR            'c'
-#define ACL_SET_VALUE_CHR        's'
+#define ACL_SET_CHR                's'
 #define ACL_ALTER_SYSTEM_CHR    'A'

 /* string holding all privilege code chars, in order by bitmask position */
@@ -164,6 +164,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_FUNCTION        (ACL_EXECUTE)
 #define ACL_ALL_RIGHTS_LANGUAGE        (ACL_USAGE)
 #define ACL_ALL_RIGHTS_LARGEOBJECT    (ACL_SELECT|ACL_UPDATE)
+#define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM)
 #define ACL_ALL_RIGHTS_SCHEMA        (ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE    (ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE            (ACL_USAGE)
@@ -245,6 +246,10 @@ 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_parameter_aclmask(const char *name, Oid roleid,
+                                    AclMode mask, AclMaskHow how);
+extern AclMode pg_parameter_acl_aclmask(Oid acl_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,
@@ -273,6 +278,10 @@ 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_parameter_aclcheck(const char *name, Oid roleid,
+                                       AclMode mode);
+extern AclResult pg_parameter_acl_aclcheck(Oid acl_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 ea774968f0..3446334e90 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -364,6 +364,8 @@ extern const char *GetConfigOption(const char *name, bool missing_ok,
 extern const char *GetConfigOptionResetString(const char *name);
 extern int    GetConfigOptionFlags(const char *name, bool missing_ok);
 extern void ProcessConfigFile(GucContext context);
+extern char *convert_GUC_name_for_parameter_acl(const char *name);
+extern bool check_GUC_name_for_parameter_acl(const char *name);
 extern void InitializeGUCOptions(void);
 extern bool SelectConfigFiles(const char *userDoption, const char *progname);
 extern void ResetAllOptions(void);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 9c1a76e8bb..4463ea66be 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,8 @@ enum SysCacheIdentifier
     OPEROID,
     OPFAMILYAMNAMENSP,
     OPFAMILYOID,
+    PARAMETERACLNAME,
+    PARAMETERACLOID,
     PARTRELID,
     PROCNAMEARGSNSP,
     PROCOID,
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
index 45ff276f7e..39b274b8fa 100644
--- a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -1,36 +1,84 @@
+-- Creating privileges on a placeholder GUC should create entries in the
+-- pg_parameter_acl catalog which conservatively grant no privileges to public.
+CREATE ROLE regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe;
 -- SET commands fire both the ProcessUtility_hook and the
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
--- should thereafter see the audit messages
+-- should thereafter see the audit messages.
 LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.audit]
 NOTICE:  in process utility: superuser finished set
+-- Creating privileges on an existent custom GUC should create precisely the
+-- right privileges, not overly conservative ones.
+GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Granting multiple privileges on a parameter should be reported correctly to
+-- the OAT hook, but beware that WITH GRANT OPTION is not represented.
+GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check when the hooks fire relative to dependency based abort of a drop
+DROP ROLE regress_role_joe;
+NOTICE:  in process utility: superuser attempting DropRoleStmt
+NOTICE:  in object access: superuser attempting drop (subId=0x0) []
+NOTICE:  in object access: superuser finished drop (subId=0x0) []
+ERROR:  role "regress_role_joe" cannot be dropped because some objects depend on it
+DETAIL:  privileges for parameter test_oat_hooks.user_var1
+privileges for parameter test_oat_hooks.super_var1
+privileges for parameter test_oat_hooks.user_var2
+privileges for parameter test_oat_hooks.super_var2
+privileges for parameter none.such
+privileges for parameter another.bogus
+-- Check the behavior of the hooks relative to do-nothing grants and revokes
+GRANT SET ON PARAMETER work_mem TO PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check the behavior of the hooks relative to unrecognized parameters
+GRANT ALL ON PARAMETER "none.such" TO PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check relative to an operation that causes the catalog entry to be deleted
+REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
 -- Create objects for use in the test
 CREATE USER regress_test_user;
 NOTICE:  in process utility: superuser attempting CreateRoleStmt
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CreateRoleStmt
 CREATE TABLE regress_test_table (t text);
 NOTICE:  in process utility: superuser attempting CreateStmt
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: CREATE TABLE regress_test_table (t text);
                      ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: CREATE TABLE regress_test_table (t text);
                      ^
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [internal]
-NOTICE:  in object access: superuser finished create (subId=0) [internal]
-NOTICE:  in object access: superuser attempting create (subId=0) [internal]
-NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0x0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0x0) [internal]
 NOTICE:  in process utility: superuser finished CreateStmt
 GRANT SELECT ON Table regress_test_table TO public;
 NOTICE:  in process utility: superuser attempting GrantStmt
@@ -39,8 +87,8 @@ CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
     SELECT $1;
 $$ LANGUAGE sql;
 NOTICE:  in process utility: superuser attempting CreateFunctionStmt
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CreateFunctionStmt
 GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
 NOTICE:  in process utility: superuser attempting GrantStmt
@@ -63,35 +111,35 @@ NOTICE:  in executor check perms: superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 -- Do those same things as non-superuser
 SET SESSION AUTHORIZATION regress_test_user;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: non-superuser finished set
 SELECT * FROM regress_test_table;
-NOTICE:  in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: non-superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: non-superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
 NOTICE:  in executor check perms: non-superuser attempting execute
@@ -110,61 +158,89 @@ NOTICE:  in executor check perms: non-superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: non-superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: non-superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: non-superuser attempting alter system
-ERROR:  must be superuser to execute ALTER SYSTEM command
+ERROR:  permission denied to set parameter "work_mem"
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: non-superuser attempting alter system
-ERROR:  must be superuser to execute ALTER SYSTEM command
+ERROR:  permission denied to set parameter "work_mem"
+SET test_oat_hooks.user_var1 = true;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var1]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var1]
+NOTICE:  in process utility: non-superuser finished set
+SET test_oat_hooks.super_var1 = true;
+NOTICE:  in process utility: non-superuser attempting set
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var1"
+ALTER SYSTEM SET test_oat_hooks.user_var1 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.user_var1"
+ALTER SYSTEM SET test_oat_hooks.super_var1 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var1"
+SET test_oat_hooks.user_var2 = true;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var2]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var2]
+NOTICE:  in process utility: non-superuser finished set
+SET test_oat_hooks.super_var2 = true;
+NOTICE:  in process utility: non-superuser attempting set
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var2"
+ALTER SYSTEM SET test_oat_hooks.user_var2 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.user_var2"
+ALTER SYSTEM SET test_oat_hooks.super_var2 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var2"
 RESET SESSION AUTHORIZATION;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: superuser finished set
 -- Turn off non-superuser permissions
 SET test_oat_hooks.deny_set_variable = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_alter_system = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_object_access = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_object_access]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_exec_perms = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_utility_commands = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set)
[test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set)
[test_oat_hooks.deny_utility_commands]
 NOTICE:  in process utility: superuser finished set
 -- Try again as non-superuser with permissions denied
 SET SESSION AUTHORIZATION regress_test_user;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization]
 ERROR:  permission denied: set session_authorization
 SELECT * FROM regress_test_table;
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
 NOTICE:  in executor check perms: superuser attempting execute
@@ -183,28 +259,43 @@ NOTICE:  in executor check perms: superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
+-- Clean up
 RESET SESSION AUTHORIZATION;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.audit = false;
 NOTICE:  in process utility: superuser attempting set
+DROP ROLE regress_role_joe;  -- fails
+ERROR:  role "regress_role_joe" cannot be dropped because some objects depend on it
+DETAIL:  privileges for parameter test_oat_hooks.user_var1
+privileges for parameter test_oat_hooks.super_var1
+privileges for parameter test_oat_hooks.user_var2
+privileges for parameter test_oat_hooks.super_var2
+privileges for parameter none.such
+privileges for parameter another.bogus
+REVOKE ALL PRIVILEGES ON PARAMETER
+    none.such, another.bogus,
+    test_oat_hooks.user_var1, test_oat_hooks.super_var1,
+    test_oat_hooks.user_var2, test_oat_hooks.super_var2
+    FROM regress_role_joe;
+DROP ROLE regress_role_joe;
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
index 09e61864ee..8b6d5373aa 100644
--- a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -1,10 +1,39 @@
+-- Creating privileges on a placeholder GUC should create entries in the
+-- pg_parameter_acl catalog which conservatively grant no privileges to public.
+CREATE ROLE regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe;
+
 -- SET commands fire both the ProcessUtility_hook and the
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
--- should thereafter see the audit messages
+-- should thereafter see the audit messages.
 LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;

+-- Creating privileges on an existent custom GUC should create precisely the
+-- right privileges, not overly conservative ones.
+GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe;
+
+-- Granting multiple privileges on a parameter should be reported correctly to
+-- the OAT hook, but beware that WITH GRANT OPTION is not represented.
+GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe;
+GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION;
+
+-- Check when the hooks fire relative to dependency based abort of a drop
+DROP ROLE regress_role_joe;
+
+-- Check the behavior of the hooks relative to do-nothing grants and revokes
+GRANT SET ON PARAMETER work_mem TO PUBLIC;
+REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC;
+
+-- Check the behavior of the hooks relative to unrecognized parameters
+GRANT ALL ON PARAMETER "none.such" TO PUBLIC;
+
+-- Check relative to an operation that causes the catalog entry to be deleted
+REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC;
+
 -- Create objects for use in the test
 CREATE USER regress_test_user;
 CREATE TABLE regress_test_table (t text);
@@ -30,6 +59,16 @@ SET work_mem = 8192;
 RESET work_mem;
 ALTER SYSTEM SET work_mem = 8192;
 ALTER SYSTEM RESET work_mem;
+
+SET test_oat_hooks.user_var1 = true;
+SET test_oat_hooks.super_var1 = true;
+ALTER SYSTEM SET test_oat_hooks.user_var1 = true;
+ALTER SYSTEM SET test_oat_hooks.super_var1 = true;
+SET test_oat_hooks.user_var2 = true;
+SET test_oat_hooks.super_var2 = true;
+ALTER SYSTEM SET test_oat_hooks.user_var2 = true;
+ALTER SYSTEM SET test_oat_hooks.super_var2 = true;
+
 RESET SESSION AUTHORIZATION;

 -- Turn off non-superuser permissions
@@ -48,6 +87,14 @@ RESET work_mem;
 ALTER SYSTEM SET work_mem = 8192;
 ALTER SYSTEM RESET work_mem;

+-- Clean up
 RESET SESSION AUTHORIZATION;

 SET test_oat_hooks.audit = false;
+DROP ROLE regress_role_joe;  -- fails
+REVOKE ALL PRIVILEGES ON PARAMETER
+    none.such, another.bogus,
+    test_oat_hooks.user_var1, test_oat_hooks.super_var1,
+    test_oat_hooks.user_var2, test_oat_hooks.super_var2
+    FROM regress_role_joe;
+DROP ROLE regress_role_joe;
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index eb7564ce22..551da5d498 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -34,6 +34,15 @@ static bool REGRESS_deny_exec_perms = false;
 static bool REGRESS_deny_utility_commands = false;
 static bool REGRESS_audit = false;

+/*
+ * GUCs for testing privileges on USERSET and SUSET variables,
+ * with and without privileges granted prior to module load.
+ */
+static bool REGRESS_userset_variable1 = false;
+static bool REGRESS_userset_variable2 = false;
+static bool REGRESS_suset_variable1 = false;
+static bool REGRESS_suset_variable2 = false;
+
 /* Saved hook values in case of unload */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
@@ -153,6 +162,56 @@ _PG_init(void)
                              NULL,
                              NULL);

+    /*
+     * test_oat_hooks.user_var{1,2} = (on|off)
+     */
+    DefineCustomBoolVariable("test_oat_hooks.user_var1",
+                             "Dummy parameter settable by public",
+                             NULL,
+                             ®RESS_userset_variable1,
+                             false,
+                             PGC_USERSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    DefineCustomBoolVariable("test_oat_hooks.user_var2",
+                             "Dummy parameter settable by public",
+                             NULL,
+                             ®RESS_userset_variable2,
+                             false,
+                             PGC_USERSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    /*
+     * test_oat_hooks.super_var{1,2} = (on|off)
+     */
+    DefineCustomBoolVariable("test_oat_hooks.super_var1",
+                             "Dummy parameter settable by superuser",
+                             NULL,
+                             ®RESS_suset_variable1,
+                             false,
+                             PGC_SUSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    DefineCustomBoolVariable("test_oat_hooks.super_var2",
+                             "Dummy parameter settable by superuser",
+                             NULL,
+                             ®RESS_suset_variable2,
+                             false,
+                             PGC_SUSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
     MarkGUCPrefixReserved("test_oat_hooks");

     /* Object access hook */
@@ -250,7 +309,14 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char
     switch (access)
     {
         case OAT_POST_ALTER:
-            if (subId & ACL_SET_VALUE)
+            if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
+            {
+                if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                             errmsg("permission denied: all privileges %s", objName)));
+            }
+            else if (subId & ACL_SET)
             {
                 if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
                     ereport(ERROR,
@@ -265,7 +331,7 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char
                              errmsg("permission denied: alter system set %s", objName)));
             }
             else
-                elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId);
+                elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
             break;
         default:
             break;
@@ -860,12 +926,14 @@ accesstype_to_string(ObjectAccessType access, int subId)
             type = "UNRECOGNIZED ObjectAccessType";
     }

-    if (subId & ACL_SET_VALUE)
-        return psprintf("%s (set)", type);
+    if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
+        return psprintf("%s (subId=0x%x, all privileges)", type, subId);
+    if (subId & ACL_SET)
+        return psprintf("%s (subId=0x%x, set)", type, subId);
     if (subId & ACL_ALTER_SYSTEM)
-        return psprintf("%s (alter system set)", type);
+        return psprintf("%s (subId=0x%x, alter system)", type, subId);

-    return  psprintf("%s (subId=%d)", type, subId);
+    return psprintf("%s (subId=0x%x)", type, subId);
 }

 static char *
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 84a35590b7..d842f934a3 100644
--- a/src/test/modules/test_pg_dump/t/001_base.pl
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -316,6 +316,38 @@ my %tests = (
         like         => { pg_dumpall_globals => 1, },
     },

+    'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role'
+      => {
+        create_order => 2,
+        create_sql =>
+          'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;',
+        regexp =>
+
+          qr/^GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;/m,
+        like => { pg_dumpall_globals => 1, },
+      },
+
+    'GRANT ALL ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION'
+      => {
+        create_order => 2,
+        create_sql =>
+          'GRANT SET, ALTER SYSTEM ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION;',
+        regexp =>
+          # "set" plus "alter system" is "all" privileges on parameters
+          qr/^GRANT ALL ON PARAMETER "custom.knob" TO regress_dump_test_role WITH GRANT OPTION;/m,
+        like => { pg_dumpall_globals => 1, },
+      },
+
+    'GRANT ALL ON PARAMETER DateStyle TO regress_dump_test_role' => {
+        create_order => 2,
+        create_sql =>
+          'GRANT ALL ON PARAMETER "DateStyle" TO regress_dump_test_role WITH GRANT OPTION; REVOKE GRANT OPTION FOR ALL
ONPARAMETER DateStyle FROM regress_dump_test_role;', 
+        regexp =>
+          # The revoke simplifies the ultimate grant so as to not include "with grant option"
+          qr/^GRANT ALL ON PARAMETER datestyle TO regress_dump_test_role;/m,
+        like => { pg_dumpall_globals => 1, },
+    },
+
     'CREATE SCHEMA public' => {
         regexp => qr/^CREATE SCHEMA public;/m,
         like   => {
diff --git a/src/test/modules/unsafe_tests/Makefile b/src/test/modules/unsafe_tests/Makefile
index 3ecf5fcfc5..df58273688 100644
--- a/src/test/modules/unsafe_tests/Makefile
+++ b/src/test/modules/unsafe_tests/Makefile
@@ -1,6 +1,6 @@
 # src/test/modules/unsafe_tests/Makefile

-REGRESS = rolenames alter_system_table
+REGRESS = rolenames alter_system_table guc_privs

 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/src/test/modules/unsafe_tests/expected/guc_privs.out
b/src/test/modules/unsafe_tests/expected/guc_privs.out
new file mode 100644
index 0000000000..f62807fd05
--- /dev/null
+++ b/src/test/modules/unsafe_tests/expected/guc_privs.out
@@ -0,0 +1,525 @@
+--
+-- Tests for privileges on GUCs.
+-- This is unsafe because changes will affect other databases in the cluster.
+--
+-- Test with a superuser role.
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Revoke privileges not yet granted
+REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin;
+REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+-- Check the new role does not yet have privileges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Check inappropriate and nonsense privilege types
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+-- Revoke, grant, and revoke again a SUSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Revoke, grant, and revoke again a USERSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Revoke privileges from a non-existent custom GUC.  This should not create
+-- entries in the catalog.
+REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+(0 rows)
+
+-- Grant and then revoke privileges on the non-existent custom GUC.  Check that
+-- a do-nothing entry is not left in the catalogs after the revoke.
+GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+        1
+(1 row)
+
+REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+(0 rows)
+
+-- Can't grant on a non-existent core GUC.
+GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin;  -- fail
+ERROR:  invalid parameter name "no_such_guc"
+-- Initially there are no privileges and no catalog entry for this GUC.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+(0 rows)
+
+-- GRANT SET creates an entry:
+GRANT SET ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- Now grant ALTER SYSTEM:
+GRANT ALL ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- REVOKE ALTER SYSTEM brings us back to just the SET privilege:
+REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- And this should remove the entry altogether:
+REVOKE SET ON PARAMETER enable_material FROM PUBLIC;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+(0 rows)
+
+-- Grant privileges on parameters to the new non-superuser role
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check the new role now has privilges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH
GRANTOPTION'); 
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Check again the inappropriate and nonsense privilege types.  The prior
+-- similar check was performed before any entry for work_mem existed.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+ERROR:  unrecognized privilege type: "WHATEVER WITH GRANT OPTION"
+-- Check other function signatures
+SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                              'max_stack_depth',
+                              'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('hash_mem_multiplier', 'set');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+-- Check object identity functions
+SELECT pg_describe_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+ pg_describe_object
+--------------------
+ parameter work_mem
+(1 row)
+
+SELECT pg_identify_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+      pg_identify_object
+------------------------------
+ ("parameter ACL",,,work_mem)
+(1 row)
+
+SELECT pg_identify_object_as_address(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+  pg_identify_object_as_address
+---------------------------------
+ ("parameter ACL",{work_mem},{})
+(1 row)
+
+SELECT classid::regclass,
+       (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname,
+       objsubid
+FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
+     classid      | parname  | objsubid
+------------------+----------+----------
+ pg_parameter_acl | work_mem |        0
+(1 row)
+
+-- Perform some operations as user 'regress_host_resource_admin'
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+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
+SELECT set_config ('jit_provider', 'llvmjit', true); -- 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
+RESET TimeZone;  -- ok
+SET max_stack_depth = 2048;  -- ok, privileges have been granted
+RESET max_stack_depth;  -- ok, privileges have been granted
+ALTER SYSTEM SET max_stack_depth = 2048;  -- ok, privileges have been granted
+ALTER SYSTEM RESET max_stack_depth;  -- ok, privileges have been granted
+SET lc_messages = 'C';  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+RESET lc_messages;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM SET lc_messages = 'C';  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM RESET lc_messages;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+SELECT set_config ('temp_buffers', '8192', false); -- ok
+ set_config
+------------
+ 64MB
+(1 row)
+
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+ALTER SYSTEM RESET ALL;  -- fail, insufficient privileges
+ERROR:  permission denied to perform ALTER SYSTEM RESET ALL
+-- Check dropping/revoking behavior
+SET SESSION AUTHORIZATION regress_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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"
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+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"
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Check that "reassign owned" doesn't affect privileges
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+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
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin; -- ok
diff --git a/src/test/modules/unsafe_tests/sql/guc_privs.sql b/src/test/modules/unsafe_tests/sql/guc_privs.sql
new file mode 100644
index 0000000000..b38622803d
--- /dev/null
+++ b/src/test/modules/unsafe_tests/sql/guc_privs.sql
@@ -0,0 +1,236 @@
+--
+-- Tests for privileges on GUCs.
+-- This is unsafe because changes will affect other databases in the cluster.
+--
+
+-- Test with a superuser role.
+CREATE ROLE regress_admin SUPERUSER;
+
+-- Perform 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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
+
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Revoke privileges not yet granted
+REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin;
+REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+-- Check the new role does not yet have privileges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+-- Check inappropriate and nonsense privilege types
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+-- Revoke, grant, and revoke again a SUSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+-- Revoke, grant, and revoke again a USERSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+
+-- Revoke privileges from a non-existent custom GUC.  This should not create
+-- entries in the catalog.
+REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+-- Grant and then revoke privileges on the non-existent custom GUC.  Check that
+-- a do-nothing entry is not left in the catalogs after the revoke.
+GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+-- Can't grant on a non-existent core GUC.
+GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin;  -- fail
+
+-- Initially there are no privileges and no catalog entry for this GUC.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- GRANT SET creates an entry:
+GRANT SET ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- Now grant ALTER SYSTEM:
+GRANT ALL ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- REVOKE ALTER SYSTEM brings us back to just the SET privilege:
+REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- And this should remove the entry altogether:
+REVOKE SET ON PARAMETER enable_material FROM PUBLIC;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+
+-- Grant privileges on parameters to the new non-superuser role
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check the new role now has privilges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH
GRANTOPTION'); 
+-- Check again the inappropriate and nonsense privilege types.  The prior
+-- similar check was performed before any entry for work_mem existed.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+
+-- Check other function signatures
+SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                              'max_stack_depth',
+                              'SET');
+SELECT has_parameter_privilege('hash_mem_multiplier', 'set');
+
+-- Check object identity functions
+SELECT pg_describe_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT pg_identify_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT pg_identify_object_as_address(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT classid::regclass,
+       (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname,
+       objsubid
+FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
+
+-- Perform some operations as user 'regress_host_resource_admin'
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+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
+SELECT set_config ('jit_provider', 'llvmjit', true); -- 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
+RESET TimeZone;  -- ok
+SET max_stack_depth = 2048;  -- ok, privileges have been granted
+RESET max_stack_depth;  -- ok, privileges have been granted
+ALTER SYSTEM SET max_stack_depth = 2048;  -- ok, privileges have been granted
+ALTER SYSTEM RESET max_stack_depth;  -- ok, privileges have been granted
+SET lc_messages = 'C';  -- fail, insufficient privileges
+RESET lc_messages;  -- fail, insufficient privileges
+ALTER SYSTEM SET lc_messages = 'C';  -- fail, insufficient privileges
+ALTER SYSTEM RESET lc_messages;  -- fail, insufficient privileges
+SELECT set_config ('temp_buffers', '8192', false); -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+ALTER SYSTEM RESET ALL;  -- fail, insufficient privileges
+
+-- Check dropping/revoking behavior
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+
+-- Check that "reassign owned" doesn't affect privileges
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+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
+
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin; -- ok

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
I wrote:
> Anyway, I'm out of patience with this for today, and I'm throwing
> it up for the cfbot to have a look at.  I'll hit the docs tomorrow.

... and of course, the cfbot gave me the raspberry, because of a
merge conflict in gram.y.  Here's v17 rebased up to HEAD.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7f4f79d1b5..6fab8714b5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -220,6 +220,11 @@
       <entry>access method operator families</entry>
      </row>

+     <row>
+      <entry><link linkend="catalog-pg-parameter-acl"><structname>pg_parameter_acl</structname></link></entry>
+      <entry>configuration parameters for which privileges have been granted</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
       <entry>information about partition key of tables</entry>
@@ -5450,6 +5455,73 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


+ <sect1 id="catalog-pg-parameter-acl">
+  <title><structname>pg_parameter_acl</structname></title>
+
+  <indexterm zone="catalog-pg-parameter-acl">
+   <primary>pg_parameter_acl</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_parameter_acl</structname> records configuration
+   parameters for which privileges have been granted to one or more roles.
+  </para>
+
+  <para>
+   Unlike most system catalogs, <structname>pg_parameter_acl</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_parameter_acl</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_parameter_acl</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>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>parname</structfield> <type>text</type>
+      </para>
+      <para>
+       The name of a configuration parameter for which privileges are granted
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>paracl</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-partitioned-table">
   <title><structname>pg_partitioned_table</structname></title>

@@ -12780,7 +12852,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
     <listitem>
      <para>
       These settings can be set from <filename>postgresql.conf</filename>,
-      or within a session via the <command>SET</command> command; but only superusers
+      or within a session via the <command>SET</command> command; but only
+      superusers and users with <literal>SET</literal> privilege granted
+      on the specific setting
       can change them via <command>SET</command>.  Changes in
       <filename>postgresql.conf</filename> will affect existing sessions
       only if no session-local value has been established with <command>SET</command>.
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 166b7a352d..84771ab6c6 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1691,7 +1691,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</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.
@@ -1959,6 +1960,26 @@ REVOKE ALL ON accounts FROM PUBLIC;
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET</literal></term>
+     <listitem>
+      <para>
+       Allows run-time configuration parameters to be set to a new value or
+       reset to the default value.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER SYSTEM</literal></term>
+     <listitem>
+      <para>
+       Allows server configuration parameters to be configured to a new value
+       or reset to the default configuration value.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>

    The privileges required by other commands are listed on the
@@ -1977,6 +1998,10 @@ REVOKE ALL ON accounts FROM PUBLIC;
    large objects,
    schemas,
    or tablespaces.
+   Some parameters are inherently settable by <literal>PUBLIC</literal>, as if
+   <literal>SET</literal> were granted, but this is not implemented as a grant,
+   nor can the ability for <literal>PUBLIC</literal> to set these parameters be
+   revoked.
    For other types of objects, the default privileges
    granted to <literal>PUBLIC</literal> are as follows:
    <literal>CONNECT</literal> and <literal>TEMPORARY</literal> (create
@@ -2097,6 +2122,16 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>TYPE</literal>
       </entry>
      </row>
+     <row>
+      <entry><literal>SET</literal></entry>
+      <entry><literal>s</literal></entry>
+      <entry><literal>PARAMETER</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ALTER SYSTEM</literal></entry>
+      <entry><literal>A</literal></entry>
+      <entry><literal>PARAMETER</literal></entry>
+     </row>
      </tbody>
    </tgroup>
   </table>
@@ -2167,6 +2202,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry>none</entry>
       <entry><literal>\dl+</literal></entry>
      </row>
+     <row>
+      <entry><literal>PARAMETER</literal></entry>
+      <entry><literal>sA</literal></entry>
+      <entry>none</entry>
+      <entry>none</entry>
+     </row>
      <row>
       <entry><literal>SCHEMA</literal></entry>
       <entry><literal>UC</literal></entry>
@@ -2274,6 +2315,14 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
    access privileges display.  A <literal>*</literal> will appear only when
    grant options have been explicitly granted to someone.
   </para>
+
+  <para>
+   No privileges are required for <literal>PUBLIC</literal> to
+   <command>SET</command> and <command>RESET</command> a
+   <literal>user</literal> parameter.  Parameters are not explicitly owned.
+   All parameters, including those added by extensions, implicitly belong to
+   the bootstrap superuser.
+  </para>
  </sect1>

  <sect1 id="ddl-rowsecurity">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4001cb2bda..22ea39b842 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22701,8 +22701,7 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
    privilege is held with grant option. Also, multiple privilege types can be
    listed separated by commas, in which case the result will be true if any of
    the listed privileges is held. (Case of the privilege string is not
-   significant, and extra whitespace is allowed between but not within
-   privilege names.)
+   significant, and extra whitespace is allowed between privilege names.)
    Some examples:
 <programlisting>
 SELECT has_table_privilege('myschema.mytable', 'select');
@@ -22836,6 +22835,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>

+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_parameter_privilege</primary>
+        </indexterm>
+        <function>has_parameter_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>parameter</parameter> <type>text</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for configuration parameter?
+        Allowable privilege types are <literal>SET</literal>
+        and <literal>ALTER SYSTEM</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/ref/alter_system.sgml b/doc/src/sgml/ref/alter_system.sgml
index 5e41f7f644..6f8bd39eaf 100644
--- a/doc/src/sgml/ref/alter_system.sgml
+++ b/doc/src/sgml/ref/alter_system.sgml
@@ -55,7 +55,8 @@ ALTER SYSTEM RESET ALL
   </para>

   <para>
-   Only superusers can use <command>ALTER SYSTEM</command>.  Also, since
+   Only superusers and users granted <literal>ALTER SYSTEM</literal> privilege
+   on a parameter can change it using <command>ALTER SYSTEM</command>.  Also, since
    this command acts directly on the file system and cannot be rolled back,
    it is not allowed inside a transaction block or function.
   </para>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 8c4edd9b0a..f86b2072f5 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 | ALTER SYSTEM } [, ... ] | ALL [ PRIVILEGES ] }
+    ON PARAMETER <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</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/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 3014c864ea..58455c519e 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -118,6 +118,13 @@ REVOKE [ GRANT OPTION FOR ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
     [ CASCADE | RESTRICT ]

+REVOKE [ GRANT OPTION FOR ]
+    { { SET | ALTER SYSTEM } [, ...] | ALL [ PRIVILEGES ] }
+    ON PARAMETER <replaceable class="parameter">configuration_parameter</replaceable> [, ...]
+    FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ ADMIN OPTION FOR ]
     <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable
class="parameter">role_specification</replaceable>[, ...] 
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 339ee9eec9..465edcf4fd 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -34,10 +34,10 @@ 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.)
-   <command>SET</command> only affects the value used by the current
-   session.
+   (But some require either superuser privileges or granted
+   <literal>SET</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>

   <para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 87d7386e01..89a0221ec9 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,6 +38,7 @@ OBJS = \
     pg_largeobject.o \
     pg_namespace.o \
     pg_operator.o \
+    pg_parameter_acl.o \
     pg_proc.o \
     pg_publication.o \
     pg_range.o \
@@ -68,7 +69,8 @@ CATALOG_HEADERS := \
     pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
     pg_foreign_table.h pg_policy.h pg_replication_origin.h \
     pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
-    pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
+    pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \
+    pg_range.h pg_transform.h \
     pg_sequence.h pg_publication.h pg_publication_namespace.h \
     pg_publication_rel.h pg_subscription.h pg_subscription_rel.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd03a8e51..5f1726c095 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
@@ -112,11 +113,13 @@ 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_Parameter(InternalGrant *grantStmt);

 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);

-static List *objectNamesToOids(ObjectType objtype, List *objnames);
+static List *objectNamesToOids(ObjectType objtype, List *objnames,
+                               bool is_grant);
 static List *objectsInSchemaToOids(ObjectType objtype, List *nspnames);
 static List *getRelationsInNamespace(Oid namespaceId, char relkind);
 static void expand_col_privileges(List *colnames, Oid table_oid,
@@ -259,6 +262,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_PARAMETER_ACL:
+            whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            break;
         default:
             elog(ERROR, "unrecognized object type: %d", objtype);
             /* not reached, but keep compiler quiet */
@@ -390,7 +396,8 @@ ExecuteGrantStmt(GrantStmt *stmt)
     switch (stmt->targtype)
     {
         case ACL_TARGET_OBJECT:
-            istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects);
+            istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects,
+                                              stmt->is_grant);
             break;
         case ACL_TARGET_ALL_IN_SCHEMA:
             istmt.objects = objectsInSchemaToOids(stmt->objtype, stmt->objects);
@@ -498,6 +505,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
             all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
             errormsg = gettext_noop("invalid privilege type %s for foreign server");
             break;
+        case OBJECT_PARAMETER_ACL:
+            all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            errormsg = gettext_noop("invalid privilege type %s for parameter");
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) stmt->objtype);
@@ -600,6 +611,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
         case OBJECT_TABLESPACE:
             ExecGrant_Tablespace(istmt);
             break;
+        case OBJECT_PARAMETER_ACL:
+            ExecGrant_Parameter(istmt);
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) istmt->objtype);
@@ -626,7 +640,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
  * to fail.
  */
 static List *
-objectNamesToOids(ObjectType objtype, List *objnames)
+objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 {
     List       *objects = NIL;
     ListCell   *cell;
@@ -759,6 +773,37 @@ objectNamesToOids(ObjectType objtype, List *objnames)
                 objects = lappend_oid(objects, srvid);
             }
             break;
+        case OBJECT_PARAMETER_ACL:
+            foreach(cell, objnames)
+            {
+                /*
+                 * In this code we represent a GUC by the OID of its entry in
+                 * pg_parameter_acl, which we have to manufacture here if it
+                 * doesn't exist yet.  (That's a hack for sure, but it avoids
+                 * messing with all the GRANT/REVOKE infrastructure that
+                 * expects to use OIDs for object identities.)  However, if
+                 * this is a REVOKE, we can instead just ignore any GUCs that
+                 * don't have such an entry, as they must not have any
+                 * privileges needing removal.
+                 */
+                char       *parameter = strVal(lfirst(cell));
+                Oid            parameterId = ParameterAclLookup(parameter, true);
+
+                if (!OidIsValid(parameterId) && is_grant)
+                {
+                    parameterId = ParameterAclCreate(parameter);
+
+                    /*
+                     * Prevent error when processing duplicate objects, and
+                     * make this new entry visible so that ExecGrant_Parameter
+                     * can update it.
+                     */
+                    CommandCounterIncrement();
+                }
+                if (OidIsValid(parameterId))
+                    objects = lappend_oid(objects, parameterId);
+            }
+            break;
         default:
             elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                  (int) objtype);
@@ -1494,6 +1539,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
             case ForeignDataWrapperRelationId:
                 istmt.objtype = OBJECT_FDW;
                 break;
+            case ParameterAclRelationId:
+                istmt.objtype = OBJECT_PARAMETER_ACL;
+                break;
             default:
                 elog(ERROR, "unexpected object class %u", classid);
                 break;
@@ -3225,6 +3273,154 @@ ExecGrant_Type(InternalGrant *istmt)
     table_close(relation, RowExclusiveLock);
 }

+static void
+ExecGrant_Parameter(InternalGrant *istmt)
+{
+    Relation    relation;
+    ListCell   *cell;
+
+    if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+        istmt->privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
+
+    relation = table_open(ParameterAclRelationId, RowExclusiveLock);
+
+    foreach(cell, istmt->objects)
+    {
+        Oid            parameterId = lfirst_oid(cell);
+        Datum        nameDatum;
+        const char *parname;
+        Datum        aclDatum;
+        bool        isNull;
+        AclMode        avail_goptions;
+        AclMode        this_privileges;
+        Acl           *old_acl;
+        Acl           *new_acl;
+        Oid            grantorId;
+        Oid            ownerId;
+        HeapTuple    tuple;
+        int            noldmembers;
+        int            nnewmembers;
+        Oid           *oldmembers;
+        Oid           *newmembers;
+
+        tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(parameterId));
+        if (!HeapTupleIsValid(tuple))
+            elog(ERROR, "cache lookup failed for parameter ACL %u",
+                 parameterId);
+
+        /* We'll need the GUC's name */
+        nameDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                                    Anum_pg_parameter_acl_parname,
+                                    &isNull);
+        Assert(!isNull);
+        parname = TextDatumGetCString(nameDatum);
+
+        /* Treat all parameters as belonging to the bootstrap superuser. */
+        ownerId = BOOTSTRAP_SUPERUSERID;
+
+        /*
+         * Get working copy of existing ACL. If there's no ACL, substitute the
+         * proper default.
+         */
+        aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                                   Anum_pg_parameter_acl_paracl,
+                                   &isNull);
+
+        if (isNull)
+        {
+            old_acl = acldefault(istmt->objtype, ownerId);
+            /* 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, ownerId,
+                            &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,
+                                     parameterId, grantorId,
+                                     OBJECT_PARAMETER_ACL,
+                                     parname,
+                                     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, ownerId);
+
+        /*
+         * We need the members of both old and new ACLs so we can correct the
+         * shared dependency information.
+         */
+        nnewmembers = aclmembers(new_acl, &newmembers);
+
+        /*
+         * If the new ACL is equal to the default, we don't need the catalog
+         * entry any longer.  Delete it rather than updating it, to avoid
+         * leaving a degenerate entry.
+         */
+        if (aclequal(new_acl, acldefault(istmt->objtype, ownerId)))
+        {
+            CatalogTupleDelete(relation, &tuple->t_self);
+        }
+        else
+        {
+            /* finished building new ACL value, now insert it */
+            HeapTuple    newtuple;
+            Datum        values[Natts_pg_parameter_acl];
+            bool        nulls[Natts_pg_parameter_acl];
+            bool        replaces[Natts_pg_parameter_acl];
+
+            MemSet(values, 0, sizeof(values));
+            MemSet(nulls, false, sizeof(nulls));
+            MemSet(replaces, false, sizeof(replaces));
+
+            replaces[Anum_pg_parameter_acl_paracl - 1] = true;
+            values[Anum_pg_parameter_acl_paracl - 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(parameterId, ParameterAclRelationId, 0,
+                                new_acl);
+
+        /* Update the shared dependency ACL info */
+        updateAclDependencies(ParameterAclRelationId, parameterId, 0,
+                              ownerId,
+                              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 +3451,10 @@ string_to_privilege(const char *privname)
         return ACL_CREATE_TEMP;
     if (strcmp(privname, "connect") == 0)
         return ACL_CONNECT;
+    if (strcmp(privname, "set") == 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 +3492,10 @@ privilege_to_string(AclMode privilege)
             return "TEMP";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET:
+            return "SET";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized privilege: %d", (int) privilege);
     }
@@ -3376,6 +3580,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_OPFAMILY:
                         msg = gettext_noop("permission denied for operator family %s");
                         break;
+                    case OBJECT_PARAMETER_ACL:
+                        msg = gettext_noop("permission denied for parameter %s");
+                        break;
                     case OBJECT_POLICY:
                         msg = gettext_noop("permission denied for policy %s");
                         break;
@@ -3567,6 +3774,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
                     case OBJECT_DEFAULT:
                     case OBJECT_DEFACL:
                     case OBJECT_DOMCONSTRAINT:
+                    case OBJECT_PARAMETER_ACL:
                     case OBJECT_PUBLICATION_NAMESPACE:
                     case OBJECT_PUBLICATION_REL:
                     case OBJECT_ROLE:
@@ -3653,6 +3861,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
         case OBJECT_LARGEOBJECT:
             return pg_largeobject_aclmask_snapshot(table_oid, roleid,
                                                    mask, how, NULL);
+        case OBJECT_PARAMETER_ACL:
+            return pg_parameter_acl_aclmask(table_oid, roleid, mask, how);
         case OBJECT_SCHEMA:
             return pg_namespace_aclmask(table_oid, roleid, mask, how);
         case OBJECT_STATISTIC_EXT:
@@ -4000,6 +4210,121 @@ pg_database_aclmask(Oid db_oid, Oid roleid,
     return result;
 }

+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC), identified by GUC name.
+ */
+AclMode
+pg_parameter_aclmask(const char *name, Oid roleid, AclMode mask, AclMaskHow how)
+{
+    AclMode        result;
+    char       *parname;
+    text       *partext;
+    HeapTuple    tuple;
+
+    /* Superusers bypass all permission checking. */
+    if (superuser_arg(roleid))
+        return mask;
+
+    /* Convert name to the form it should have in pg_parameter_acl... */
+    parname = convert_GUC_name_for_parameter_acl(name);
+    partext = cstring_to_text(parname);
+
+    /* ... and look it up */
+    tuple = SearchSysCache1(PARAMETERACLNAME, PointerGetDatum(partext));
+
+    if (!HeapTupleIsValid(tuple))
+    {
+        /* If no entry, GUC has no permissions for non-superusers */
+        result = ACL_NO_RIGHTS;
+    }
+    else
+    {
+        Datum        aclDatum;
+        bool        isNull;
+        Acl           *acl;
+
+        aclDatum = SysCacheGetAttr(PARAMETERACLNAME, tuple,
+                                   Anum_pg_parameter_acl_paracl,
+                                   &isNull);
+        if (isNull)
+        {
+            /* No ACL, so build default ACL */
+            acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID);
+            aclDatum = (Datum) 0;
+        }
+        else
+        {
+            /* detoast ACL if necessary */
+            acl = DatumGetAclP(aclDatum);
+        }
+
+        result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, mask, how);
+
+        /* if we have a detoasted copy, free it */
+        if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+            pfree(acl);
+
+        ReleaseSysCache(tuple);
+    }
+
+    pfree(parname);
+    pfree(partext);
+
+    return result;
+}
+
+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC), identified by the OID of its pg_parameter_acl entry.
+ */
+AclMode
+pg_parameter_acl_aclmask(Oid acl_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 ACL from pg_parameter_acl */
+    tuple = SearchSysCache1(PARAMETERACLOID, ObjectIdGetDatum(acl_oid));
+    if (!HeapTupleIsValid(tuple))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("parameter ACL with OID %u does not exist",
+                        acl_oid)));
+
+    aclDatum = SysCacheGetAttr(PARAMETERACLOID, tuple,
+                               Anum_pg_parameter_acl_paracl,
+                               &isNull);
+    if (isNull)
+    {
+        /* No ACL, so build default ACL */
+        acl = acldefault(OBJECT_PARAMETER_ACL, BOOTSTRAP_SUPERUSERID);
+        aclDatum = (Datum) 0;
+    }
+    else
+    {
+        /* detoast ACL if necessary */
+        acl = DatumGetAclP(aclDatum);
+    }
+
+    result = aclmask(acl, roleid, BOOTSTRAP_SUPERUSERID, 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 +5038,32 @@ 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 (GUC), identified by GUC name.
+ */
+AclResult
+pg_parameter_aclcheck(const char *name, Oid roleid, AclMode mode)
+{
+    if (pg_parameter_aclmask(name, roleid, mode, ACLMASK_ANY) != 0)
+        return ACLCHECK_OK;
+    else
+        return ACLCHECK_NO_PRIV;
+}
+
+/*
+ * Exported routine for checking a user's access privileges to a configuration
+ * parameter (GUC), identified by the OID of its pg_parameter_acl entry.
+ */
+AclResult
+pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, AclMode mode)
+{
+    if (pg_parameter_acl_aclmask(acl_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 dfd5fb669e..520f77971b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -247,32 +248,35 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == SharedDescriptionRelationId ||
-        relationId == SharedDependRelationId ||
-        relationId == SharedSecLabelRelationId ||
-        relationId == TableSpaceRelationId ||
         relationId == DbRoleSettingRelationId ||
+        relationId == ParameterAclRelationId ||
         relationId == ReplicationOriginRelationId ||
-        relationId == SubscriptionRelationId)
+        relationId == SharedDependRelationId ||
+        relationId == SharedDescriptionRelationId ||
+        relationId == SharedSecLabelRelationId ||
+        relationId == SubscriptionRelationId ||
+        relationId == TableSpaceRelationId)
         return true;
     /* These are their indexes */
-    if (relationId == AuthIdRolnameIndexId ||
-        relationId == AuthIdOidIndexId ||
-        relationId == AuthMemRoleMemIndexId ||
+    if (relationId == AuthIdOidIndexId ||
+        relationId == AuthIdRolnameIndexId ||
         relationId == AuthMemMemRoleIndexId ||
+        relationId == AuthMemRoleMemIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == SharedDescriptionObjIndexId ||
-        relationId == SharedDependDependerIndexId ||
-        relationId == SharedDependReferenceIndexId ||
-        relationId == SharedSecLabelObjectIndexId ||
-        relationId == TablespaceOidIndexId ||
-        relationId == TablespaceNameIndexId ||
         relationId == DbRoleSettingDatidRolidIndexId ||
+        relationId == ParameterAclOidIndexId ||
+        relationId == ParameterAclParnameIndexId ||
         relationId == ReplicationOriginIdentIndex ||
         relationId == ReplicationOriginNameIndex ||
+        relationId == SharedDependDependerIndexId ||
+        relationId == SharedDependReferenceIndexId ||
+        relationId == SharedDescriptionObjIndexId ||
+        relationId == SharedSecLabelObjectIndexId ||
+        relationId == SubscriptionNameIndexId ||
         relationId == SubscriptionObjectIndexId ||
-        relationId == SubscriptionNameIndexId)
+        relationId == TablespaceNameIndexId ||
+        relationId == TablespaceOidIndexId)
         return true;
     /* These are their toast tables and toast indexes */
     if (relationId == PgAuthidToastTable ||
@@ -281,6 +285,8 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
+        relationId == PgParameterAclToastTable ||
+        relationId == PgParameterAclToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 25fe56d310..de10923391 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -178,6 +179,7 @@ static const Oid object_classes[] = {
     DefaultAclRelationId,        /* OCLASS_DEFACL */
     ExtensionRelationId,        /* OCLASS_EXTENSION */
     EventTriggerRelationId,        /* OCLASS_EVENT_TRIGGER */
+    ParameterAclRelationId,        /* OCLASS_PARAMETER_ACL */
     PolicyRelationId,            /* OCLASS_POLICY */
     PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */
     PublicationRelationId,        /* OCLASS_PUBLICATION */
@@ -1507,6 +1509,7 @@ doDeletion(const ObjectAddress *object, int flags)
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
         case OCLASS_SUBSCRIPTION:
+        case OCLASS_PARAMETER_ACL:
             elog(ERROR, "global objects cannot be deleted by doDeletion");
             break;

@@ -2861,6 +2864,9 @@ getObjectClass(const ObjectAddress *object)
         case EventTriggerRelationId:
             return OCLASS_EVENT_TRIGGER;

+        case ParameterAclRelationId:
+            return OCLASS_PARAMETER_ACL;
+
         case PolicyRelationId:
             return OCLASS_POLICY;

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 3fd17ea64f..31c80f7209 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -818,6 +819,10 @@ static const struct object_type_map
     {
         "event trigger", OBJECT_EVENT_TRIGGER
     },
+    /* OCLASS_PARAMETER_ACL */
+    {
+        "parameter ACL", OBJECT_PARAMETER_ACL
+    },
     /* OCLASS_POLICY */
     {
         "policy", OBJECT_POLICY
@@ -1014,6 +1019,7 @@ get_object_address(ObjectType objtype, Node *object,
             case OBJECT_FDW:
             case OBJECT_FOREIGN_SERVER:
             case OBJECT_EVENT_TRIGGER:
+            case OBJECT_PARAMETER_ACL:
             case OBJECT_ACCESS_METHOD:
             case OBJECT_PUBLICATION:
             case OBJECT_SUBSCRIPTION:
@@ -1315,6 +1321,11 @@ get_object_address_unqualified(ObjectType objtype,
             address.objectId = get_event_trigger_oid(name, missing_ok);
             address.objectSubId = 0;
             break;
+        case OBJECT_PARAMETER_ACL:
+            address.classId = ParameterAclRelationId;
+            address.objectId = ParameterAclLookup(name, missing_ok);
+            address.objectSubId = 0;
+            break;
         case OBJECT_PUBLICATION:
             address.classId = PublicationRelationId;
             address.objectId = get_publication_oid(name, missing_ok);
@@ -2307,6 +2318,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
         case OBJECT_FDW:
         case OBJECT_FOREIGN_SERVER:
         case OBJECT_LANGUAGE:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_PUBLICATION:
         case OBJECT_ROLE:
         case OBJECT_SCHEMA:
@@ -2597,6 +2609,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
         case OBJECT_TSPARSER:
         case OBJECT_TSTEMPLATE:
         case OBJECT_ACCESS_METHOD:
+        case OBJECT_PARAMETER_ACL:
             /* We treat these object types as being owned by superusers */
             if (!superuser_arg(roleid))
                 ereport(ERROR,
@@ -3880,6 +3893,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
                 break;
             }

+        case OCLASS_PARAMETER_ACL:
+            {
+                HeapTuple    tup;
+                Datum        nameDatum;
+                bool        isNull;
+                char       *parname;
+
+                tup = SearchSysCache1(PARAMETERACLOID,
+                                      ObjectIdGetDatum(object->objectId));
+                if (!HeapTupleIsValid(tup))
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for parameter ACL %u",
+                             object->objectId);
+                    break;
+                }
+                nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup,
+                                            Anum_pg_parameter_acl_parname,
+                                            &isNull);
+                Assert(!isNull);
+                parname = TextDatumGetCString(nameDatum);
+                appendStringInfo(&buffer, _("parameter %s"), parname);
+                ReleaseSysCache(tup);
+                break;
+            }
+
         case OCLASS_POLICY:
             {
                 Relation    policy_rel;
@@ -4547,6 +4586,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
             appendStringInfoString(&buffer, "event trigger");
             break;

+        case OCLASS_PARAMETER_ACL:
+            appendStringInfoString(&buffer, "parameter ACL");
+            break;
+
         case OCLASS_POLICY:
             appendStringInfoString(&buffer, "policy");
             break;
@@ -5693,6 +5736,34 @@ getObjectIdentityParts(const ObjectAddress *object,
                 break;
             }

+        case OCLASS_PARAMETER_ACL:
+            {
+                HeapTuple    tup;
+                Datum        nameDatum;
+                bool        isNull;
+                char       *parname;
+
+                tup = SearchSysCache1(PARAMETERACLOID,
+                                      ObjectIdGetDatum(object->objectId));
+                if (!HeapTupleIsValid(tup))
+                {
+                    if (!missing_ok)
+                        elog(ERROR, "cache lookup failed for parameter ACL %u",
+                             object->objectId);
+                    break;
+                }
+                nameDatum = SysCacheGetAttr(PARAMETERACLOID, tup,
+                                            Anum_pg_parameter_acl_parname,
+                                            &isNull);
+                Assert(!isNull);
+                parname = TextDatumGetCString(nameDatum);
+                appendStringInfoString(&buffer, parname);
+                if (objname)
+                    *objname = list_make1(parname);
+                ReleaseSysCache(tup);
+                break;
+            }
+
         case OCLASS_POLICY:
             {
                 Relation    polDesc;
diff --git a/src/backend/catalog/pg_parameter_acl.c b/src/backend/catalog/pg_parameter_acl.c
new file mode 100644
index 0000000000..2decee909b
--- /dev/null
+++ b/src/backend/catalog/pg_parameter_acl.c
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_parameter_acl.c
+ *      routines to support manipulation of the pg_parameter_acl relation
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *      src/backend/catalog/pg_parameter_acl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_parameter_acl.h"
+#include "utils/builtins.h"
+#include "utils/pg_locale.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * ParameterAclLookup - Given a configuration parameter name,
+ * look up the associated configuration parameter ACL's OID.
+ *
+ * If missing_ok is false, throw an error if ACL entry not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+ParameterAclLookup(const char *parameter, bool missing_ok)
+{
+    Oid            oid;
+    char       *parname;
+
+    /* Convert name to the form it should have in pg_parameter_acl... */
+    parname = convert_GUC_name_for_parameter_acl(parameter);
+
+    /* ... and look it up */
+    oid = GetSysCacheOid1(PARAMETERACLNAME, Anum_pg_parameter_acl_oid,
+                          PointerGetDatum(cstring_to_text(parname)));
+
+    if (!OidIsValid(oid) && !missing_ok)
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("parameter ACL \"%s\" does not exist", parameter)));
+
+    pfree(parname);
+
+    return oid;
+}
+
+/*
+ * ParameterAclCreate
+ *
+ * Add a new tuple to pg_parameter_acl.
+ *
+ * parameter: the parameter name to create an entry for.
+ * Caller should have verified that there's no such entry already.
+ *
+ * Returns the new entry's OID.
+ */
+Oid
+ParameterAclCreate(const char *parameter)
+{
+    Oid            parameterId;
+    char       *parname;
+    Relation    rel;
+    TupleDesc    tupDesc;
+    HeapTuple    tuple;
+    Datum        values[Natts_pg_parameter_acl];
+    bool        nulls[Natts_pg_parameter_acl];
+
+    /*
+     * To prevent cluttering pg_parameter_acl with useless entries, insist
+     * that the name be valid.
+     */
+    if (!check_GUC_name_for_parameter_acl(parameter))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_NAME),
+                 errmsg("invalid parameter name \"%s\"",
+                        parameter)));
+
+    /* Convert name to the form it should have in pg_parameter_acl. */
+    parname = convert_GUC_name_for_parameter_acl(parameter);
+
+    /*
+     * Create and insert a new record containing a null ACL.
+     *
+     * We don't take a strong enough lock to prevent concurrent insertions,
+     * relying instead on the unique index.
+     */
+    rel = table_open(ParameterAclRelationId, RowExclusiveLock);
+    tupDesc = RelationGetDescr(rel);
+    MemSet(values, 0, sizeof(values));
+    MemSet(nulls, false, sizeof(nulls));
+    parameterId = GetNewOidWithIndex(rel,
+                                     ParameterAclOidIndexId,
+                                     Anum_pg_parameter_acl_oid);
+    values[Anum_pg_parameter_acl_oid - 1] = ObjectIdGetDatum(parameterId);
+    values[Anum_pg_parameter_acl_parname - 1] =
+        PointerGetDatum(cstring_to_text(parname));
+    nulls[Anum_pg_parameter_acl_paracl - 1] = true;
+    tuple = heap_form_tuple(tupDesc, values, nulls);
+    CatalogTupleInsert(rel, tuple);
+
+    /* Close pg_parameter_acl, but keep lock till commit. */
+    heap_freetuple(tuple);
+    table_close(rel, NoLock);
+
+    return parameterId;
+}
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1f64c8aa51..5456b8222b 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -658,6 +658,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
         case OCLASS_DEFACL:
         case OCLASS_EXTENSION:
         case OCLASS_EVENT_TRIGGER:
+        case OCLASS_PARAMETER_ACL:
         case OCLASS_POLICY:
         case OCLASS_PUBLICATION:
         case OCLASS_PUBLICATION_NAMESPACE:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 3c3fc2515b..4642527881 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -940,6 +940,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
         case OBJECT_DATABASE:
         case OBJECT_TABLESPACE:
         case OBJECT_ROLE:
+        case OBJECT_PARAMETER_ACL:
             /* no support for global objects */
             return false;
         case OBJECT_EVENT_TRIGGER:
@@ -1015,6 +1016,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
         case OCLASS_DATABASE:
         case OCLASS_TBLSPACE:
         case OCLASS_ROLE:
+        case OCLASS_PARAMETER_ACL:
             /* no support for global objects */
             return false;
         case OCLASS_EVENT_TRIGGER:
@@ -2042,6 +2044,8 @@ stringify_grant_objtype(ObjectType objtype)
             return "LARGE OBJECT";
         case OBJECT_SCHEMA:
             return "SCHEMA";
+        case OBJECT_PARAMETER_ACL:
+            return "PARAMETER";
         case OBJECT_PROCEDURE:
             return "PROCEDURE";
         case OBJECT_ROUTINE:
@@ -2153,6 +2157,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
         case OBJECT_OPCLASS:
         case OBJECT_OPERATOR:
         case OBJECT_OPFAMILY:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_POLICY:
         case OBJECT_PUBLICATION:
         case OBJECT_PUBLICATION_NAMESPACE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7a62d547e2..7ae19b98bb 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -78,6 +78,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
         case OBJECT_OPCLASS:
         case OBJECT_OPERATOR:
         case OBJECT_OPFAMILY:
+        case OBJECT_PARAMETER_ACL:
         case OBJECT_POLICY:
         case OBJECT_PUBLICATION_NAMESPACE:
         case OBJECT_PUBLICATION_REL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7febb5018f..a241b44497 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12655,6 +12655,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
             case OCLASS_DEFACL:
             case OCLASS_EXTENSION:
             case OCLASS_EVENT_TRIGGER:
+            case OCLASS_PARAMETER_ACL:
             case OCLASS_PUBLICATION:
             case OCLASS_PUBLICATION_NAMESPACE:
             case OCLASS_PUBLICATION_REL:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e3f4a5d27..2cc92a8943 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -371,8 +371,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 parameter_name
+%type <list>    OptSchemaEltList parameter_name_list

 %type <chr>        am_type

@@ -827,7 +827,8 @@ 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 PATH PLACING PLAN PLANS POLICY
+    PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
+    PLACING PLAN PLANS POLICY
     POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
     PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION

@@ -7197,6 +7198,13 @@ privilege:    SELECT opt_column_list
                 n->cols = $2;
                 $$ = n;
             }
+        | ALTER SYSTEM_P
+            {
+                AccessPriv *n = makeNode(AccessPriv);
+                n->priv_name = pstrdup("alter system");
+                n->cols = NIL;
+                $$ = n;
+            }
         | ColId opt_column_list
             {
                 AccessPriv *n = makeNode(AccessPriv);
@@ -7206,6 +7214,28 @@ privilege:    SELECT opt_column_list
             }
         ;

+parameter_name_list:
+        parameter_name
+            {
+                $$ = list_make1(makeString($1));
+            }
+        | parameter_name_list ',' parameter_name
+            {
+                $$ = lappend($1, makeString($3));
+            }
+        ;
+
+parameter_name:
+        ColId
+            {
+                $$ = $1;
+            }
+        | parameter_name '.' ColId
+            {
+                $$ = psprintf("%s.%s", $1, $3);
+            }
+        ;
+

 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
@@ -7307,6 +7337,14 @@ privilege_target:
                     n->objs = $3;
                     $$ = n;
                 }
+            | PARAMETER parameter_name_list
+                {
+                    PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+                    n->targtype = ACL_TARGET_OBJECT;
+                    n->objtype = OBJECT_PARAMETER_ACL;
+                    n->objs = $2;
+                    $$ = n;
+                }
             | SCHEMA name_list
                 {
                     PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -17065,6 +17103,7 @@ unreserved_keyword:
             | OWNED
             | OWNER
             | PARALLEL
+            | PARAMETER
             | PARSER
             | PARTIAL
             | PARTITION
@@ -17682,6 +17721,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 83cf7ac9ff..9263fa2901 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/proclang.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"
@@ -109,6 +111,7 @@ static Oid    convert_tablespace_name(text *tablespacename);
 static AclMode convert_tablespace_priv_string(text *priv_type_text);
 static Oid    convert_type_name(text *typename);
 static AclMode convert_type_priv_string(text *priv_type_text);
+static AclMode convert_parameter_priv_string(text *priv_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);

@@ -306,6 +309,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 +803,10 @@ acldefault(ObjectType objtype, Oid ownerId)
             world_default = ACL_USAGE;
             owner_default = ACL_ALL_RIGHTS_TYPE;
             break;
+        case OBJECT_PARAMETER_ACL:
+            world_default = ACL_NO_RIGHTS;
+            owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
+            break;
         default:
             elog(ERROR, "unrecognized objtype: %d", (int) objtype);
             world_default = ACL_NO_RIGHTS;    /* keep compiler quiet */
@@ -873,6 +886,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
         case 'L':
             objtype = OBJECT_LARGEOBJECT;
             break;
+        case 'p':
+            objtype = OBJECT_PARAMETER_ACL;
+            break;
         case 'n':
             objtype = OBJECT_SCHEMA;
             break;
@@ -1602,6 +1618,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") == 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 +1718,10 @@ convert_aclright_to_string(int aclright)
             return "TEMPORARY";
         case ACL_CONNECT:
             return "CONNECT";
+        case ACL_SET:
+            return "SET";
+        case ACL_ALTER_SYSTEM:
+            return "ALTER SYSTEM";
         default:
             elog(ERROR, "unrecognized aclright: %d", aclright);
             return NULL;
@@ -4429,6 +4453,96 @@ convert_type_priv_string(text *priv_type_text)
     return convert_any_priv_string(priv_type_text, type_priv_map);
 }

+/*
+ * has_parameter_privilege variants
+ *        These are all named "has_parameter_privilege" at the SQL level.
+ *        They take various combinations of parameter name with
+ *        user name, user OID, or implicit user = current_user.
+ *
+ *        The result is a boolean value: true if user has been granted
+ *        the indicated privilege or false if not.
+ */
+
+/*
+ * has_param_priv_byname
+ *
+ *        Helper function to check user privileges on a parameter given the
+ *        role by Oid, parameter by text name, and privileges as AclMode.
+ */
+static bool
+has_param_priv_byname(Oid roleid, const text *parameter, AclMode priv)
+{
+    char       *paramstr = text_to_cstring(parameter);
+
+    return pg_parameter_aclcheck(paramstr, roleid, priv) == ACLCHECK_OK;
+}
+
+/*
+ * has_parameter_privilege_name_name
+ *        Check user privileges on a parameter given name username, text
+ *        parameter, and text priv name.
+ */
+Datum
+has_parameter_privilege_name_name(PG_FUNCTION_ARGS)
+{
+    Name        username = PG_GETARG_NAME(0);
+    text       *parameter = PG_GETARG_TEXT_PP(1);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+    Oid            roleid = get_role_oid_or_public(NameStr(*username));
+
+    PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_name
+ *        Check user privileges on a parameter given text parameter and text priv
+ *        name.  current_user is assumed
+ */
+Datum
+has_parameter_privilege_name(PG_FUNCTION_ARGS)
+{
+    text       *parameter = PG_GETARG_TEXT_PP(0);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(1));
+
+    PG_RETURN_BOOL(has_param_priv_byname(GetUserId(), parameter, priv));
+}
+
+/*
+ * has_parameter_privilege_id_name
+ *        Check user privileges on a parameter given roleid, text parameter, and
+ *        text priv name.
+ */
+Datum
+has_parameter_privilege_id_name(PG_FUNCTION_ARGS)
+{
+    Oid            roleid = PG_GETARG_OID(0);
+    text       *parameter = PG_GETARG_TEXT_PP(1);
+    AclMode        priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
+
+    PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv));
+}
+
+/*
+ *        Support routines for has_parameter_privilege family.
+ */
+
+/*
+ * convert_parameter_priv_string
+ *        Convert text string to AclMode value.
+ */
+static AclMode
+convert_parameter_priv_string(text *priv_text)
+{
+    static const priv_map parameter_priv_map[] = {
+        {"SET", ACL_SET},
+        {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)},
+        {"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+        {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
+        {NULL, 0}
+    };
+
+    return convert_any_priv_string(priv_text, parameter_priv_map);
+}

 /*
  * pg_has_role variants
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a675877d19..8d265f2d23 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_publication.h"
@@ -574,6 +575,28 @@ static const struct cachedesc cacheinfo[] = {
         },
         8
     },
+    {ParameterAclRelationId,    /* PARAMETERACLNAME */
+        ParameterAclParnameIndexId,
+        1,
+        {
+            Anum_pg_parameter_acl_parname,
+            0,
+            0,
+            0
+        },
+        4
+    },
+    {ParameterAclRelationId,    /* PARAMETERACLOID */
+        ParameterAclOidIndexId,
+        1,
+        {
+            Anum_pg_parameter_acl_oid,
+            0,
+            0,
+            0
+        },
+        4
+    },
     {PartitionedRelationId,        /* PARTRELID */
         PartitionedRelidIndexId,
         1,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9e8ab1420d..265008bdc6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -45,6 +45,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_parameter_acl.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
@@ -5713,6 +5714,65 @@ guc_name_compare(const char *namea, const char *nameb)
 }


+/*
+ * Convert a GUC name to the form that should be used in pg_parameter_acl.
+ *
+ * We need to canonicalize entries since, for example, case should not be
+ * significant.  In addition, we apply the map_old_guc_names[] mapping so that
+ * any obsolete names will be converted when stored in a new PG version.
+ * Note however that this function does not verify legality of the name.
+ *
+ * The result is a palloc'd string.
+ */
+char *
+convert_GUC_name_for_parameter_acl(const char *name)
+{
+    char       *result;
+
+    /* Apply old-GUC-name mapping. */
+    for (int i = 0; map_old_guc_names[i] != NULL; i += 2)
+    {
+        if (guc_name_compare(name, map_old_guc_names[i]) == 0)
+        {
+            name = map_old_guc_names[i + 1];
+            break;
+        }
+    }
+
+    /* Apply case-folding that matches guc_name_compare(). */
+    result = pstrdup(name);
+    for (char *ptr = result; *ptr != '\0'; ptr++)
+    {
+        char        ch = *ptr;
+
+        if (ch >= 'A' && ch <= 'Z')
+        {
+            ch += 'a' - 'A';
+            *ptr = ch;
+        }
+    }
+
+    return result;
+}
+
+/*
+ * Check whether we should allow creation of a pg_parameter_acl entry
+ * for the given name.  (This can be applied either before or after
+ * canonicalizing it.)
+ */
+bool
+check_GUC_name_for_parameter_acl(const char *name)
+{
+    /* OK if the GUC exists. */
+    if (find_option(name, false, true, DEBUG1) != NULL)
+        return true;
+    /* Otherwise, it'd better be a valid custom GUC name. */
+    if (valid_custom_variable_name(name))
+        return true;
+    return false;
+}
+
+
 /*
  * Initialize GUC options during program startup.
  *
@@ -7568,6 +7628,17 @@ set_config_option(const char *name, const char *value,
         case PGC_SUSET:
             if (context == PGC_USERSET || context == PGC_BACKEND)
             {
+                /*
+                 * Check whether the current user has been granted privilege
+                 * to set this GUC.
+                 */
+                AclResult    aclresult;
+
+                aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
+                if (aclresult == ACLCHECK_OK)
+                    break;        /* okay */
+
+                /* No granted privilege */
                 ereport(elevel,
                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                          errmsg("permission denied to set parameter \"%s\"",
@@ -8617,11 +8688,6 @@ 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
      */
@@ -8649,6 +8715,29 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
             break;
     }

+    /*
+     * Check permission to run ALTER SYSTEM on the target variable
+     */
+    if (!superuser())
+    {
+        if (resetall)
+            ereport(ERROR,
+                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                     errmsg("permission denied to perform ALTER SYSTEM RESET ALL")));
+        else
+        {
+            AclResult    aclresult;
+
+            aclresult = pg_parameter_aclcheck(name, GetUserId(),
+                                              ACL_ALTER_SYSTEM);
+            if (aclresult != ACLCHECK_OK)
+                ereport(ERROR,
+                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                         errmsg("permission denied to set parameter \"%s\"",
+                                name)));
+        }
+    }
+
     /*
      * Unless it's RESET_ALL, validate the target variable and value
      */
@@ -8760,13 +8849,18 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
     }

     /*
-     * Invoke the post-alter hook for altering this GUC variable.
+     * Invoke the post-alter hook for setting this GUC variable.  GUCs
+     * typically do not have corresponding entries in pg_parameter_acl, so we
+     * call the hook using the name rather than a potentially-non-existent
+     * OID.  Nonetheless, we pass ParameterAclRelationId so that this call
+     * context can be distinguished from others.  (Note that "name" will be
+     * NULL in the RESET ALL case.)
      *
      * We do this here rather than at the end, because ALTER SYSTEM is not
      * transactional.  If the hook aborts our transaction, it will be cleaner
      * to do so before we touch any files.
      */
-    InvokeObjectPostAlterHookArgStr(InvalidOid, name,
+    InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name,
                                     ACL_ALTER_SYSTEM,
                                     altersysstmt->setstmt->kind,
                                     false);
@@ -8943,9 +9037,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
             break;
     }

-    /* Invoke the post-alter hook for setting this GUC variable. */
-    InvokeObjectPostAlterHookArgStr(InvalidOid, stmt->name,
-                                    ACL_SET_VALUE, stmt->kind, false);
+    /* Invoke the post-alter hook for setting this GUC variable, by name. */
+    InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name,
+                                    ACL_SET, stmt->kind, false);
 }

 /*
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6086d57cf3..3e68dfc78f 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, PARAMETER or LARGE OBJECT)
  *    acls: the ACL string fetched from the database
  *    baseacls: the initial ACL string for this object
  *    owner: username of object owner (will be passed through fmtId); can be
@@ -501,6 +501,11 @@ do { \
         CONVERT_PRIV('U', "USAGE");
     else if (strcmp(type, "FOREIGN TABLE") == 0)
         CONVERT_PRIV('r', "SELECT");
+    else if (strcmp(type, "PARAMETER") == 0)
+    {
+        CONVERT_PRIV('s', "SET");
+        CONVERT_PRIV('A', "ALTER SYSTEM");
+    }
     else if (strcmp(type, "LARGE OBJECT") == 0)
     {
         CONVERT_PRIV('r', "SELECT");
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..2dc3362763 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -18,6 +18,7 @@
 #include <time.h>
 #include <unistd.h>

+#include "catalog/pg_authid_d.h"
 #include "common/connect.h"
 #include "common/file_utils.h"
 #include "common/logging.h"
@@ -36,6 +37,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 dropTablespaces(PGconn *conn);
 static void dumpTablespaces(PGconn *conn);
 static void dropDBs(PGconn *conn);
@@ -585,6 +587,10 @@ main(int argc, char *argv[])

             /* Dump role memberships */
             dumpRoleMembership(conn);
+
+            /* Dump role GUC privileges */
+            if (server_version >= 150000 && !skip_acls)
+                dumpRoleGUCPrivs(conn);
         }

         /* Dump tablespaces */
@@ -989,6 +995,65 @@ dumpRoleMembership(PGconn *conn)
 }


+/*
+ * 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)
+{
+    PGresult   *res;
+    int            i;
+
+    /*
+     * Get all parameters that have non-default acls defined.
+     */
+    res = executeQuery(conn, "SELECT parname, "
+                       "pg_catalog.pg_get_userbyid(" CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS parowner, "
+                       "paracl, "
+                       "pg_catalog.acldefault('p', " CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS acldefault "
+                       "FROM pg_catalog.pg_parameter_acl "
+                       "ORDER BY 1");
+
+    if (PQntuples(res) > 0)
+        fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n");
+
+    for (i = 0; i < PQntuples(res); i++)
+    {
+        PQExpBuffer buf = createPQExpBuffer();
+        char       *parname = PQgetvalue(res, i, 0);
+        char       *parowner = PQgetvalue(res, i, 1);
+        char       *paracl = PQgetvalue(res, i, 2);
+        char       *acldefault = PQgetvalue(res, i, 3);
+        char       *fparname;
+
+        /* needed for buildACLCommands() */
+        fparname = pg_strdup(fmtId(parname));
+
+        if (!buildACLCommands(fparname, NULL, NULL, "PARAMETER",
+                              paracl, acldefault,
+                              parowner, "", server_version, buf))
+        {
+            pg_log_error("could not parse ACL list (%s) for parameter \"%s\"",
+                         paracl, parname);
+            PQfinish(conn);
+            exit_nicely(1);
+        }
+
+        fprintf(OPF, "%s", buf->data);
+
+        free(fparname);
+        destroyPQExpBuffer(buf);
+    }
+
+    PQclear(res);
+    fprintf(OPF, "\n\n");
+}
+
+
 /*
  * Drop tablespaces.
  */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 645d46ab75..32d0b4855f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3737,7 +3737,8 @@ psql_completion(const char *text, int start, int end)
  * ALTER DEFAULT PRIVILEGES, so use TailMatches
  */
     /* Complete GRANT/REVOKE with a list of roles and privileges */
-    else if (TailMatches("GRANT|REVOKE"))
+    else if (TailMatches("GRANT|REVOKE") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR"))
     {
         /*
          * With ALTER DEFAULT PRIVILEGES, restrict completion to grantable
@@ -3749,6 +3750,7 @@ psql_completion(const char *text, int start, int end)
                           "EXECUTE", "USAGE", "ALL");
         else
             COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+                                     "GRANT",
                                      "SELECT",
                                      "INSERT",
                                      "UPDATE",
@@ -3761,14 +3763,48 @@ psql_completion(const char *text, int start, int end)
                                      "TEMPORARY",
                                      "EXECUTE",
                                      "USAGE",
+                                     "SET",
+                                     "ALTER SYSTEM",
                                      "ALL");
     }

+    else if (TailMatches("REVOKE", "GRANT"))
+        COMPLETE_WITH("OPTION FOR");
+    else if (TailMatches("REVOKE", "GRANT", "OPTION"))
+        COMPLETE_WITH("FOR");
+
+    else if (TailMatches("GRANT|REVOKE", "ALTER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER"))
+        COMPLETE_WITH("SYSTEM");
+
+    else if (TailMatches("GRANT|REVOKE", "SET") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "SET") ||
+             TailMatches("GRANT|REVOKE", "ALTER", "SYSTEM") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", "ALTER", "SYSTEM"))
+        COMPLETE_WITH("ON PARAMETER");
+
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "PARAMETER") ||
+             TailMatches("GRANT|REVOKE", MatchAny, MatchAny, "ON", "PARAMETER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER"))
+        COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+
+    else if (TailMatches("GRANT", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("GRANT", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny))
+        COMPLETE_WITH("TO");
+
+    else if (TailMatches("REVOKE", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "PARAMETER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, MatchAny, "ON", "PARAMETER", MatchAny))
+        COMPLETE_WITH("FROM");
+
     /*
      * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
      * TO/FROM
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny))
     {
         if
(TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
             COMPLETE_WITH("ON");
@@ -3785,7 +3821,8 @@ psql_completion(const char *text, int start, int end)
      * here will only work if the privilege list contains exactly one
      * privilege.
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON"))
     {
         /*
          * With ALTER DEFAULT PRIVILEGES, restrict completion to the kinds of
@@ -3807,6 +3844,7 @@ psql_completion(const char *text, int start, int end)
                                             "FUNCTION",
                                             "LANGUAGE",
                                             "LARGE OBJECT",
+                                            "PARAMETER",
                                             "PROCEDURE",
                                             "ROUTINE",
                                             "SCHEMA",
@@ -3815,13 +3853,15 @@ psql_completion(const char *text, int start, int end)
                                             "TABLESPACE",
                                             "TYPE");
     }
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL"))
         COMPLETE_WITH("FUNCTIONS IN SCHEMA",
                       "PROCEDURES IN SCHEMA",
                       "ROUTINES IN SCHEMA",
                       "SEQUENCES IN SCHEMA",
                       "TABLES IN SCHEMA");
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN") ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN"))
         COMPLETE_WITH("DATA WRAPPER", "SERVER");

     /*
@@ -3830,7 +3870,8 @@ psql_completion(const char *text, int start, int end)
      *
      * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
      */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", MatchAny))
     {
         if (TailMatches("DATABASE"))
             COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -3868,6 +3909,22 @@ psql_completion(const char *text, int start, int end)
              (HeadMatches("REVOKE") && TailMatches("FROM")))
         COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
                                  Keywords_for_list_of_grant_roles);
+
+    /*
+     * Offer grant options after that.
+     */
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny))
+        COMPLETE_WITH("WITH ADMIN OPTION",
+                      "WITH GRANT OPTION",
+                      "GRANTED BY");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH"))
+        COMPLETE_WITH("ADMIN OPTION",
+                      "GRANT OPTION");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION"))
+        COMPLETE_WITH("GRANTED BY");
+    else if (HeadMatches("GRANT") && TailMatches("TO", MatchAny, "WITH", MatchAny, "OPTION", "GRANTED", "BY"))
+        COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+                                 Keywords_for_list_of_grant_roles);
     /* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
     else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM"))
         COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
@@ -3879,7 +3936,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("FROM");

     /* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA",
MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
@@ -3888,7 +3946,8 @@ psql_completion(const char *text, int start, int end)
     }

     /* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
@@ -3897,7 +3956,8 @@ psql_completion(const char *text, int start, int end)
     }

     /* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+    else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny) ||
+             TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
     {
         if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
             COMPLETE_WITH("TO");
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..d027075a4c 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -120,6 +120,7 @@ typedef enum ObjectClass
     OCLASS_DEFACL,                /* pg_default_acl */
     OCLASS_EXTENSION,            /* pg_extension */
     OCLASS_EVENT_TRIGGER,        /* pg_event_trigger */
+    OCLASS_PARAMETER_ACL,        /* pg_parameter_acl */
     OCLASS_POLICY,                /* pg_policy */
     OCLASS_PUBLICATION,            /* pg_publication */
     OCLASS_PUBLICATION_NAMESPACE,    /* pg_publication_namespace */
diff --git a/src/include/catalog/objectaccess.h b/src/include/catalog/objectaccess.h
index 4d54ae2a7d..ac6adcb730 100644
--- a/src/include/catalog/objectaccess.h
+++ b/src/include/catalog/objectaccess.h
@@ -239,7 +239,7 @@ extern void RunFunctionExecuteHookStr(const char *objectStr);
             RunObjectTruncateHookStr(objectName);                        \
     } while(0)

-#define InvokeObjectPostAlterHookStr(className,objectName,subId)            \
+#define InvokeObjectPostAlterHookStr(classId,objectName,subId)            \
     InvokeObjectPostAlterHookArgStr((classId),(objectName),(subId),        \
                                  InvalidOid,false)
 #define InvokeObjectPostAlterHookArgStr(classId,objectName,subId,        \
diff --git a/src/include/catalog/pg_parameter_acl.h b/src/include/catalog/pg_parameter_acl.h
new file mode 100644
index 0000000000..8316391e51
--- /dev/null
+++ b/src/include/catalog/pg_parameter_acl.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_parameter_acl.h
+ *      definition of the "configuration parameter ACL" system catalog
+ *      (pg_parameter_acl).
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_parameter_acl.h
+ *
+ * NOTES
+ *      The Catalog.pm module reads this file and derives schema
+ *      information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARAMETER_ACL_H
+#define PG_PARAMETER_ACL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_parameter_acl_d.h"
+
+/* ----------------
+ *        pg_parameter_acl definition.  cpp turns this into
+ *        typedef struct FormData_pg_parameter_acl
+ * ----------------
+ */
+CATALOG(pg_parameter_acl,8924,ParameterAclRelationId) BKI_SHARED_RELATION
+{
+    Oid            oid;            /* oid */
+
+#ifdef CATALOG_VARLEN            /* variable-length fields start here */
+    /* name of parameter */
+    text        parname BKI_FORCE_NOT_NULL;
+
+    /* access permissions */
+    aclitem        paracl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_parameter_acl;
+
+
+/* ----------------
+ *        Form_pg_parameter_acl corresponds to a pointer to a tuple with
+ *        the format of pg_parameter_acl relation.
+ * ----------------
+ */
+typedef FormData_pg_parameter_acl *Form_pg_parameter_acl;
+
+DECLARE_TOAST(pg_parameter_acl, 8925, 8926);
+#define PgParameterAclToastTable 8925
+#define PgParameterAclToastIndex 8926
+
+DECLARE_UNIQUE_INDEX(pg_parameter_acl_parname_index, 8927, ParameterAclParnameIndexId, on pg_parameter_acl using
btree(parnametext_ops)); 
+DECLARE_UNIQUE_INDEX_PKEY(pg_parameter_acl_oid_index, 8928, ParameterAclOidIndexId, on pg_parameter_acl using
btree(oidoid_ops)); 
+
+
+extern Oid    ParameterAclLookup(const char *parameter, bool missing_ok);
+extern Oid    ParameterAclCreate(const char *parameter);
+
+#endif                            /* PG_PARAMETER_ACL_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 25304430f4..4d285ece8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7213,6 +7213,22 @@
   proname => 'has_type_privilege', provolatile => 's', prorettype => 'bool',
   proargtypes => 'oid text', prosrc => 'has_type_privilege_id' },

+{ oid => '8050',
+  descr => 'user privilege on parameter by username, parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'name text text',
+  prosrc => 'has_parameter_privilege_name_name' },
+{ oid => '8051',
+  descr => 'user privilege on parameter by user oid, parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'oid text text',
+  prosrc => 'has_parameter_privilege_id_name' },
+{ oid => '8052',
+  descr => 'current user privilege on parameter by parameter name',
+  proname => 'has_parameter_privilege', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'text text',
+  prosrc => 'has_parameter_privilege_name' },
+
 { oid => '2705', descr => 'user privilege on role by username, role name',
   proname => 'pg_has_role', provolatile => 's', prorettype => 'bool',
   proargtypes => 'name name text', prosrc => 'pg_has_role_name_name' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4a2ca81f3c..8998d34560 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,8 +92,8 @@ 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 ACL_SET_VALUE    (1<<12) /* for configuration parameters */
-#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#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 */
@@ -2162,6 +2162,7 @@ typedef enum ObjectType
     OBJECT_OPCLASS,
     OBJECT_OPERATOR,
     OBJECT_OPFAMILY,
+    OBJECT_PARAMETER_ACL,
     OBJECT_POLICY,
     OBJECT_PROCEDURE,
     OBJECT_PUBLICATION,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 0caa7310f2..8a2ab405a2 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -329,6 +329,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/utils/acl.h b/src/include/utils/acl.h
index 91ce3d8e9c..48f7d72add 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,7 +146,7 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR            'C'
 #define ACL_CREATE_TEMP_CHR        'T'
 #define ACL_CONNECT_CHR            'c'
-#define ACL_SET_VALUE_CHR        's'
+#define ACL_SET_CHR                's'
 #define ACL_ALTER_SYSTEM_CHR    'A'

 /* string holding all privilege code chars, in order by bitmask position */
@@ -164,6 +164,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_FUNCTION        (ACL_EXECUTE)
 #define ACL_ALL_RIGHTS_LANGUAGE        (ACL_USAGE)
 #define ACL_ALL_RIGHTS_LARGEOBJECT    (ACL_SELECT|ACL_UPDATE)
+#define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM)
 #define ACL_ALL_RIGHTS_SCHEMA        (ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE    (ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE            (ACL_USAGE)
@@ -245,6 +246,10 @@ 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_parameter_aclmask(const char *name, Oid roleid,
+                                    AclMode mask, AclMaskHow how);
+extern AclMode pg_parameter_acl_aclmask(Oid acl_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,
@@ -273,6 +278,10 @@ 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_parameter_aclcheck(const char *name, Oid roleid,
+                                       AclMode mode);
+extern AclResult pg_parameter_acl_aclcheck(Oid acl_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 ea774968f0..3446334e90 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -364,6 +364,8 @@ extern const char *GetConfigOption(const char *name, bool missing_ok,
 extern const char *GetConfigOptionResetString(const char *name);
 extern int    GetConfigOptionFlags(const char *name, bool missing_ok);
 extern void ProcessConfigFile(GucContext context);
+extern char *convert_GUC_name_for_parameter_acl(const char *name);
+extern bool check_GUC_name_for_parameter_acl(const char *name);
 extern void InitializeGUCOptions(void);
 extern bool SelectConfigFiles(const char *userDoption, const char *progname);
 extern void ResetAllOptions(void);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 9c1a76e8bb..4463ea66be 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,8 @@ enum SysCacheIdentifier
     OPEROID,
     OPFAMILYAMNAMENSP,
     OPFAMILYOID,
+    PARAMETERACLNAME,
+    PARAMETERACLOID,
     PARTRELID,
     PROCNAMEARGSNSP,
     PROCOID,
diff --git a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
index 45ff276f7e..39b274b8fa 100644
--- a/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
+++ b/src/test/modules/test_oat_hooks/expected/test_oat_hooks.out
@@ -1,36 +1,84 @@
+-- Creating privileges on a placeholder GUC should create entries in the
+-- pg_parameter_acl catalog which conservatively grant no privileges to public.
+CREATE ROLE regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe;
 -- SET commands fire both the ProcessUtility_hook and the
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
--- should thereafter see the audit messages
+-- should thereafter see the audit messages.
 LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.audit]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.audit]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.audit]
 NOTICE:  in process utility: superuser finished set
+-- Creating privileges on an existent custom GUC should create precisely the
+-- right privileges, not overly conservative ones.
+GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Granting multiple privileges on a parameter should be reported correctly to
+-- the OAT hook, but beware that WITH GRANT OPTION is not represented.
+GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check when the hooks fire relative to dependency based abort of a drop
+DROP ROLE regress_role_joe;
+NOTICE:  in process utility: superuser attempting DropRoleStmt
+NOTICE:  in object access: superuser attempting drop (subId=0x0) []
+NOTICE:  in object access: superuser finished drop (subId=0x0) []
+ERROR:  role "regress_role_joe" cannot be dropped because some objects depend on it
+DETAIL:  privileges for parameter test_oat_hooks.user_var1
+privileges for parameter test_oat_hooks.super_var1
+privileges for parameter test_oat_hooks.user_var2
+privileges for parameter test_oat_hooks.super_var2
+privileges for parameter none.such
+privileges for parameter another.bogus
+-- Check the behavior of the hooks relative to do-nothing grants and revokes
+GRANT SET ON PARAMETER work_mem TO PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check the behavior of the hooks relative to unrecognized parameters
+GRANT ALL ON PARAMETER "none.such" TO PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
+-- Check relative to an operation that causes the catalog entry to be deleted
+REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC;
+NOTICE:  in process utility: superuser attempting GrantStmt
+NOTICE:  in process utility: superuser finished GrantStmt
 -- Create objects for use in the test
 CREATE USER regress_test_user;
 NOTICE:  in process utility: superuser attempting CreateRoleStmt
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CreateRoleStmt
 CREATE TABLE regress_test_table (t text);
 NOTICE:  in process utility: superuser attempting CreateStmt
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: CREATE TABLE regress_test_table (t text);
                      ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: CREATE TABLE regress_test_table (t text);
                      ^
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
-NOTICE:  in object access: superuser attempting create (subId=0) [internal]
-NOTICE:  in object access: superuser finished create (subId=0) [internal]
-NOTICE:  in object access: superuser attempting create (subId=0) [internal]
-NOTICE:  in object access: superuser finished create (subId=0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0x0) [internal]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [internal]
+NOTICE:  in object access: superuser finished create (subId=0x0) [internal]
 NOTICE:  in process utility: superuser finished CreateStmt
 GRANT SELECT ON Table regress_test_table TO public;
 NOTICE:  in process utility: superuser attempting GrantStmt
@@ -39,8 +87,8 @@ CREATE FUNCTION regress_test_func (t text) RETURNS text AS $$
     SELECT $1;
 $$ LANGUAGE sql;
 NOTICE:  in process utility: superuser attempting CreateFunctionStmt
-NOTICE:  in object access: superuser attempting create (subId=0) [explicit]
-NOTICE:  in object access: superuser finished create (subId=0) [explicit]
+NOTICE:  in object access: superuser attempting create (subId=0x0) [explicit]
+NOTICE:  in object access: superuser finished create (subId=0x0) [explicit]
 NOTICE:  in process utility: superuser finished CreateFunctionStmt
 GRANT EXECUTE ON FUNCTION regress_test_func (text) TO public;
 NOTICE:  in process utility: superuser attempting GrantStmt
@@ -63,35 +111,35 @@ NOTICE:  in executor check perms: superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 -- Do those same things as non-superuser
 SET SESSION AUTHORIZATION regress_test_user;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: non-superuser finished set
 SELECT * FROM regress_test_table;
-NOTICE:  in object access: non-superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: non-superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in object access: non-superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: non-superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
 NOTICE:  in executor check perms: non-superuser attempting execute
@@ -110,61 +158,89 @@ NOTICE:  in executor check perms: non-superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: non-superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: non-superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: non-superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: non-superuser attempting alter system
-ERROR:  must be superuser to execute ALTER SYSTEM command
+ERROR:  permission denied to set parameter "work_mem"
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: non-superuser attempting alter system
-ERROR:  must be superuser to execute ALTER SYSTEM command
+ERROR:  permission denied to set parameter "work_mem"
+SET test_oat_hooks.user_var1 = true;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var1]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var1]
+NOTICE:  in process utility: non-superuser finished set
+SET test_oat_hooks.super_var1 = true;
+NOTICE:  in process utility: non-superuser attempting set
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var1"
+ALTER SYSTEM SET test_oat_hooks.user_var1 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.user_var1"
+ALTER SYSTEM SET test_oat_hooks.super_var1 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var1"
+SET test_oat_hooks.user_var2 = true;
+NOTICE:  in process utility: non-superuser attempting set
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [test_oat_hooks.user_var2]
+NOTICE:  in object_access_hook_str: non-superuser finished alter (subId=0x1000, set) [test_oat_hooks.user_var2]
+NOTICE:  in process utility: non-superuser finished set
+SET test_oat_hooks.super_var2 = true;
+NOTICE:  in process utility: non-superuser attempting set
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var2"
+ALTER SYSTEM SET test_oat_hooks.user_var2 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.user_var2"
+ALTER SYSTEM SET test_oat_hooks.super_var2 = true;
+NOTICE:  in process utility: non-superuser attempting alter system
+ERROR:  permission denied to set parameter "test_oat_hooks.super_var2"
 RESET SESSION AUTHORIZATION;
 NOTICE:  in process utility: non-superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: superuser finished set
 -- Turn off non-superuser permissions
 SET test_oat_hooks.deny_set_variable = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_set_variable]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_set_variable]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_alter_system = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_alter_system]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_alter_system]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_object_access = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_object_access]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_object_access]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_object_access]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_exec_perms = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_exec_perms]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [test_oat_hooks.deny_exec_perms]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.deny_utility_commands = true;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [test_oat_hooks.deny_utility_commands]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set)
[test_oat_hooks.deny_utility_commands]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set)
[test_oat_hooks.deny_utility_commands]
 NOTICE:  in process utility: superuser finished set
 -- Try again as non-superuser with permissions denied
 SET SESSION AUTHORIZATION regress_test_user;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: non-superuser attempting alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: non-superuser attempting alter (subId=0x1000, set) [session_authorization]
 ERROR:  permission denied: set session_authorization
 SELECT * FROM regress_test_table;
-NOTICE:  in object access: superuser attempting namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser attempting namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
-NOTICE:  in object access: superuser finished namespace search (subId=0) [no report on violation, allowed]
+NOTICE:  in object access: superuser finished namespace search (subId=0x0) [no report on violation, allowed]
 LINE 1: SELECT * FROM regress_test_table;
                       ^
 NOTICE:  in executor check perms: superuser attempting execute
@@ -183,28 +259,43 @@ NOTICE:  in executor check perms: superuser finished execute

 SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 RESET work_mem;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [work_mem]
 NOTICE:  in process utility: superuser finished set
 ALTER SYSTEM SET work_mem = 8192;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
 ALTER SYSTEM RESET work_mem;
 NOTICE:  in process utility: superuser attempting alter system
-NOTICE:  in object_access_hook_str: superuser attempting alter (alter system set) [work_mem]
-NOTICE:  in object_access_hook_str: superuser finished alter (alter system set) [work_mem]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x2000, alter system) [work_mem]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x2000, alter system) [work_mem]
 NOTICE:  in process utility: superuser finished alter system
+-- Clean up
 RESET SESSION AUTHORIZATION;
 NOTICE:  in process utility: superuser attempting set
-NOTICE:  in object_access_hook_str: superuser attempting alter (set) [session_authorization]
-NOTICE:  in object_access_hook_str: superuser finished alter (set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser attempting alter (subId=0x1000, set) [session_authorization]
+NOTICE:  in object_access_hook_str: superuser finished alter (subId=0x1000, set) [session_authorization]
 NOTICE:  in process utility: superuser finished set
 SET test_oat_hooks.audit = false;
 NOTICE:  in process utility: superuser attempting set
+DROP ROLE regress_role_joe;  -- fails
+ERROR:  role "regress_role_joe" cannot be dropped because some objects depend on it
+DETAIL:  privileges for parameter test_oat_hooks.user_var1
+privileges for parameter test_oat_hooks.super_var1
+privileges for parameter test_oat_hooks.user_var2
+privileges for parameter test_oat_hooks.super_var2
+privileges for parameter none.such
+privileges for parameter another.bogus
+REVOKE ALL PRIVILEGES ON PARAMETER
+    none.such, another.bogus,
+    test_oat_hooks.user_var1, test_oat_hooks.super_var1,
+    test_oat_hooks.user_var2, test_oat_hooks.super_var2
+    FROM regress_role_joe;
+DROP ROLE regress_role_joe;
diff --git a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
index 09e61864ee..8b6d5373aa 100644
--- a/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
+++ b/src/test/modules/test_oat_hooks/sql/test_oat_hooks.sql
@@ -1,10 +1,39 @@
+-- Creating privileges on a placeholder GUC should create entries in the
+-- pg_parameter_acl catalog which conservatively grant no privileges to public.
+CREATE ROLE regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.user_var1 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var1 TO regress_role_joe;
+
 -- SET commands fire both the ProcessUtility_hook and the
 -- object_access_hook_str.  Since the auditing GUC starts out false, we miss the
 -- initial "attempting" audit message from the ProcessUtility_hook, but we
--- should thereafter see the audit messages
+-- should thereafter see the audit messages.
 LOAD 'test_oat_hooks';
 SET test_oat_hooks.audit = true;

+-- Creating privileges on an existent custom GUC should create precisely the
+-- right privileges, not overly conservative ones.
+GRANT SET ON PARAMETER test_oat_hooks.user_var2 TO regress_role_joe;
+GRANT SET ON PARAMETER test_oat_hooks.super_var2 TO regress_role_joe;
+
+-- Granting multiple privileges on a parameter should be reported correctly to
+-- the OAT hook, but beware that WITH GRANT OPTION is not represented.
+GRANT SET, ALTER SYSTEM ON PARAMETER none.such TO regress_role_joe;
+GRANT SET, ALTER SYSTEM ON PARAMETER another.bogus TO regress_role_joe WITH GRANT OPTION;
+
+-- Check when the hooks fire relative to dependency based abort of a drop
+DROP ROLE regress_role_joe;
+
+-- Check the behavior of the hooks relative to do-nothing grants and revokes
+GRANT SET ON PARAMETER work_mem TO PUBLIC;
+REVOKE ALTER SYSTEM ON PARAMETER work_mem FROM PUBLIC;
+
+-- Check the behavior of the hooks relative to unrecognized parameters
+GRANT ALL ON PARAMETER "none.such" TO PUBLIC;
+
+-- Check relative to an operation that causes the catalog entry to be deleted
+REVOKE ALL ON PARAMETER "none.such" FROM PUBLIC;
+
 -- Create objects for use in the test
 CREATE USER regress_test_user;
 CREATE TABLE regress_test_table (t text);
@@ -30,6 +59,16 @@ SET work_mem = 8192;
 RESET work_mem;
 ALTER SYSTEM SET work_mem = 8192;
 ALTER SYSTEM RESET work_mem;
+
+SET test_oat_hooks.user_var1 = true;
+SET test_oat_hooks.super_var1 = true;
+ALTER SYSTEM SET test_oat_hooks.user_var1 = true;
+ALTER SYSTEM SET test_oat_hooks.super_var1 = true;
+SET test_oat_hooks.user_var2 = true;
+SET test_oat_hooks.super_var2 = true;
+ALTER SYSTEM SET test_oat_hooks.user_var2 = true;
+ALTER SYSTEM SET test_oat_hooks.super_var2 = true;
+
 RESET SESSION AUTHORIZATION;

 -- Turn off non-superuser permissions
@@ -48,6 +87,14 @@ RESET work_mem;
 ALTER SYSTEM SET work_mem = 8192;
 ALTER SYSTEM RESET work_mem;

+-- Clean up
 RESET SESSION AUTHORIZATION;

 SET test_oat_hooks.audit = false;
+DROP ROLE regress_role_joe;  -- fails
+REVOKE ALL PRIVILEGES ON PARAMETER
+    none.such, another.bogus,
+    test_oat_hooks.user_var1, test_oat_hooks.super_var1,
+    test_oat_hooks.user_var2, test_oat_hooks.super_var2
+    FROM regress_role_joe;
+DROP ROLE regress_role_joe;
diff --git a/src/test/modules/test_oat_hooks/test_oat_hooks.c b/src/test/modules/test_oat_hooks/test_oat_hooks.c
index eb7564ce22..551da5d498 100644
--- a/src/test/modules/test_oat_hooks/test_oat_hooks.c
+++ b/src/test/modules/test_oat_hooks/test_oat_hooks.c
@@ -34,6 +34,15 @@ static bool REGRESS_deny_exec_perms = false;
 static bool REGRESS_deny_utility_commands = false;
 static bool REGRESS_audit = false;

+/*
+ * GUCs for testing privileges on USERSET and SUSET variables,
+ * with and without privileges granted prior to module load.
+ */
+static bool REGRESS_userset_variable1 = false;
+static bool REGRESS_userset_variable2 = false;
+static bool REGRESS_suset_variable1 = false;
+static bool REGRESS_suset_variable2 = false;
+
 /* Saved hook values in case of unload */
 static object_access_hook_type next_object_access_hook = NULL;
 static object_access_hook_type_str next_object_access_hook_str = NULL;
@@ -153,6 +162,56 @@ _PG_init(void)
                              NULL,
                              NULL);

+    /*
+     * test_oat_hooks.user_var{1,2} = (on|off)
+     */
+    DefineCustomBoolVariable("test_oat_hooks.user_var1",
+                             "Dummy parameter settable by public",
+                             NULL,
+                             ®RESS_userset_variable1,
+                             false,
+                             PGC_USERSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    DefineCustomBoolVariable("test_oat_hooks.user_var2",
+                             "Dummy parameter settable by public",
+                             NULL,
+                             ®RESS_userset_variable2,
+                             false,
+                             PGC_USERSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    /*
+     * test_oat_hooks.super_var{1,2} = (on|off)
+     */
+    DefineCustomBoolVariable("test_oat_hooks.super_var1",
+                             "Dummy parameter settable by superuser",
+                             NULL,
+                             ®RESS_suset_variable1,
+                             false,
+                             PGC_SUSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
+    DefineCustomBoolVariable("test_oat_hooks.super_var2",
+                             "Dummy parameter settable by superuser",
+                             NULL,
+                             ®RESS_suset_variable2,
+                             false,
+                             PGC_SUSET,
+                             GUC_NOT_IN_SAMPLE,
+                             NULL,
+                             NULL,
+                             NULL);
+
     MarkGUCPrefixReserved("test_oat_hooks");

     /* Object access hook */
@@ -250,7 +309,14 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char
     switch (access)
     {
         case OAT_POST_ALTER:
-            if (subId & ACL_SET_VALUE)
+            if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
+            {
+                if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                             errmsg("permission denied: all privileges %s", objName)));
+            }
+            else if (subId & ACL_SET)
             {
                 if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
                     ereport(ERROR,
@@ -265,7 +331,7 @@ REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char
                              errmsg("permission denied: alter system set %s", objName)));
             }
             else
-                elog(ERROR, "Unknown SettingAclRelationId subId: %d", subId);
+                elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
             break;
         default:
             break;
@@ -860,12 +926,14 @@ accesstype_to_string(ObjectAccessType access, int subId)
             type = "UNRECOGNIZED ObjectAccessType";
     }

-    if (subId & ACL_SET_VALUE)
-        return psprintf("%s (set)", type);
+    if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
+        return psprintf("%s (subId=0x%x, all privileges)", type, subId);
+    if (subId & ACL_SET)
+        return psprintf("%s (subId=0x%x, set)", type, subId);
     if (subId & ACL_ALTER_SYSTEM)
-        return psprintf("%s (alter system set)", type);
+        return psprintf("%s (subId=0x%x, alter system)", type, subId);

-    return  psprintf("%s (subId=%d)", type, subId);
+    return psprintf("%s (subId=0x%x)", type, subId);
 }

 static char *
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 84a35590b7..d842f934a3 100644
--- a/src/test/modules/test_pg_dump/t/001_base.pl
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -316,6 +316,38 @@ my %tests = (
         like         => { pg_dumpall_globals => 1, },
     },

+    'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role'
+      => {
+        create_order => 2,
+        create_sql =>
+          'GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;',
+        regexp =>
+
+          qr/^GRANT ALTER SYSTEM ON PARAMETER full_page_writes TO regress_dump_test_role;/m,
+        like => { pg_dumpall_globals => 1, },
+      },
+
+    'GRANT ALL ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION'
+      => {
+        create_order => 2,
+        create_sql =>
+          'GRANT SET, ALTER SYSTEM ON PARAMETER Custom.Knob TO regress_dump_test_role WITH GRANT OPTION;',
+        regexp =>
+          # "set" plus "alter system" is "all" privileges on parameters
+          qr/^GRANT ALL ON PARAMETER "custom.knob" TO regress_dump_test_role WITH GRANT OPTION;/m,
+        like => { pg_dumpall_globals => 1, },
+      },
+
+    'GRANT ALL ON PARAMETER DateStyle TO regress_dump_test_role' => {
+        create_order => 2,
+        create_sql =>
+          'GRANT ALL ON PARAMETER "DateStyle" TO regress_dump_test_role WITH GRANT OPTION; REVOKE GRANT OPTION FOR ALL
ONPARAMETER DateStyle FROM regress_dump_test_role;', 
+        regexp =>
+          # The revoke simplifies the ultimate grant so as to not include "with grant option"
+          qr/^GRANT ALL ON PARAMETER datestyle TO regress_dump_test_role;/m,
+        like => { pg_dumpall_globals => 1, },
+    },
+
     'CREATE SCHEMA public' => {
         regexp => qr/^CREATE SCHEMA public;/m,
         like   => {
diff --git a/src/test/modules/unsafe_tests/Makefile b/src/test/modules/unsafe_tests/Makefile
index 3ecf5fcfc5..df58273688 100644
--- a/src/test/modules/unsafe_tests/Makefile
+++ b/src/test/modules/unsafe_tests/Makefile
@@ -1,6 +1,6 @@
 # src/test/modules/unsafe_tests/Makefile

-REGRESS = rolenames alter_system_table
+REGRESS = rolenames alter_system_table guc_privs

 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/src/test/modules/unsafe_tests/expected/guc_privs.out
b/src/test/modules/unsafe_tests/expected/guc_privs.out
new file mode 100644
index 0000000000..f62807fd05
--- /dev/null
+++ b/src/test/modules/unsafe_tests/expected/guc_privs.out
@@ -0,0 +1,525 @@
+--
+-- Tests for privileges on GUCs.
+-- This is unsafe because changes will affect other databases in the cluster.
+--
+-- Test with a superuser role.
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Revoke privileges not yet granted
+REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin;
+REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+-- Check the new role does not yet have privileges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Check inappropriate and nonsense privilege types
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+-- Revoke, grant, and revoke again a SUSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Revoke, grant, and revoke again a USERSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Revoke privileges from a non-existent custom GUC.  This should not create
+-- entries in the catalog.
+REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+(0 rows)
+
+-- Grant and then revoke privileges on the non-existent custom GUC.  Check that
+-- a do-nothing entry is not left in the catalogs after the revoke.
+GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+        1
+(1 row)
+
+REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+ ?column?
+----------
+(0 rows)
+
+-- Can't grant on a non-existent core GUC.
+GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin;  -- fail
+ERROR:  invalid parameter name "no_such_guc"
+-- Initially there are no privileges and no catalog entry for this GUC.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+(0 rows)
+
+-- GRANT SET creates an entry:
+GRANT SET ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- Now grant ALTER SYSTEM:
+GRANT ALL ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- REVOKE ALTER SYSTEM brings us back to just the SET privilege:
+REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+        1
+(1 row)
+
+-- And this should remove the entry altogether:
+REVOKE SET ON PARAMETER enable_material FROM PUBLIC;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+ ?column?
+----------
+(0 rows)
+
+-- Grant privileges on parameters to the new non-superuser role
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check the new role now has privilges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH
GRANTOPTION'); 
+ has_parameter_privilege
+-------------------------
+ f
+(1 row)
+
+-- Check again the inappropriate and nonsense privilege types.  The prior
+-- similar check was performed before any entry for work_mem existed.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+ERROR:  unrecognized privilege type: "WHATEVER WITH GRANT OPTION"
+-- Check other function signatures
+SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                              'max_stack_depth',
+                              'SET');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+SELECT has_parameter_privilege('hash_mem_multiplier', 'set');
+ has_parameter_privilege
+-------------------------
+ t
+(1 row)
+
+-- Check object identity functions
+SELECT pg_describe_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+ pg_describe_object
+--------------------
+ parameter work_mem
+(1 row)
+
+SELECT pg_identify_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+      pg_identify_object
+------------------------------
+ ("parameter ACL",,,work_mem)
+(1 row)
+
+SELECT pg_identify_object_as_address(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+  pg_identify_object_as_address
+---------------------------------
+ ("parameter ACL",{work_mem},{})
+(1 row)
+
+SELECT classid::regclass,
+       (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname,
+       objsubid
+FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
+     classid      | parname  | objsubid
+------------------+----------+----------
+ pg_parameter_acl | work_mem |        0
+(1 row)
+
+-- Perform some operations as user 'regress_host_resource_admin'
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+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
+SELECT set_config ('jit_provider', 'llvmjit', true); -- 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
+RESET TimeZone;  -- ok
+SET max_stack_depth = 2048;  -- ok, privileges have been granted
+RESET max_stack_depth;  -- ok, privileges have been granted
+ALTER SYSTEM SET max_stack_depth = 2048;  -- ok, privileges have been granted
+ALTER SYSTEM RESET max_stack_depth;  -- ok, privileges have been granted
+SET lc_messages = 'C';  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+RESET lc_messages;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM SET lc_messages = 'C';  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM RESET lc_messages;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+SELECT set_config ('temp_buffers', '8192', false); -- ok
+ set_config
+------------
+ 64MB
+(1 row)
+
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+ALTER SYSTEM RESET ALL;  -- fail, insufficient privileges
+ERROR:  permission denied to perform ALTER SYSTEM RESET ALL
+-- Check dropping/revoking behavior
+SET SESSION AUTHORIZATION regress_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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"
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+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"
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Check that "reassign owned" doesn't affect privileges
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_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 parameter autovacuum_work_mem
+privileges for parameter hash_mem_multiplier
+privileges for parameter max_stack_depth
+privileges for parameter shared_buffers
+privileges for parameter temp_file_limit
+privileges for parameter work_mem
+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
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin; -- ok
diff --git a/src/test/modules/unsafe_tests/sql/guc_privs.sql b/src/test/modules/unsafe_tests/sql/guc_privs.sql
new file mode 100644
index 0000000000..b38622803d
--- /dev/null
+++ b/src/test/modules/unsafe_tests/sql/guc_privs.sql
@@ -0,0 +1,236 @@
+--
+-- Tests for privileges on GUCs.
+-- This is unsafe because changes will affect other databases in the cluster.
+--
+
+-- Test with a superuser role.
+CREATE ROLE regress_admin SUPERUSER;
+
+-- Perform 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 = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- 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
+
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Revoke privileges not yet granted
+REVOKE SET, ALTER SYSTEM ON PARAMETER work_mem FROM regress_host_resource_admin;
+REVOKE SET, ALTER SYSTEM ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+-- Check the new role does not yet have privileges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+-- Check inappropriate and nonsense privilege types
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+-- Revoke, grant, and revoke again a SUSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+GRANT SET ON PARAMETER zero_damaged_pages TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER zero_damaged_pages FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'zero_damaged_pages', 'ALTER SYSTEM');
+-- Revoke, grant, and revoke again a USERSET parameter not yet granted
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+GRANT SET ON PARAMETER work_mem TO regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+REVOKE SET ON PARAMETER work_mem FROM regress_host_resource_admin;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+
+-- Revoke privileges from a non-existent custom GUC.  This should not create
+-- entries in the catalog.
+REVOKE ALL ON PARAMETER "none.such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+-- Grant and then revoke privileges on the non-existent custom GUC.  Check that
+-- a do-nothing entry is not left in the catalogs after the revoke.
+GRANT ALL ON PARAMETER none.such TO regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+REVOKE ALL ON PARAMETER "None.Such" FROM regress_host_resource_admin;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'none.such';
+-- Can't grant on a non-existent core GUC.
+GRANT ALL ON PARAMETER no_such_guc TO regress_host_resource_admin;  -- fail
+
+-- Initially there are no privileges and no catalog entry for this GUC.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- GRANT SET creates an entry:
+GRANT SET ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- Now grant ALTER SYSTEM:
+GRANT ALL ON PARAMETER enable_material TO PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- REVOKE ALTER SYSTEM brings us back to just the SET privilege:
+REVOKE ALTER SYSTEM ON PARAMETER enable_material FROM PUBLIC;
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'enable_material', 'SET, ALTER SYSTEM');
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+-- And this should remove the entry altogether:
+REVOKE SET ON PARAMETER enable_material FROM PUBLIC;
+SELECT 1 FROM pg_parameter_acl WHERE parname = 'enable_material';
+
+-- Grant privileges on parameters to the new non-superuser role
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check the new role now has privilges on parameters
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET, ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SET WITH GRANT OPTION, ALTER SYSTEM WITH
GRANTOPTION'); 
+-- Check again the inappropriate and nonsense privilege types.  The prior
+-- similar check was performed before any entry for work_mem existed.
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+SELECT has_parameter_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+
+-- Check other function signatures
+SELECT has_parameter_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+                              'max_stack_depth',
+                              'SET');
+SELECT has_parameter_privilege('hash_mem_multiplier', 'set');
+
+-- Check object identity functions
+SELECT pg_describe_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT pg_identify_object(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT pg_identify_object_as_address(tableoid, oid, 0)
+FROM pg_parameter_acl WHERE parname = 'work_mem';
+SELECT classid::regclass,
+       (SELECT parname FROM pg_parameter_acl WHERE oid = goa.objid) AS parname,
+       objsubid
+FROM pg_get_object_address('parameter ACL', '{work_mem}', '{}') goa;
+
+-- Perform some operations as user 'regress_host_resource_admin'
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+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
+SELECT set_config ('jit_provider', 'llvmjit', true); -- 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
+RESET TimeZone;  -- ok
+SET max_stack_depth = 2048;  -- ok, privileges have been granted
+RESET max_stack_depth;  -- ok, privileges have been granted
+ALTER SYSTEM SET max_stack_depth = 2048;  -- ok, privileges have been granted
+ALTER SYSTEM RESET max_stack_depth;  -- ok, privileges have been granted
+SET lc_messages = 'C';  -- fail, insufficient privileges
+RESET lc_messages;  -- fail, insufficient privileges
+ALTER SYSTEM SET lc_messages = 'C';  -- fail, insufficient privileges
+ALTER SYSTEM RESET lc_messages;  -- fail, insufficient privileges
+SELECT set_config ('temp_buffers', '8192', false); -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+ALTER SYSTEM RESET ALL;  -- fail, insufficient privileges
+
+-- Check dropping/revoking behavior
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+
+-- Check that "reassign owned" doesn't affect privileges
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET, ALTER SYSTEM ON PARAMETER
+    autovacuum_work_mem, hash_mem_multiplier, max_stack_depth,
+    shared_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
+SET SESSION AUTHORIZATION regress_admin;
+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
+
+-- Clean up
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_admin; -- ok

Re: Granting SET and ALTER SYSTE privileges for GUCs

От
Tom Lane
Дата:
I wrote:
> Here's v17 rebased up to HEAD.

Pushed after fooling around with the docs.

I have a couple of followup ideas in mind (\dcp and another one),
which I'll start separate threads about.

            regards, tom lane