pgsql: Fix search_path to a safe value during maintenance operations.
Fix search_path to a safe value during maintenance operations.
While executing maintenance operations (ANALYZE, CLUSTER, REFRESH
MATERIALIZED VIEW, REINDEX, or VACUUM), set search_path to
'pg_catalog, pg_temp' to prevent inconsistent behavior.
Functions that are used for functional indexes, in index expressions,
or in materialized views and depend on a different search path must be
declared with CREATE FUNCTION ... SET search_path='...'.
This change addresses a security risk introduced in commit 60684dd834,
where a role with MAINTAIN privileges on a table may be able to
escalate privileges to the table owner. That commit is not yet part of
any release, so no need to backpatch.
Discussion: /messages/by-id/e44327179e5c9015c8dda67351c04da552066017.camel@j-davis.com
Reviewed-by: Greg Stark
Reviewed-by: Nathan Bossart
Branch
------
master
Details
-------
https://git.postgresql.org/pg/commitdiff/05e17373517114167d002494e004fa0aa32d1fd1
Modified Files
--------------
contrib/amcheck/verify_nbtree.c | 2 ++
src/backend/access/brin/brin.c | 2 ++
src/backend/catalog/index.c | 8 ++++++++
src/backend/commands/analyze.c | 2 ++
src/backend/commands/cluster.c | 2 ++
src/backend/commands/indexcmds.c | 6 ++++++
src/backend/commands/matview.c | 2 ++
src/backend/commands/vacuum.c | 2 ++
src/bin/scripts/t/100_vacuumdb.pl | 4 ----
src/include/utils/guc.h | 6 ++++++
src/test/modules/test_oat_hooks/expected/test_oat_hooks.out | 4 ++++
src/test/regress/expected/privileges.out | 12 ++++++------
src/test/regress/expected/vacuum.out | 2 +-
src/test/regress/sql/privileges.sql | 8 ++++----
src/test/regress/sql/vacuum.sql | 2 +-
15 files changed, 48 insertions(+), 16 deletions(-)
On Fri, 2023-06-09 at 20:54 +0000, Jeff Davis wrote:
Fix search_path to a safe value during maintenance operations.
Looks like this is causing pg_amcheck failures in the buildfarm.
Investigating...
Regards,
Jeff Davis
On Fri, 2023-06-09 at 15:16 -0700, Jeff Davis wrote:
On Fri, 2023-06-09 at 20:54 +0000, Jeff Davis wrote:
Fix search_path to a safe value during maintenance operations.
Looks like this is causing pg_amcheck failures in the buildfarm.
Investigating...
It looks related to bt_index_check_internal(), which is called by SQL
functions bt_index_check() and bt_index_parent_check(). SQL functions
can be called in parallel, so it raises the error:
ERROR: cannot set parameters during a parallel operation
because commit 05e1737351 added the SetConfigOption() line. Normally
those functions would not be called in parallel, but
debug_parallel_mode makes that happen.
Attached a patch to mark those functions as PARALLEL UNSAFE, which
fixes the problem.
Alternatively, I could just take out that line, as those SQL functions
are not controlled by the MAINTAIN privilege. But for consistency I
think it's a good idea to leave it in so that index functions are
called with the right search path for amcheck.
--
Jeff Davis
PostgreSQL Contributor Team - AWS
Attachments:
v1-0001-amcheck-mark-bt_index_check-PARALLEL-UNSAFE.patchtext/x-patch; charset=UTF-8; name=v1-0001-amcheck-mark-bt_index_check-PARALLEL-UNSAFE.patchDownload
From 0bc11bbc4b06228d33bd9fc6b29dcc9a25d81151 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Fri, 9 Jun 2023 17:24:53 -0700
Subject: [PATCH v1] amcheck: mark bt_index_check() PARALLEL UNSAFE.
These functions call bt_index_check_internal(), which needs to set the
right search path before executing any index functions.
Discussion: https://postgr.es/m/20230609232446.GA123624@nathanxps13
---
contrib/amcheck/Makefile | 4 +++-
contrib/amcheck/amcheck--1.3--1.4.sql | 13 +++++++++++++
contrib/amcheck/amcheck.control | 2 +-
3 files changed, 17 insertions(+), 2 deletions(-)
create mode 100644 contrib/amcheck/amcheck--1.3--1.4.sql
diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile
index b82f221e50..4896c586f8 100644
--- a/contrib/amcheck/Makefile
+++ b/contrib/amcheck/Makefile
@@ -7,7 +7,9 @@ OBJS = \
verify_nbtree.o
EXTENSION = amcheck
-DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
+DATA = amcheck--1.3--1.4.sql amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql \
+ amcheck--1.0--1.1.sql amcheck--1.0.sql
+
PGFILEDESC = "amcheck - function for verifying relation integrity"
REGRESS = check check_btree check_heap
diff --git a/contrib/amcheck/amcheck--1.3--1.4.sql b/contrib/amcheck/amcheck--1.3--1.4.sql
new file mode 100644
index 0000000000..409cd55195
--- /dev/null
+++ b/contrib/amcheck/amcheck--1.3--1.4.sql
@@ -0,0 +1,13 @@
+/* contrib/amcheck/amcheck--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.4'" to load this file. \quit
+
+--
+-- Mark bt_index_check functions as PARALLEL UNSAFE.
+--
+ALTER FUNCTION bt_index_check(regclass) PARALLEL UNSAFE;
+ALTER FUNCTION bt_index_check(regclass, boolean) PARALLEL UNSAFE;
+ALTER FUNCTION bt_index_parent_check(regclass) PARALLEL UNSAFE;
+ALTER FUNCTION bt_index_parent_check(regclass, boolean) PARALLEL UNSAFE;
+ALTER FUNCTION bt_index_parent_check(regclass, boolean, boolean) PARALLEL UNSAFE;
diff --git a/contrib/amcheck/amcheck.control b/contrib/amcheck/amcheck.control
index ab50931f75..e67ace01c9 100644
--- a/contrib/amcheck/amcheck.control
+++ b/contrib/amcheck/amcheck.control
@@ -1,5 +1,5 @@
# amcheck extension
comment = 'functions for verifying relation integrity'
-default_version = '1.3'
+default_version = '1.4'
module_pathname = '$libdir/amcheck'
relocatable = true
--
2.34.1
On Fri, 2023-06-09 at 17:37 -0700, Jeff Davis wrote:
Attached a patch to mark those functions as PARALLEL UNSAFE, which
fixes the problem.
On second thought, that might not be acceptable for amcheck, depending
on how its run.
I think it's OK if run by pg_amcheck, because that runs it on a single
index at a time in each connection, and parallelizes by opening more
connections.
But if some users are calling the check functions across many tables in
a single select statement (e.g. in the targetlist of a query on
pg_class), then they'll experience a regression.
Alternatively, I could just take out that line, as those SQL
functions
are not controlled by the MAINTAIN privilege. But for consistency I
think it's a good idea to leave it in so that index functions are
called with the right search path for amcheck.
If marking them PARALLEL UNSAFE is not acceptable, then this seems like
a fine solution.
Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
Attached a patch to mark those functions as PARALLEL UNSAFE, which
fixes the problem.
Alternatively, I could just take out that line, as those SQL functions
are not controlled by the MAINTAIN privilege. But for consistency I
think it's a good idea to leave it in so that index functions are
called with the right search path for amcheck.
I concur with the upthread objection that it is way too late in
the release cycle to be introducing a breaking change like this.
I request that you revert it.
regards, tom lane
On Sat, Jun 10, 2023 at 01:33:31AM -0400, Tom Lane wrote:
Jeff Davis <pgsql@j-davis.com> writes:
Attached a patch to mark those functions as PARALLEL UNSAFE, which
fixes the problem.Alternatively, I could just take out that line, as those SQL functions
are not controlled by the MAINTAIN privilege. But for consistency I
think it's a good idea to leave it in so that index functions are
called with the right search path for amcheck.I concur with the upthread objection that it is way too late in
the release cycle to be introducing a breaking change like this.
I request that you revert it.
The timing was not great, but this is fixing a purported defect in an older
v16 feature. If the MAINTAIN privilege is actually fine, we're all set for
v16. If MAINTAIN does have a material problem that $SUBJECT had fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem a
different way.
On Mon, Jun 12, 2023 at 1:05 PM Noah Misch <noah@leadboat.com> wrote:
I concur with the upthread objection that it is way too late in
the release cycle to be introducing a breaking change like this.
I request that you revert it.The timing was not great, but this is fixing a purported defect in an older
v16 feature. If the MAINTAIN privilege is actually fine, we're all set for
v16. If MAINTAIN does have a material problem that $SUBJECT had fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem a
different way.
I wonder why this commit used pg_catalog, pg_temp rather than just the
empty string, as AutoVacWorkerMain does.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, 2023-06-12 at 13:33 -0400, Robert Haas wrote:
I wonder why this commit used pg_catalog, pg_temp rather than just
the
empty string, as AutoVacWorkerMain does.
I followed the rules here for "Writing SECURITY DEFINER Functions
Safely":
https://www.postgresql.org/docs/16/sql-createfunction.html
which suggests adding pg_temp at the end (otherwise it is searched
first by default).
It's not exactly like a SECURITY DEFINER function, but running a
maintenance command does switch to the table owner, so the risks are
similar.
I don't see a problem with the pg_temp schema in non-interactive
processes, because we trust the non-interactive processes.
Regards,
Jeff Davis
On Mon, 2023-06-12 at 13:05 -0400, Noah Misch wrote:
The timing was not great, but this is fixing a purported defect in an
older
v16 feature. If the MAINTAIN privilege is actually fine, we're all
set for
v16. If MAINTAIN does have a material problem that $SUBJECT had
fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem
a
different way.
Someone with the MAINTAIN privilege on a table can use search_path
tricks against the table owner, if the code is susceptible, because
maintenance code runs with the privileges of the table owner.
I was concerned enough to bring it up on the -security list, and then
to -hackers followed by a commit (too late). But perhaps that was
paranoia: the practical risk is probably quite low, because a user with
the MAINTAIN privilege is likely to be highly trusted.
I'd like to hear from others on the topic about the relative risks of
shipping with/without the search_path changes.
I don't think a full revert of the MAINTAIN privilege is the right
thing -- the predefined role is very valuable and many other predefined
roles are much more dangerous than pg_maintain is.
Regards,
Jeff Davis
On Mon, Jun 12, 2023 at 5:40 PM Jeff Davis <pgsql@j-davis.com> wrote:
On Mon, 2023-06-12 at 13:05 -0400, Noah Misch wrote:
The timing was not great, but this is fixing a purported defect in an
older
v16 feature. If the MAINTAIN privilege is actually fine, we're all
set for
v16. If MAINTAIN does have a material problem that $SUBJECT had
fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem
a
different way.Someone with the MAINTAIN privilege on a table can use search_path
tricks against the table owner, if the code is susceptible, because
maintenance code runs with the privileges of the table owner.
Only change the search_path if someone other than the table owner or
superuser is running the command (which should only be possible via the new
MAINTAIN privilege)?
David J.
On Monday, June 12, 2023, David G. Johnston <david.g.johnston@gmail.com>
wrote:
On Mon, Jun 12, 2023 at 5:40 PM Jeff Davis <pgsql@j-davis.com> wrote:
On Mon, 2023-06-12 at 13:05 -0400, Noah Misch wrote:
The timing was not great, but this is fixing a purported defect in an
older
v16 feature. If the MAINTAIN privilege is actually fine, we're all
set for
v16. If MAINTAIN does have a material problem that $SUBJECT had
fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem
a
different way.Someone with the MAINTAIN privilege on a table can use search_path
tricks against the table owner, if the code is susceptible, because
maintenance code runs with the privileges of the table owner.Only change the search_path if someone other than the table owner or
superuser is running the command (which should only be possible via the new
MAINTAIN privilege)?
On a related note, are we OK with someone using this privilege setting
their own default_statistics_target?
https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-DEFAULT-STATISTICS-TARGET
My prior attempt to open up analyze had brought this up as a reason to
avoid having someone besides the table owner allowed to analyze the table.
David J.
On Mon, Jun 12, 2023 at 8:20 PM Jeff Davis <pgsql@j-davis.com> wrote:
I followed the rules here for "Writing SECURITY DEFINER Functions
Safely":https://www.postgresql.org/docs/16/sql-createfunction.html
which suggests adding pg_temp at the end (otherwise it is searched
first by default).
Interesting. The issue of "what is a safe search path?" is more
nuanced than I would prefer. :-(
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jun 12, 2023 at 05:39:40PM -0700, Jeff Davis wrote:
On Mon, 2023-06-12 at 13:05 -0400, Noah Misch wrote:
The timing was not great, but this is fixing a purported defect in an
older
v16 feature.� If the MAINTAIN privilege is actually fine, we're all
set for
v16.� If MAINTAIN does have a material problem that $SUBJECT had
fixed, we
should either revert MAINTAIN, un-revert $SUBJECT, or fix the problem
a
different way.Someone with the MAINTAIN privilege on a table can use search_path
tricks against the table owner, if the code is susceptible, because
maintenance code runs with the privileges of the table owner.I was concerned enough to bring it up on the -security list, and then
to -hackers followed by a commit (too late). But perhaps that was
paranoia: the practical risk is probably quite low, because a user with
the MAINTAIN privilege is likely to be highly trusted.I'd like to hear from others on the topic about the relative risks of
shipping with/without the search_path changes.
I find shipping with the search_path change ($SUBJECT) to be lower risk
overall, though both are fairly low-risk. Expect no new errors in non-FULL
VACUUM, which doesn't run the relevant kinds of code. Tables not ready for
the search_path change in ANALYZE already cause errors in Autovacuum ANALYZE
and have since 2018-02 (CVE-2018-1058). Hence, $SUBJECT poses less
compatibility risk than the CVE-2018-1058 fix.
Best argument for shipping without $SUBJECT: we already have REFERENCES and
TRIGGER privilege that tend to let the grantee hijack the table owner's
account. Adding MAINTAIN to the list, while sad, is defensible. I still
prefer to ship with $SUBJECT, not without.
On Mon, 2023-06-12 at 17:50 -0700, David G. Johnston wrote:
Only change the search_path if someone other than the table owner or
superuser is running the command (which should only be possible via
the new MAINTAIN privilege)?
That sounds like a reasonable compromise, but a bit messy. If we do it
this way, is there hope to clean things up a bit in the future? These
special cases are quite difficult to document in a comprehensible way.
If others like this approach I'm fine with it.
Regards,
Jeff Davis
On Tue, 2023-06-13 at 11:24 -0400, Robert Haas wrote:
Interesting. The issue of "what is a safe search path?" is more
nuanced than I would prefer. :-(
As far as I can tell, search_path was designed as a convenience for
interactive queries, where a lot of these nuances make sense. But they
don't make sense as defaults for code inside functions, in my opinion.
Regards,
Jeff Davis
On Mon, 2023-06-12 at 18:31 -0700, David G. Johnston wrote:
On a related note, are we OK with someone using this privilege
setting their own default_statistics_target?https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-DEFAULT-STATISTICS-TARGET
My prior attempt to open up analyze had brought this up as a reason
to avoid having someone besides the table owner allowed to analyze
the table.
Thank you for bringing it up. I don't see a major concern there, but
please link to the prior discussion so I can see the objections.
Regards,
Jeff Davis
On Tue, Jun 13, 2023 at 12:54 PM Jeff Davis <pgsql@j-davis.com> wrote:
On Mon, 2023-06-12 at 18:31 -0700, David G. Johnston wrote:
On a related note, are we OK with someone using this privilege
setting their own default_statistics_target?https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-DEFAULT-STATISTICS-TARGET
My prior attempt to open up analyze had brought this up as a reason
to avoid having someone besides the table owner allowed to analyze
the table.Thank you for bringing it up. I don't see a major concern there, but
please link to the prior discussion so I can see the objections.
This is the specific (first?) message I am recalling.
/messages/by-id/A737B7A37273E048B164557ADEF4A58B53803F5A@ntex2010i.host.magwien.gv.at
David J.
Noah Misch <noah@leadboat.com> writes:
Best argument for shipping without $SUBJECT: we already have REFERENCES and
TRIGGER privilege that tend to let the grantee hijack the table owner's
account. Adding MAINTAIN to the list, while sad, is defensible. I still
prefer to ship with $SUBJECT, not without.
What I'm concerned about is making such a fundamental semantics change
post-beta1. It'll basically invalidate any application compatibility
testing anybody might have done against beta1. I think this ship has
sailed as far as v16 is concerned, although we could reconsider it
in v17.
Also, I fail to see any connection to the MAINTAIN privilege: the
committed-and-reverted patch would break things whether the user
was making any use of that privilege or not. Thus, I do not accept
the idea that we're fixing something that's new in 16.
regards, tom lane
On Tue, 2023-06-13 at 13:22 -0700, David G. Johnston wrote:
This is the specific (first?) message I am recalling.
/messages/by-id/A737B7A37273E048B164557ADEF4A58B53803F5A@ntex2010i.host.magwien.gv.at
The most objection seems to be expressed most succinctly in this
message:
/messages/by-id/16134.1456767564@sss.pgh.pa.us
"if we allow non-owners to run ANALYZE, they'd be able to mess things
up by setting the stats target either much lower or much higher than
the table owner expected"
I have trouble seeing much of a problem here if there is an explicit
MAINTAIN privilege. If you grant someone MAINTAIN to someone, it's not
surprising that you need to coordinate maintenance-related settings
with that user; and if you don't, then it's not surprising that the
statistics could get messed up.
Perhaps the objections in that thread were because the proposal
involved inferring the privilege to ANALYZE from other privileges,
rather than having an explicit MAINTAIN privilege?
Regards,
Jeff Davis
On Tue, Jun 13, 2023 at 1:55 PM Jeff Davis <pgsql@j-davis.com> wrote:
Perhaps the objections in that thread were because the proposal
involved inferring the privilege to ANALYZE from other privileges,
rather than having an explicit MAINTAIN privilege?
That is true; but it seems worth being explicit whether we expect the user
to only be able to run "ANALYZE" using defaults (like auto-analyze would
do) or if this additional capability is assumed to be part of the grant. I
do imagine you'd want to be able to set the statistic target in order to do
vacuum --analyze-in-stages with a non-superuser.
David J.
Jeff Davis <pgsql@j-davis.com> writes:
The most objection seems to be expressed most succinctly in this
message:
/messages/by-id/16134.1456767564@sss.pgh.pa.us
"if we allow non-owners to run ANALYZE, they'd be able to mess things
up by setting the stats target either much lower or much higher than
the table owner expected"
I have trouble seeing much of a problem here if there is an explicit
MAINTAIN privilege. If you grant someone MAINTAIN to someone, it's not
surprising that you need to coordinate maintenance-related settings
with that user; and if you don't, then it's not surprising that the
statistics could get messed up.
I agree that granting MAINTAIN implies that you trust the grantee
not to mess up your stats.
Perhaps the objections in that thread were because the proposal
involved inferring the privilege to ANALYZE from other privileges,
rather than having an explicit MAINTAIN privilege?
Exactly.
regards, tom lane
On Tue, 2023-06-13 at 16:23 -0400, Tom Lane wrote:
What I'm concerned about is making such a fundamental semantics
change
post-beta1.
I have added the patch to the July CF for v17.
If someone does feel like something should be done for v16, David G.
Johnston posted one possibility here:
/messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com
But as Noah pointed out, there are other privileges that can be abused,
so a workaround for 16 might not be important if we have a likely fix
for MAINTAIN coming in 17.
Regards,
Jeff Davis
On Thu, Jun 15, 2023 at 12:59 AM Jeff Davis <pgsql@j-davis.com> wrote:
On Tue, 2023-06-13 at 16:23 -0400, Tom Lane wrote:
What I'm concerned about is making such a fundamental semantics
change
post-beta1.I have added the patch to the July CF for v17.
If someone does feel like something should be done for v16, David G.
Johnston posted one possibility here:/messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com
But as Noah pointed out, there are other privileges that can be abused,
so a workaround for 16 might not be important if we have a likely fix
for MAINTAIN coming in 17.
Rather than is_superuser(userid) || userid == ownerid, I think that
the test should be has_privs_of_role(userid, ownerid).
I'm inclined to think that this is a real security issue and am not
very sanguine about waiting another year to fix it, but at the same
time, I'm somewhat worried that the proposed fix might be too narrow
or wrongly-shaped. I'm not too convinced that we've properly
understood what all of the problems in this area are. :-(
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, 2023-06-19 at 16:03 -0400, Robert Haas wrote:
I'm inclined to think that this is a real security issue and am not
Can you expand on that a bit? You mean a practical security issue for
the intended use cases?
very sanguine about waiting another year to fix it, but at the same
time, I'm somewhat worried that the proposed fix might be too narrow
or wrongly-shaped. I'm not too convinced that we've properly
understood what all of the problems in this area are. :-(
Would it be acceptable to document that the MAINTAIN privilege (along
with TRIGGER and, if I understand correctly, REFERENCES) carries
privilege escalation risk for the grantor?
Regards,
Jeff Davis
[ emerges from hibernation ]
On Mon, Jun 19, 2023 at 6:58 PM Jeff Davis <pgsql@j-davis.com> wrote:
On Mon, 2023-06-19 at 16:03 -0400, Robert Haas wrote:
I'm inclined to think that this is a real security issue and am not
Can you expand on that a bit? You mean a practical security issue for
the intended use cases?
Yeah. I mean, as things stand, it seems like giving someone the
MAINTAIN privilege will be sufficient to allow them to escalate to the
table owner if there are any expression indexes involved. That seems
like a real problem. We shouldn't ship a new feature with a built-in
security hole like that.
I was pretty outraged when I realized that we'd been shipping releases
for years where CREATEROLE let you grab superuser because you could
just grant yourself pg_execute_server_program and then go to town.
Ideally, that hazard should have been identified and fixed in some way
before introducing pg_execute_server_program. I don't know whether the
hazard wasn't realized at the time or whether somebody somehow
convinced themselves that that was OK, but it clearly isn't.
Now we're proposing to ship a brand-new feature with a hole that we
definitely already know exists. I can't understand that at all. Should
we just go file the CVE against ourselves right now, then? Seriously,
what are we doing?
If we're not going to fix the feature so that it doesn't break the
security model, we should probably just revert it. I don't understand
at all the idea of shipping something that we 100% know is broken.
very sanguine about waiting another year to fix it, but at the same
time, I'm somewhat worried that the proposed fix might be too narrow
or wrongly-shaped. I'm not too convinced that we've properly
understood what all of the problems in this area are. :-(Would it be acceptable to document that the MAINTAIN privilege (along
with TRIGGER and, if I understand correctly, REFERENCES) carries
privilege escalation risk for the grantor?
That's clearly better than nothing, but also seems like it's pretty
clearly the wrong approach. If somebody electrocutes themselves on the
toaster in the break room, you don't stick a sign on the side of it
that says "this toaster will electrocute you if you try to use it" and
then call it good. You either fix or replace the toaster, or at the
very least throw it out, or at the VERY least unplug it. I am failing
to understand how this situation is any different.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 2023-06-29 Th 11:19, Robert Haas wrote:
Now we're proposing to ship a brand-new feature with a hole that we
definitely already know exists. I can't understand that at all. Should
we just go file the CVE against ourselves right now, then? Seriously,
what are we doing?If we're not going to fix the feature so that it doesn't break the
security model, we should probably just revert it. I don't understand
at all the idea of shipping something that we 100% know is broken.
+1
cheers
andrew
--
Andrew Dunstan
EDB:https://www.enterprisedb.com
On Thu, Jun 29, 2023 at 11:19:38AM -0400, Robert Haas wrote:
[ emerges from hibernation ]
Welcome back.
If we're not going to fix the feature so that it doesn't break the
security model, we should probably just revert it. I don't understand
at all the idea of shipping something that we 100% know is broken.
Given Jeff's commit followed the precedent set by the fix for
CVE-2018-1058, I'm inclined to think he was on the right track. Perhaps a
more targeted fix, such as only changing search_path when the command is
not run by the table owner (as suggested upthread [0]/messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com) is worth
considering.
[0]: /messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, 2023-06-29 at 11:19 -0400, Robert Haas wrote:
Yeah. I mean, as things stand, it seems like giving someone the
MAINTAIN privilege will be sufficient to allow them to escalate to
the
table owner if there are any expression indexes involved. That seems
like a real problem. We shouldn't ship a new feature with a built-in
security hole like that.
Let's take David's suggestion[1]/messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com then, and only restrict the search
path for those without owner privileges on the object.
That would mean no behavior change unless using the MAINTAIN privilege,
which is new, so no breakage. And if someone is using the MAINTAIN
privilege, they wouldn't be able to abuse the search_path, so it would
close the hole.
Patch attached (created a bit quickly, but seems to work).
Regards,
Jeff Davis
[1]: /messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com
/messages/by-id/CAKFQuwaVJkM9u+qpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw@mail.gmail.com
Attachments:
0001-Restrict-search_path-for-non-owners-performing-maint.patchtext/x-patch; charset=UTF-8; name=0001-Restrict-search_path-for-non-owners-performing-maint.patchDownload
From 67387349d2d84cdc2d7b6d7ab5d5824e5ccc89bf Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Thu, 29 Jun 2023 16:03:53 -0700
Subject: [PATCH] Restrict search_path for non-owners performing maintenance.
When non-owners execute maintenance operations (ANALYZE, CLUSTER,
REFRESH MATERIALIZED VIEW, REINDEX, or VACUUM), set search_path to
'pg_catalog, pg_temp'.
Functions that are used for functional indexes, in index expressions,
or in materialized views and depend on a different search path should
be declared with CREATE FUNCTION ... SET search_path='...'.
This change addresses a security risk introduced in commit 60684dd834,
where a role with MAINTAIN privileges on a table may be able to
escalate privileges to the table owner. That commit is not yet part of
any release, so no need to backpatch.
A previous fix 05e1737351 was deemed too large a change. This is a
narrower fix that only affects non-owners, which previously could not
execute maintenance commands. Per suggestion from David G. Johnston.
Discussion: https://postgr.es/m/CAKFQuwaVJkM9u%2BqpOaom2UkPE1sz0BASF-E5amxWPxncUhm4Hw%40mail.gmail.com
Discussion: https://postgr.es/m/e44327179e5c9015c8dda67351c04da552066017.camel%40j-davis.com
---
contrib/amcheck/verify_nbtree.c | 2 ++
src/backend/access/brin/brin.c | 2 ++
src/backend/catalog/index.c | 5 +++++
src/backend/commands/analyze.c | 2 ++
src/backend/commands/cluster.c | 3 ++-
src/backend/commands/indexcmds.c | 4 +++-
src/backend/commands/matview.c | 2 ++
src/backend/commands/vacuum.c | 2 ++
src/backend/utils/init/usercontext.c | 17 +++++++++++++++++
src/include/utils/guc.h | 6 ++++++
src/include/utils/usercontext.h | 1 +
11 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 94a9759322..1a77035e25 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -40,6 +40,7 @@
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
+#include "utils/usercontext.h"
PG_MODULE_MAGIC;
@@ -281,6 +282,7 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
SetUserIdAndSecContext(heaprel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heaprel->rd_rel->relowner);
}
else
{
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 3c6a956eaa..9e04fd6ecb 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -40,6 +40,7 @@
#include "utils/index_selfuncs.h"
#include "utils/memutils.h"
#include "utils/rel.h"
+#include "utils/usercontext.h"
/*
@@ -1066,6 +1067,7 @@ brin_summarize_range(PG_FUNCTION_ARGS)
SetUserIdAndSecContext(heapRel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRel->rd_rel->relowner);
}
else
{
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 352e43d0e6..9123e7e7b7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -84,6 +84,7 @@
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"
+#include "utils/usercontext.h"
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_index_pg_class_oid = InvalidOid;
@@ -1475,6 +1476,7 @@ index_concurrently_build(Oid heapRelationId,
SetUserIdAndSecContext(heapRel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRel->rd_rel->relowner);
indexRelation = index_open(indexRelationId, RowExclusiveLock);
@@ -3006,6 +3008,7 @@ index_build(Relation heapRelation,
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRelation->rd_rel->relowner);
/* Set up initial progress report status */
{
@@ -3341,6 +3344,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRelation->rd_rel->relowner);
indexRelation = index_open(indexId, RowExclusiveLock);
@@ -3601,6 +3605,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRelation->rd_rel->relowner);
if (progress)
{
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fc9a371f9b..cbe8350a7e 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -67,6 +67,7 @@
#include "utils/spccache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
+#include "utils/usercontext.h"
/* Per-index data for ANALYZE */
@@ -348,6 +349,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, onerel->rd_rel->relowner);
/* measure elapsed time iff autovacuum logging requires it */
if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 03b24ab90f..efed6401ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"
+#include "utils/usercontext.h"
/*
* This struct is used to pass around the information on tables to be
@@ -355,7 +356,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
-
+ RestrictSearchPath(save_userid, OldHeap->rd_rel->relowner);
/*
* Since we may open a new transaction for each relation, we have to check
* that the relation still is what we think it is.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9bc97e1fc2..f4dead0f48 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,7 +69,7 @@
#include "utils/regproc.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
-
+#include "utils/usercontext.h"
/* non-export function prototypes */
static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts);
@@ -1300,6 +1300,7 @@ DefineIndex(Oid relationId,
SetUserIdAndSecContext(childrel->rd_rel->relowner,
child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
child_save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(child_save_userid, childrel->rd_rel->relowner);
/*
* Don't try to create indexes on foreign tables, though. Skip
@@ -3750,6 +3751,7 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params)
SetUserIdAndSecContext(heapRel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, heapRel->rd_rel->relowner);
/* determine safety of this index for set_indexsafe_procflags */
idx->safe = (indexRel->rd_indexprs == NIL &&
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f9a3bdfc3a..920703442b 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -45,6 +45,7 @@
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "utils/usercontext.h"
typedef struct
@@ -179,6 +180,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
SetUserIdAndSecContext(relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, relowner);
/* Make sure it is a materialized view. */
if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7fe6a54c06..58caa761ac 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -61,6 +61,7 @@
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "utils/usercontext.h"
/*
@@ -2171,6 +2172,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
SetUserIdAndSecContext(rel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
+ RestrictSearchPath(save_userid, rel->rd_rel->relowner);
/*
* If PROCESS_MAIN is set (the default), it's time to vacuum the main
diff --git a/src/backend/utils/init/usercontext.c b/src/backend/utils/init/usercontext.c
index dd9a0dd6a8..b62420b56e 100644
--- a/src/backend/utils/init/usercontext.c
+++ b/src/backend/utils/init/usercontext.c
@@ -90,3 +90,20 @@ RestoreUserContext(UserContext *context)
AtEOXact_GUC(false, context->save_nestlevel);
SetUserIdAndSecContext(context->save_userid, context->save_sec_context);
}
+
+/*
+ * When potentially executing code on an object with the given owner, if
+ * userid does not have owner privileges, set a restricted safe search_path.
+ *
+ * Note: the caller is expected to save and restore the GUC nest level as
+ * necessary.
+ */
+void
+RestrictSearchPath(Oid userid, Oid owner)
+{
+ if (has_privs_of_role(userid, owner))
+ return;
+
+ SetConfigOption("search_path", GUC_SAFE_SEARCH_PATH, PGC_USERSET,
+ PGC_S_SESSION);
+}
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index d5253c7ed2..edd82a551b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -201,6 +201,12 @@ typedef enum
#define GUC_QUALIFIER_SEPARATOR '.'
+/*
+ * Safe search path when executing code as the table owner, such as during
+ * maintenance operations.
+ */
+#define GUC_SAFE_SEARCH_PATH "pg_catalog, pg_temp"
+
/*
* Bit values in "flags" of a GUC variable. Note that these don't appear
* on disk, so we can reassign their values freely.
diff --git a/src/include/utils/usercontext.h b/src/include/utils/usercontext.h
index a8195c194d..d5422ce70d 100644
--- a/src/include/utils/usercontext.h
+++ b/src/include/utils/usercontext.h
@@ -22,5 +22,6 @@ typedef struct UserContext
/* Function prototypes. */
extern void SwitchToUntrustedUser(Oid userid, UserContext *context);
extern void RestoreUserContext(UserContext *context);
+extern void RestrictSearchPath(Oid userid, Oid owner);
#endif /* USERCONTEXT_H */
--
2.34.1
Jeff Davis <pgsql@j-davis.com> writes:
On Thu, 2023-06-29 at 11:19 -0400, Robert Haas wrote:
We shouldn't ship a new feature with a built-in
security hole like that.
Let's take David's suggestion[1] then, and only restrict the search
path for those without owner privileges on the object.
I think that's a seriously awful kluge. It will mean that things behave
differently for the owner than for MAINTAIN grantees, which pretty much
destroys the use-case for that privilege, as well as being very confusing
and hard to debug. Yes, *if* you're careful about search path cleanliness
then you can make it work, but that will be a foot-gun forevermore.
(I'm also less than convinced that this is sufficient to remove all
security hazards. One pretty obvious question is do we really want
superusers to be treated as owners, rather than MAINTAIN grantees,
for this purpose.)
I'm leaning to Robert's thought that we need to revert this for now,
and think harder about how to make it work cleanly and safely.
regards, tom lane
On Thu, Jun 29, 2023 at 08:53:56PM -0400, Tom Lane wrote:
I'm leaning to Robert's thought that we need to revert this for now,
and think harder about how to make it work cleanly and safely.
Since it sounds like this is headed towards a revert, here's a patch for
removing MAINTAIN and pg_maintain.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v1-0001-Revert-MAINTAIN-privilege-and-pg_maintain-predefi.patchtext/x-diff; charset=us-asciiDownload
From 29b98518b9acecd9ca08eb3397e3d3a65e9f4431 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 29 Jun 2023 20:56:12 -0700
Subject: [PATCH v1 1/1] Revert MAINTAIN privilege and pg_maintain predefined
role.
This reverts the following commits: 4dbdb82513, c2122aae63,
5b1a879943, 9e1e9d6560, ff9618e82a, 60684dd834, 4441fc704d,
and b5d6382496.
---
doc/src/sgml/ddl.sgml | 35 ++----
doc/src/sgml/func.sgml | 2 +-
.../sgml/ref/alter_default_privileges.sgml | 4 +-
doc/src/sgml/ref/analyze.sgml | 6 +-
doc/src/sgml/ref/cluster.sgml | 10 +-
doc/src/sgml/ref/grant.sgml | 3 +-
doc/src/sgml/ref/lock.sgml | 4 +-
.../sgml/ref/refresh_materialized_view.sgml | 5 +-
doc/src/sgml/ref/reindex.sgml | 23 ++--
doc/src/sgml/ref/revoke.sgml | 2 +-
doc/src/sgml/ref/vacuum.sgml | 6 +-
doc/src/sgml/user-manag.sgml | 12 --
src/backend/catalog/aclchk.c | 15 ---
src/backend/commands/analyze.c | 13 +-
src/backend/commands/cluster.c | 43 ++-----
src/backend/commands/indexcmds.c | 34 +++--
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/matview.c | 3 +-
src/backend/commands/tablecmds.c | 16 ++-
src/backend/commands/vacuum.c | 65 +++++-----
src/backend/utils/adt/acl.c | 8 --
src/bin/pg_dump/dumputils.c | 1 -
src/bin/pg_dump/t/002_pg_dump.pl | 2 +-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/pg_authid.dat | 5 -
src/include/commands/tablecmds.h | 5 +-
src/include/commands/vacuum.h | 5 +-
src/include/nodes/parsenodes.h | 3 +-
src/include/utils/acl.h | 5 +-
.../expected/cluster-conflict-partition.out | 8 +-
.../specs/cluster-conflict-partition.spec | 2 +-
.../perl/PostgreSQL/Test/AdjustUpgrade.pm | 3 -
src/test/regress/expected/cluster.out | 7 --
src/test/regress/expected/create_index.out | 4 +-
src/test/regress/expected/dependency.out | 22 ++--
src/test/regress/expected/privileges.out | 116 ++++--------------
src/test/regress/expected/rowsecurity.out | 34 ++---
src/test/regress/sql/cluster.sql | 5 -
src/test/regress/sql/dependency.sql | 2 +-
src/test/regress/sql/privileges.sql | 68 ----------
40 files changed, 178 insertions(+), 436 deletions(-)
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index e32f8253d0..4317995965 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1718,8 +1718,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>, <literal>USAGE</literal>, <literal>SET</literal>,
- <literal>ALTER SYSTEM</literal>, and <literal>MAINTAIN</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.
@@ -2010,19 +2010,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
</para>
</listitem>
</varlistentry>
-
- <varlistentry id="ddl-priv-maintain">
- <term><literal>MAINTAIN</literal></term>
- <listitem>
- <para>
- Allows <command>VACUUM</command>, <command>ANALYZE</command>,
- <command>CLUSTER</command>, <command>REFRESH MATERIALIZED VIEW</command>,
- <command>REINDEX</command>, and <command>LOCK TABLE</command> on a
- relation.
- </para>
- </listitem>
- </varlistentry>
- </variablelist>
+ </variablelist>
The privileges required by other commands are listed on the
reference page of the respective command.
@@ -2171,11 +2159,6 @@ REVOKE ALL ON accounts FROM PUBLIC;
<entry><literal>A</literal></entry>
<entry><literal>PARAMETER</literal></entry>
</row>
- <row>
- <entry><literal>MAINTAIN</literal></entry>
- <entry><literal>m</literal></entry>
- <entry><literal>TABLE</literal></entry>
- </row>
</tbody>
</tgroup>
</table>
@@ -2266,7 +2249,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
</row>
<row>
<entry><literal>TABLE</literal> (and table-like objects)</entry>
- <entry><literal>arwdDxtm</literal></entry>
+ <entry><literal>arwdDxt</literal></entry>
<entry>none</entry>
<entry><literal>\dp</literal></entry>
</row>
@@ -2325,11 +2308,11 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
<programlisting>
=> \dp mytable
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------+-------+------------------------+-----------------------+----------
- public | mytable | table | miriam=arwdDxtm/miriam+| col1: +|
- | | | =r/miriam +| miriam_rw=rw/miriam |
- | | | admin=arw/miriam | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------+-------+-----------------------+-----------------------+----------
+ public | mytable | table | miriam=arwdDxt/miriam+| col1: +|
+ | | | =r/miriam +| miriam_rw=rw/miriam |
+ | | | admin=arw/miriam | |
(1 row)
</programlisting>
</para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..0b62e0c828 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23545,7 +23545,7 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
are <literal>SELECT</literal>, <literal>INSERT</literal>,
<literal>UPDATE</literal>, <literal>DELETE</literal>,
<literal>TRUNCATE</literal>, <literal>REFERENCES</literal>,
- <literal>TRIGGER</literal>, and <literal>MAINTAIN</literal>.
+ and <literal>TRIGGER</literal>.
</para></entry>
</row>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index a33461fbc2..f1d54f5aa3 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -28,7 +28,7 @@ ALTER DEFAULT PRIVILEGES
<phrase>where <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> is one of:</phrase>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON TABLES
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@@ -51,7 +51,7 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
REVOKE [ GRANT OPTION FOR ]
- { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+ { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON TABLES
FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 954491b5df..aa3e9e1c5f 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -182,9 +182,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
<title>Notes</title>
<para>
- To analyze a table, one must ordinarily have the <literal>MAINTAIN</literal>
- privilege on the table. However, database owners are allowed to
+ To analyze a table, one must ordinarily be the table's owner or a
+ superuser. However, database owners are allowed to
analyze all tables in their databases, except shared catalogs.
+ (The restriction for shared catalogs means that a true database-wide
+ <command>ANALYZE</command> can only be performed by a superuser.)
<command>ANALYZE</command> will skip over any tables that the calling user
does not have permission to analyze.
</para>
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 06f3d269e6..0ed29a5c6d 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -70,8 +70,9 @@ CLUSTER [VERBOSE]
<command>CLUSTER</command> without a
<replaceable class="parameter">table_name</replaceable> reclusters all the
previously-clustered tables in the current database that the calling user
- has privileges for. This form of <command>CLUSTER</command> cannot be
- executed inside a transaction block.
+ owns, or all such tables if called by a superuser. This
+ form of <command>CLUSTER</command> cannot be executed inside a transaction
+ block.
</para>
<para>
@@ -132,11 +133,6 @@ CLUSTER [VERBOSE]
<refsect1>
<title>Notes</title>
- <para>
- To cluster a table, one must have the <literal>MAINTAIN</literal> privilege
- on the table.
- </para>
-
<para>
In cases where you are accessing single rows randomly
within a table, the actual order of the data in the
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 35bf0332c8..1ae5770fbb 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
@@ -193,7 +193,6 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
<term><literal>USAGE</literal></term>
<term><literal>SET</literal></term>
<term><literal>ALTER SYSTEM</literal></term>
- <term><literal>MAINTAIN</literal></term>
<listitem>
<para>
Specific types of privileges, as defined in <xref linkend="ddl-priv"/>.
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 070855da18..6ce2518de7 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -166,8 +166,8 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
<para>
To lock a table, the user must have the right privilege for the specified
- <replaceable class="parameter">lockmode</replaceable>.
- If the user has <literal>MAINTAIN</literal>,
+ <replaceable class="parameter">lockmode</replaceable>, or be the table's
+ owner or a superuser. If the user has
<literal>UPDATE</literal>, <literal>DELETE</literal>, or
<literal>TRUNCATE</literal> privileges on the table, any <replaceable
class="parameter">lockmode</replaceable> is permitted. If the user has
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 19737668cd..675d6090f3 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -31,9 +31,8 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
<para>
<command>REFRESH MATERIALIZED VIEW</command> completely replaces the
- contents of a materialized view. To execute this command you must have the
- <literal>MAINTAIN</literal>
- privilege on the materialized view. The old contents are discarded. If
+ contents of a materialized view. To execute this command you must be the
+ owner of the materialized view. The old contents are discarded. If
<literal>WITH DATA</literal> is specified (or defaults) the backing query
is executed to provide the new data, and the materialized view is left in a
scannable state. If <literal>WITH NO DATA</literal> is specified no new
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index bef3486843..21e2e91d89 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -292,21 +292,16 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { DA
</para>
<para>
- Reindexing a single index or table requires
- having the <literal>MAINTAIN</literal> privilege on the
- table. Note that while <command>REINDEX</command> on a partitioned index or
- table requires having the <literal>MAINTAIN</literal> privilege on the
- partitioned table, such commands skip the privilege checks when processing
- the individual partitions. Reindexing a schema or database requires being the
- owner of that schema or database or having privileges of the
- <link linkend="predefined-roles-table"><literal>pg_maintain</literal></link>
- role. Note specifically that it's thus
+ Reindexing a single index or table requires being the owner of that
+ index or table. Reindexing a schema or database requires being the
+ owner of that schema or database. Note specifically that it's thus
possible for non-superusers to rebuild indexes of tables owned by
- other users. However, as a special exception,
- <command>REINDEX DATABASE</command>, <command>REINDEX SCHEMA</command>,
- and <command>REINDEX SYSTEM</command> will skip indexes on shared catalogs
- unless the user has the <literal>MAINTAIN</literal> privilege on the
- catalog.
+ other users. However, as a special exception, when
+ <command>REINDEX DATABASE</command>, <command>REINDEX SCHEMA</command>
+ or <command>REINDEX SYSTEM</command> is issued by a non-superuser,
+ indexes on shared catalogs will be skipped unless the user owns the
+ catalog (which typically won't be the case). Of course, superusers
+ can always reindex anything.
</para>
<para>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a..2db66bbf37 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
REVOKE [ GRANT OPTION FOR ]
- { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+ { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable>schema_name</replaceable> [, ...] }
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index c42bbea9e2..65c03bf829 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -444,9 +444,11 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
<title>Notes</title>
<para>
- To vacuum a table, one must ordinarily have the <literal>MAINTAIN</literal>
- privilege on the table. However, database owners are allowed to
+ To vacuum a table, one must ordinarily be the table's owner or a
+ superuser. However, database owners are allowed to
vacuum all tables in their databases, except shared catalogs.
+ (The restriction for shared catalogs means that a true database-wide
+ <command>VACUUM</command> can only be performed by a superuser.)
<command>VACUUM</command> will skip over any tables that the calling user
does not have permission to vacuum.
</para>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index e1540dd481..27c1f3d703 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -683,18 +683,6 @@ DROP ROLE doomed_role;
the <link linkend="sql-checkpoint"><command>CHECKPOINT</command></link>
command.</entry>
</row>
- <row>
- <entry>pg_maintain</entry>
- <entry>Allow executing
- <link linkend="sql-vacuum"><command>VACUUM</command></link>,
- <link linkend="sql-analyze"><command>ANALYZE</command></link>,
- <link linkend="sql-cluster"><command>CLUSTER</command></link>,
- <link linkend="sql-refreshmaterializedview"><command>REFRESH MATERIALIZED VIEW</command></link>,
- <link linkend="sql-reindex"><command>REINDEX</command></link>,
- and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
- relations, as if having <literal>MAINTAIN</literal> rights on those
- objects, even without having it explicitly.</entry>
- </row>
<row>
<entry>pg_use_reserved_connections</entry>
<entry>Allow use of connection slots reserved via
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index bc2ad773c9..d1f5dcd8be 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -2612,8 +2612,6 @@ string_to_privilege(const char *privname)
return ACL_SET;
if (strcmp(privname, "alter system") == 0)
return ACL_ALTER_SYSTEM;
- if (strcmp(privname, "maintain") == 0)
- return ACL_MAINTAIN;
if (strcmp(privname, "rule") == 0)
return 0; /* ignore old RULE privileges */
ereport(ERROR,
@@ -2655,8 +2653,6 @@ privilege_to_string(AclMode privilege)
return "SET";
case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM";
- case ACL_MAINTAIN:
- return "MAINTAIN";
default:
elog(ERROR, "unrecognized privilege: %d", (int) privilege);
}
@@ -3388,17 +3384,6 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
has_privs_of_role(roleid, ROLE_PG_WRITE_ALL_DATA))
result |= (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE));
- /*
- * Check if ACL_MAINTAIN is being checked and, if so, and not already set
- * as part of the result, then check if the user is a member of the
- * pg_maintain role, which allows VACUUM, ANALYZE, CLUSTER, REFRESH
- * MATERIALIZED VIEW, and REINDEX on all relations.
- */
- if (mask & ACL_MAINTAIN &&
- !(result & ACL_MAINTAIN) &&
- has_privs_of_role(roleid, ROLE_PG_MAINTAIN))
- result |= ACL_MAINTAIN;
-
return result;
}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fc9a371f9b..7b2fff09fc 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -159,15 +159,16 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
/*
- * Check if relation needs to be skipped based on privileges. This check
+ * Check if relation needs to be skipped based on ownership. This check
* happens also when building the relation list to analyze for a manual
* operation, and needs to be done additionally here as ANALYZE could
- * happen across multiple transactions where privileges could have changed
- * in-between. Make sure to generate only logs for ANALYZE in this case.
+ * happen across multiple transactions where relation ownership could have
+ * changed in-between. Make sure to generate only logs for ANALYZE in
+ * this case.
*/
- if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel),
- onerel->rd_rel,
- params->options & ~VACOPT_VACUUM))
+ if (!vacuum_is_relation_owner(RelationGetRelid(onerel),
+ onerel->rd_rel,
+ params->options & VACOPT_ANALYZE))
{
relation_close(onerel, ShareUpdateExclusiveLock);
return;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 03b24ab90f..92c465c05b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -80,7 +80,6 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
static List *get_tables_to_cluster(MemoryContext cluster_context);
static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context,
Oid indexOid);
-static bool cluster_is_permitted_for_relation(Oid relid, Oid userid);
/*---------------------------------------------------------------------------
@@ -148,8 +147,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
tableOid = RangeVarGetRelidExtended(stmt->relation,
AccessExclusiveLock,
0,
- RangeVarCallbackMaintainsTable,
- NULL);
+ RangeVarCallbackOwnsTable, NULL);
rel = table_open(tableOid, NoLock);
/*
@@ -366,8 +364,8 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
*/
if (recheck)
{
- /* Check that the user still has privileges for the relation */
- if (!cluster_is_permitted_for_relation(tableOid, save_userid))
+ /* Check that the user still owns the relation */
+ if (!object_ownercheck(RelationRelationId, tableOid, save_userid))
{
relation_close(OldHeap, AccessExclusiveLock);
goto out;
@@ -1642,7 +1640,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
/*
- * Get a list of tables that the current user has privileges on and
+ * Get a list of tables that the current user owns and
* have indisclustered set. Return the list in a List * of RelToCluster
* (stored in the specified memory context), each one giving the tableOid
* and the indexOid on which the table is already clustered.
@@ -1659,8 +1657,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
List *rtcs = NIL;
/*
- * Get all indexes that have indisclustered set and that the current user
- * has the appropriate privileges for.
+ * Get all indexes that have indisclustered set and are owned by
+ * appropriate user.
*/
indRelation = table_open(IndexRelationId, AccessShareLock);
ScanKeyInit(&entry,
@@ -1674,7 +1672,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
index = (Form_pg_index) GETSTRUCT(indexTuple);
- if (!cluster_is_permitted_for_relation(index->indrelid, GetUserId()))
+ if (!object_ownercheck(RelationRelationId, index->indrelid, GetUserId()))
continue;
/* Use a permanent memory context for the result list */
@@ -1722,13 +1720,10 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
continue;
- /*
- * It's possible that the user does not have privileges to CLUSTER the
- * leaf partition despite having such privileges on the partitioned
- * table. We skip any partitions which the user is not permitted to
- * CLUSTER.
- */
- if (!cluster_is_permitted_for_relation(relid, GetUserId()))
+ /* Silently skip partitions which the user has no access to. */
+ if (!object_ownercheck(RelationRelationId, relid, GetUserId()) &&
+ (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) ||
+ IsSharedRelation(relid)))
continue;
/* Use a permanent memory context for the result list */
@@ -1744,19 +1739,3 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
return rtcs;
}
-
-/*
- * Return whether userid has privileges to CLUSTER relid. If not, this
- * function emits a WARNING.
- */
-static bool
-cluster_is_permitted_for_relation(Oid relid, Oid userid)
-{
- if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK)
- return true;
-
- ereport(WARNING,
- (errmsg("permission denied to cluster \"%s\", skipping it",
- get_rel_name(relid))));
- return false;
-}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 403f5fc143..02250ae74b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -26,7 +26,6 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_am.h"
-#include "catalog/pg_authid.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
@@ -2829,7 +2828,6 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
char relkind;
struct ReindexIndexCallbackState *state = arg;
LOCKMODE table_lockmode;
- Oid table_oid;
/*
* Lock level here should match table lock in reindex_index() for
@@ -2869,19 +2867,14 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
errmsg("\"%s\" is not an index", relation->relname)));
/* Check permissions */
- table_oid = IndexGetRelation(relId, true);
- if (OidIsValid(table_oid))
- {
- AclResult aclresult;
-
- aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, OBJECT_INDEX, relation->relname);
- }
+ if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, relation->relname);
/* Lock heap before index to avoid deadlock. */
if (relId != oldRelId)
{
+ Oid table_oid = IndexGetRelation(relId, true);
+
/*
* If the OID isn't valid, it means the index was concurrently
* dropped, which is not a problem for us; just return normally.
@@ -2916,7 +2909,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
(params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
ShareUpdateExclusiveLock : ShareLock,
0,
- RangeVarCallbackMaintainsTable, NULL);
+ RangeVarCallbackOwnsTable, NULL);
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, params, isTopLevel);
@@ -2998,8 +2991,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
{
objectOid = get_namespace_oid(objectName, false);
- if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()) &&
- !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
+ if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
objectName);
}
@@ -3011,8 +3003,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("can only reindex the currently open database")));
- if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()) &&
- !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
+ if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
get_database_name(objectOid));
}
@@ -3084,12 +3075,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
continue;
/*
- * We already checked privileges on the database or schema, but we
- * further restrict reindexing shared catalogs to roles with the
- * MAINTAIN privilege on the relation.
+ * The table can be reindexed if the user is superuser, the table
+ * owner, or the database/schema owner (but in the latter case, only
+ * if it's not a shared relation). object_ownercheck includes the
+ * superuser case, and depending on objectKind we already know that
+ * the user has permission to run REINDEX on this database or schema
+ * per the permission checks at the beginning of this routine.
*/
if (classtuple->relisshared &&
- pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
+ !object_ownercheck(RelationRelationId, relid, GetUserId()))
continue;
/*
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 92662cbbc8..40ef4ede26 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -284,7 +284,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
AclMode aclmask;
/* any of these privileges permit any lock mode */
- aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
+ aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
/* SELECT privileges also permit ACCESS SHARE and below */
if (lockmode <= AccessShareLock)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f9a3bdfc3a..ac2e74fa3f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -165,8 +165,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/
matviewOid = RangeVarGetRelidExtended(stmt->relation,
lockmode, 0,
- RangeVarCallbackMaintainsTable,
- NULL);
+ RangeVarCallbackOwnsTable, NULL);
matviewRel = table_open(matviewOid, NoLock);
relowner = matviewRel->rd_rel->relowner;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d985278ac6..313e058482 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16977,16 +16977,15 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
* This is intended as a callback for RangeVarGetRelidExtended(). It allows
* the relation to be locked only if (1) it's a plain or partitioned table,
* materialized view, or TOAST table and (2) the current user is the owner (or
- * the superuser) or has been granted MAINTAIN. This meets the
- * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
- * MATERIALIZED VIEW; we expose it here so that it can be used by all.
+ * the superuser). This meets the permission-checking needs of CLUSTER,
+ * REINDEX TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it
+ * can be used by all.
*/
void
-RangeVarCallbackMaintainsTable(const RangeVar *relation,
- Oid relId, Oid oldRelId, void *arg)
+RangeVarCallbackOwnsTable(const RangeVar *relation,
+ Oid relId, Oid oldRelId, void *arg)
{
char relkind;
- AclResult aclresult;
/* Nothing to do if the relation was not found. */
if (!OidIsValid(relId))
@@ -17007,9 +17006,8 @@ RangeVarCallbackMaintainsTable(const RangeVar *relation,
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
/* Check permissions */
- aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, OBJECT_TABLE, relation->relname);
+ if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)), relation->relname);
}
/*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7fe6a54c06..57ca41add2 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -697,35 +697,32 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy,
}
/*
- * Check if the current user has privileges to vacuum or analyze the relation.
- * If not, issue a WARNING log message and return false to let the caller
- * decide what to do with this relation. This routine is used to decide if a
- * relation can be processed for VACUUM or ANALYZE.
+ * Check if a given relation can be safely vacuumed or analyzed. If the
+ * user is not the relation owner, issue a WARNING log message and return
+ * false to let the caller decide what to do with this relation. This
+ * routine is used to decide if a relation can be processed for VACUUM or
+ * ANALYZE.
*/
bool
-vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
- bits32 options)
+vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, bits32 options)
{
char *relname;
Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
/*
- * Privilege checks are bypassed in some cases (e.g., when recursing to a
- * relation's TOAST table).
- */
- if (options & VACOPT_SKIP_PRIVS)
- return true;
-
- /*----------
- * A role has privileges to vacuum or analyze the relation if any of the
- * following are true:
- * - the role owns the current database and the relation is not shared
- * - the role has the MAINTAIN privilege on the relation
- *----------
+ * Check permissions.
+ *
+ * We allow the user to vacuum or analyze a table if he is superuser, the
+ * table owner, or the database owner (but in the latter case, only if
+ * it's not a shared relation). object_ownercheck includes the superuser
+ * case.
+ *
+ * Note we choose to treat permissions failure as a WARNING and keep
+ * trying to vacuum or analyze the rest of the DB --- is this appropriate?
*/
- if ((object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) ||
- pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) == ACLCHECK_OK)
+ if (object_ownercheck(RelationRelationId, relid, GetUserId()) ||
+ (object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared))
return true;
relname = NameStr(reltuple->relname);
@@ -941,10 +938,10 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context,
classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
- * Make a returnable VacuumRelation for this rel if the user has the
- * required privileges.
+ * Make a returnable VacuumRelation for this rel if user is a proper
+ * owner.
*/
- if (vacuum_is_permitted_for_relation(relid, classForm, options))
+ if (vacuum_is_relation_owner(relid, classForm, options))
{
oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation,
@@ -1041,7 +1038,7 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
continue;
/* check permissions of relation */
- if (!vacuum_is_permitted_for_relation(relid, classForm, options))
+ if (!vacuum_is_relation_owner(relid, classForm, options))
continue;
/*
@@ -2034,15 +2031,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
}
/*
- * Check if relation needs to be skipped based on privileges. This check
+ * Check if relation needs to be skipped based on ownership. This check
* happens also when building the relation list to vacuum for a manual
* operation, and needs to be done additionally here as VACUUM could
- * happen across multiple transactions where privileges could have changed
- * in-between. Make sure to only generate logs for VACUUM in this case.
+ * happen across multiple transactions where relation ownership could have
+ * changed in-between. Make sure to only generate logs for VACUUM in this
+ * case.
*/
- if (!vacuum_is_permitted_for_relation(RelationGetRelid(rel),
- rel->rd_rel,
- params->options & ~VACOPT_ANALYZE))
+ if (!vacuum_is_relation_owner(RelationGetRelid(rel),
+ rel->rd_rel,
+ params->options & VACOPT_VACUUM))
{
relation_close(rel, lmode);
PopActiveSnapshot();
@@ -2228,14 +2226,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
{
VacuumParams toast_vacuum_params;
- /*
- * Force VACOPT_PROCESS_MAIN so vacuum_rel() processes it. Likewise,
- * set VACOPT_SKIP_PRIVS since privileges on the main relation are
- * sufficient to process it.
- */
+ /* force VACOPT_PROCESS_MAIN so vacuum_rel() processes it */
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- toast_vacuum_params.options |= VACOPT_SKIP_PRIVS;
vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index c660fd3e70..883e09393a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -332,9 +332,6 @@ aclparse(const char *s, AclItem *aip, Node *escontext)
case ACL_ALTER_SYSTEM_CHR:
read = ACL_ALTER_SYSTEM;
break;
- case ACL_MAINTAIN_CHR:
- read = ACL_MAINTAIN;
- break;
case 'R': /* ignore old RULE privileges */
read = 0;
break;
@@ -1626,7 +1623,6 @@ makeaclitem(PG_FUNCTION_ARGS)
{"CONNECT", ACL_CONNECT},
{"SET", ACL_SET},
{"ALTER SYSTEM", ACL_ALTER_SYSTEM},
- {"MAINTAIN", ACL_MAINTAIN},
{"RULE", 0}, /* ignore old RULE privileges */
{NULL, 0}
};
@@ -1735,8 +1731,6 @@ convert_aclright_to_string(int aclright)
return "SET";
case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM";
- case ACL_MAINTAIN:
- return "MAINTAIN";
default:
elog(ERROR, "unrecognized aclright: %d", aclright);
return NULL;
@@ -2046,8 +2040,6 @@ convert_table_priv_string(text *priv_type_text)
{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
{"TRIGGER", ACL_TRIGGER},
{"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
- {"MAINTAIN", ACL_MAINTAIN},
- {"MAINTAIN WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_MAINTAIN)},
{"RULE", 0}, /* ignore old RULE privileges */
{"RULE WITH GRANT OPTION", 0},
{NULL, 0}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 0ea96346cb..d2851cf568 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -463,7 +463,6 @@ do { \
CONVERT_PRIV('d', "DELETE");
CONVERT_PRIV('t', "TRIGGER");
CONVERT_PRIV('D', "TRUNCATE");
- CONVERT_PRIV('m', "MAINTAIN");
}
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 63bb4689d4..d42243bf71 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -794,7 +794,7 @@ my %tests = (
\QREVOKE ALL ON TABLES FROM regress_dump_test_role;\E\n
\QALTER DEFAULT PRIVILEGES \E
\QFOR ROLE regress_dump_test_role \E
- \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLES TO regress_dump_test_role;\E
+ \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO regress_dump_test_role;\E
/xm,
like => { %full_runs, section_post_data => 1, },
unlike => { no_privs => 1, },
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 935bb9bd95..e9fddd91eb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1151,7 +1151,7 @@ Keywords_for_list_of_owner_roles, "PUBLIC"
#define Privilege_options_of_grant_and_revoke \
"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \
"CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", "SET", "ALTER SYSTEM", \
-"MAINTAIN", "ALL"
+"ALL"
/* ALTER PROCEDURE options */
#define Alter_procedure_options \
@@ -3881,7 +3881,7 @@ psql_completion(const char *text, int start, int end)
if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
"DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
- "CREATE", "EXECUTE", "USAGE", "MAINTAIN", "ALL");
+ "CREATE", "EXECUTE", "USAGE", "ALL");
else if (TailMatches("GRANT"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
Privilege_options_of_grant_and_revoke);
@@ -3933,7 +3933,7 @@ psql_completion(const char *text, int start, int end)
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|MAINTAIN|ALL"))
+ if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
COMPLETE_WITH("ON");
else if (TailMatches("GRANT", MatchAny))
COMPLETE_WITH("TO");
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 1d7e00b2fd..6b4a0aaaad 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -84,11 +84,6 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
-{ oid => '4549', oid_symbol => 'ROLE_PG_MAINTAIN',
- rolname => 'pg_maintain', rolsuper => 'f', rolinherit => 't',
- rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
- rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 250d89ff88..16b6126669 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -96,9 +96,8 @@ extern void AtEOSubXact_on_commit_actions(bool isCommit,
SubTransactionId mySubid,
SubTransactionId parentSubid);
-extern void RangeVarCallbackMaintainsTable(const RangeVar *relation,
- Oid relId, Oid oldRelId,
- void *arg);
+extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
+ Oid relId, Oid oldRelId, void *arg);
extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index cb5b11ab31..39fbd5f10a 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -191,7 +191,6 @@ typedef struct VacAttrStats
#define VACOPT_DISABLE_PAGE_SKIPPING 0x100 /* don't skip any pages */
#define VACOPT_SKIP_DATABASE_STATS 0x200 /* skip vac_update_datfrozenxid() */
#define VACOPT_ONLY_DATABASE_STATS 0x400 /* only vac_update_datfrozenxid() */
-#define VACOPT_SKIP_PRIVS 0x800 /* skip privilege checks */
/*
* Values used by index_cleanup and truncate params.
@@ -341,8 +340,8 @@ extern bool vacuum_get_cutoffs(Relation rel, const VacuumParams *params,
extern bool vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs);
extern void vac_update_datfrozenxid(void);
extern void vacuum_delay_point(void);
-extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
- bits32 options);
+extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple,
+ bits32 options);
extern Relation vacuum_open_relation(Oid relid, RangeVar *relation,
bits32 options, bool verbose,
LOCKMODE lmode);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3bec90e52..9dca3b6528 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -94,8 +94,7 @@ typedef uint64 AclMode; /* a bitmask of privilege bits */
#define ACL_CONNECT (1<<11) /* for databases */
#define ACL_SET (1<<12) /* for configuration parameters */
#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
-#define ACL_MAINTAIN (1<<14) /* for relations */
-#define N_ACL_RIGHTS 15 /* 1 plus the last 1<<x */
+#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
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f8e1238fa2..aba1afa971 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -148,16 +148,15 @@ typedef struct ArrayType Acl;
#define ACL_CONNECT_CHR 'c'
#define ACL_SET_CHR 's'
#define ACL_ALTER_SYSTEM_CHR 'A'
-#define ACL_MAINTAIN_CHR 'm'
/* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsAm"
+#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsA"
/*
* Bitmasks defining "all rights" for each supported object type
*/
#define ACL_ALL_RIGHTS_COLUMN (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
-#define ACL_ALL_RIGHTS_RELATION (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER|ACL_MAINTAIN)
+#define ACL_ALL_RIGHTS_RELATION (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
#define ACL_ALL_RIGHTS_SEQUENCE (ACL_USAGE|ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_DATABASE (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW (ACL_USAGE)
diff --git a/src/test/isolation/expected/cluster-conflict-partition.out b/src/test/isolation/expected/cluster-conflict-partition.out
index 7be9e56ef1..7acb675c97 100644
--- a/src/test/isolation/expected/cluster-conflict-partition.out
+++ b/src/test/isolation/expected/cluster-conflict-partition.out
@@ -3,7 +3,7 @@ Parsed test spec with 2 sessions
starting permutation: s1_begin s1_lock_parent s2_auth s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
step s1_commit: COMMIT;
step s2_cluster: <... completed>
@@ -11,7 +11,7 @@ step s2_reset: RESET ROLE;
starting permutation: s1_begin s2_auth s1_lock_parent s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
step s1_commit: COMMIT;
@@ -21,14 +21,14 @@ step s2_reset: RESET ROLE;
starting permutation: s1_begin s1_lock_child s2_auth s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
step s1_commit: COMMIT;
step s2_reset: RESET ROLE;
starting permutation: s1_begin s2_auth s1_lock_child s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
step s1_commit: COMMIT;
diff --git a/src/test/isolation/specs/cluster-conflict-partition.spec b/src/test/isolation/specs/cluster-conflict-partition.spec
index 4d38a7f49a..5091f684a9 100644
--- a/src/test/isolation/specs/cluster-conflict-partition.spec
+++ b/src/test/isolation/specs/cluster-conflict-partition.spec
@@ -23,7 +23,7 @@ step s1_lock_child { LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
step s1_commit { COMMIT; }
session s2
-step s2_auth { SET ROLE regress_cluster_part; SET client_min_messages = ERROR; }
+step s2_auth { SET ROLE regress_cluster_part; }
step s2_cluster { CLUSTER cluster_part_tab USING cluster_part_ind; }
step s2_reset { RESET ROLE; }
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index 843f65b448..028eeea7fc 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -280,9 +280,6 @@ sub adjust_old_dumpfile
$dump =~
s {^REVOKE SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE}
{REVOKE ALL ON TABLE}mg;
- $dump =~
- s {^(GRANT SELECT,INSERT,REFERENCES,TRIGGER,TRUNCATE),UPDATE ON TABLE}
- {$1,MAINTAIN,UPDATE ON TABLE}mg;
}
if ($old_version < 14)
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index a13aafff0b..542c2e098c 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -352,9 +352,7 @@ INSERT INTO clstr_3 VALUES (1);
-- this user can only cluster clstr_1 and clstr_3, but the latter
-- has not been clustered
SET SESSION AUTHORIZATION regress_clstr_user;
-SET client_min_messages = ERROR; -- order of "skipping" warnings may vary
CLUSTER;
-RESET client_min_messages;
SELECT * FROM clstr_1 UNION ALL
SELECT * FROM clstr_2 UNION ALL
SELECT * FROM clstr_3;
@@ -502,17 +500,12 @@ CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
CREATE ROLE regress_ptnowner;
CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
-SET SESSION AUTHORIZATION regress_ptnowner;
-CLUSTER ptnowner USING ptnowner_i_idx;
-ERROR: permission denied for table ptnowner
-RESET SESSION AUTHORIZATION;
ALTER TABLE ptnowner OWNER TO regress_ptnowner;
CREATE TEMP TABLE ptnowner_oldnodes AS
SELECT oid, relname, relfilenode FROM pg_partition_tree('ptnowner') AS tree
JOIN pg_class AS c ON c.oid=tree.relid;
SET SESSION AUTHORIZATION regress_ptnowner;
CLUSTER ptnowner USING ptnowner_i_idx;
-WARNING: permission denied to cluster "ptnowner2", skipping it
RESET SESSION AUTHORIZATION;
SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 1473bc3175..acfd9d1f4f 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2831,9 +2831,9 @@ RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: permission denied for table pg_toast_1260
+ERROR: must be owner of table pg_toast_1260
REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: permission denied for index pg_toast_1260_index
+ERROR: must be owner of index pg_toast_1260_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 2b96720e29..6d9498cdd1 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -19,7 +19,7 @@ DETAIL: privileges for table deptest
REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
DROP USER regress_dep_user;
ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it
DETAIL: privileges for table deptest
@@ -67,21 +67,21 @@ CREATE TABLE deptest (a serial primary key, b text);
GRANT ALL ON deptest1 TO regress_dep_user2;
RESET SESSION AUTHORIZATION;
\z deptest1
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+----------+-------+------------------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0 +| |
- | | | regress_dep_user1=a*r*w*d*D*x*t*m*/regress_dep_user0+| |
- | | | regress_dep_user2=arwdDxtm/regress_dep_user1 | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+----------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 +| |
+ | | | regress_dep_user1=a*r*w*d*D*x*t*/regress_dep_user0+| |
+ | | | regress_dep_user2=arwdDxt/regress_dep_user1 | |
(1 row)
DROP OWNED BY regress_dep_user1;
-- all grants revoked
\z deptest1
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+----------+-------+----------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0 | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+---------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 | |
(1 row)
-- table was dropped
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 3e4dfcc2ec..c1e610e62f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2278,9 +2278,9 @@ SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
(1 row)
SELECT * FROM pg_input_error_info('regress_priv_user1=rY', 'aclitem');
- message | detail | hint | sql_error_code
-----------------------------------------------------------+--------+------+----------------
- invalid mode character: must be one of "arwdDxtXUCTcsAm" | | | 22P02
+ message | detail | hint | sql_error_code
+---------------------------------------------------------+--------+------+----------------
+ invalid mode character: must be one of "arwdDxtXUCTcsA" | | | 22P02
(1 row)
--
@@ -2621,38 +2621,38 @@ set session role regress_priv_user4;
grant select on dep_priv_test to regress_priv_user5;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 +| |
- | | | regress_priv_user4=r*/regress_priv_user2 +| |
- | | | regress_priv_user4=r*/regress_priv_user3 +| |
- | | | regress_priv_user5=r/regress_priv_user4 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 +| |
+ | | | regress_priv_user4=r*/regress_priv_user2 +| |
+ | | | regress_priv_user4=r*/regress_priv_user3 +| |
+ | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row)
set session role regress_priv_user2;
revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 +| |
- | | | regress_priv_user4=r*/regress_priv_user3 +| |
- | | | regress_priv_user5=r/regress_priv_user4 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 +| |
+ | | | regress_priv_user4=r*/regress_priv_user3 +| |
+ | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row)
set session role regress_priv_user3;
revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 | |
(1 row)
set session role regress_priv_user1;
@@ -2782,20 +2782,6 @@ LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT;
\c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
--- LOCK TABLE and MAINTAIN permission
-GRANT MAINTAIN ON lock_table TO regress_locktable_user;
-SET SESSION AUTHORIZATION regress_locktable_user;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
-ROLLBACK;
-BEGIN;
-LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
-COMMIT;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
-COMMIT;
-\c
-REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-- clean up
DROP TABLE lock_table;
DROP USER regress_locktable_user;
@@ -2909,59 +2895,3 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient;
--- MAINTAIN
-CREATE ROLE regress_no_maintain;
-CREATE ROLE regress_maintain;
-CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
-CREATE TABLE maintain_test (a INT);
-CREATE INDEX ON maintain_test (a);
-GRANT MAINTAIN ON maintain_test TO regress_maintain;
-CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
-GRANT MAINTAIN ON refresh_test TO regress_maintain;
-CREATE SCHEMA reindex_test;
--- negative tests; should fail
-SET ROLE regress_no_maintain;
-VACUUM maintain_test;
-WARNING: permission denied to vacuum "maintain_test", skipping it
-ANALYZE maintain_test;
-WARNING: permission denied to analyze "maintain_test", skipping it
-VACUUM (ANALYZE) maintain_test;
-WARNING: permission denied to vacuum "maintain_test", skipping it
-CLUSTER maintain_test USING maintain_test_a_idx;
-ERROR: permission denied for table maintain_test
-REFRESH MATERIALIZED VIEW refresh_test;
-ERROR: permission denied for table refresh_test
-REINDEX TABLE maintain_test;
-ERROR: permission denied for table maintain_test
-REINDEX INDEX maintain_test_a_idx;
-ERROR: permission denied for index maintain_test_a_idx
-REINDEX SCHEMA reindex_test;
-ERROR: must be owner of schema reindex_test
-RESET ROLE;
-SET ROLE regress_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-ERROR: must be owner of schema reindex_test
-RESET ROLE;
-SET ROLE regress_maintain_all;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-DROP TABLE maintain_test;
-DROP MATERIALIZED VIEW refresh_test;
-DROP SCHEMA reindex_test;
-DROP ROLE regress_no_maintain;
-DROP ROLE regress_maintain;
-DROP ROLE regress_maintain_all;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index e278346420..4e54976618 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -93,23 +93,23 @@ CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave
CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
USING (cid <> 44);
\dp
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------
- regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| |
- | | | =arwdDxtm/regress_rls_alice | |
- regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | p1: +
- | | | =arwdDxtm/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
- | | | | | FROM uaccount +
- | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
- | | | | | p2r (RESTRICTIVE): +
- | | | | | (u): ((cid <> 44) AND (cid < 50)) +
- | | | | | to: regress_rls_dave +
- | | | | | p1r (RESTRICTIVE): +
- | | | | | (u): (cid <> 44) +
- | | | | | to: regress_rls_dave
- regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| |
- | | | =r/regress_rls_alice | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------------------+----------+-------+---------------------------------------------+-------------------+--------------------------------------------
+ regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =arwdDxt/regress_rls_alice | |
+ regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: +
+ | | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
+ | | | | | FROM uaccount +
+ | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
+ | | | | | p2r (RESTRICTIVE): +
+ | | | | | (u): ((cid <> 44) AND (cid < 50)) +
+ | | | | | to: regress_rls_dave +
+ | | | | | p1r (RESTRICTIVE): +
+ | | | | | (u): (cid <> 44) +
+ | | | | | to: regress_rls_dave
+ regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =r/regress_rls_alice | |
(3 rows)
\d document
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index b7115f8610..6cb9c926c0 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -145,9 +145,7 @@ INSERT INTO clstr_3 VALUES (1);
-- this user can only cluster clstr_1 and clstr_3, but the latter
-- has not been clustered
SET SESSION AUTHORIZATION regress_clstr_user;
-SET client_min_messages = ERROR; -- order of "skipping" warnings may vary
CLUSTER;
-RESET client_min_messages;
SELECT * FROM clstr_1 UNION ALL
SELECT * FROM clstr_2 UNION ALL
SELECT * FROM clstr_3;
@@ -238,9 +236,6 @@ CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
CREATE ROLE regress_ptnowner;
CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
-SET SESSION AUTHORIZATION regress_ptnowner;
-CLUSTER ptnowner USING ptnowner_i_idx;
-RESET SESSION AUTHORIZATION;
ALTER TABLE ptnowner OWNER TO regress_ptnowner;
CREATE TEMP TABLE ptnowner_oldnodes AS
SELECT oid, relname, relfilenode FROM pg_partition_tree('ptnowner') AS tree
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 8d74ed7122..2559c62d0b 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -21,7 +21,7 @@ REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
DROP USER regress_dep_user;
-- now we are OK to drop him
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 134809e8cc..bf0035d96d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1778,21 +1778,6 @@ COMMIT;
\c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
--- LOCK TABLE and MAINTAIN permission
-GRANT MAINTAIN ON lock_table TO regress_locktable_user;
-SET SESSION AUTHORIZATION regress_locktable_user;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
-ROLLBACK;
-BEGIN;
-LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
-COMMIT;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
-COMMIT;
-\c
-REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-
-- clean up
DROP TABLE lock_table;
DROP USER regress_locktable_user;
@@ -1876,56 +1861,3 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient;
-
--- MAINTAIN
-CREATE ROLE regress_no_maintain;
-CREATE ROLE regress_maintain;
-CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
-
-CREATE TABLE maintain_test (a INT);
-CREATE INDEX ON maintain_test (a);
-GRANT MAINTAIN ON maintain_test TO regress_maintain;
-CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
-GRANT MAINTAIN ON refresh_test TO regress_maintain;
-CREATE SCHEMA reindex_test;
-
--- negative tests; should fail
-SET ROLE regress_no_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-SET ROLE regress_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-SET ROLE regress_maintain_all;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-DROP TABLE maintain_test;
-DROP MATERIALIZED VIEW refresh_test;
-DROP SCHEMA reindex_test;
-DROP ROLE regress_no_maintain;
-DROP ROLE regress_maintain;
-DROP ROLE regress_maintain_all;
--
2.25.1
On Thu, 2023-06-29 at 20:53 -0400, Tom Lane wrote:
I think that's a seriously awful kluge. It will mean that things
behave
differently for the owner than for MAINTAIN grantees, which pretty
much
destroys the use-case for that privilege, as well as being very
confusing
and hard to debug.
In version 15, try this:
CREATE USER foo;
CREATE SCHEMA foo AUTHORIZATION foo;
CREATE USER bar;
CREATE SCHEMA bar AUTHORIZATION bar;
\c - foo
CREATE FUNCTION foo.mod10(INT) RETURNS INT IMMUTABLE
LANGUAGE plpgsql AS $$ BEGIN RETURN mod($1,10); END; $$;
CREATE TABLE t(i INT);
-- units digit must be unique
CREATE UNIQUE INDEX t_idx ON t (foo.mod10(i));
INSERT INTO t VALUES(7); -- success
INSERT INTO t VALUES(17); -- fails
GRANT USAGE ON SCHEMA foo TO bar;
GRANT INSERT ON t TO bar;
\c - bar
CREATE FUNCTION bar.mod(INT, INT) RETURNS INT IMMUTABLE
LANGUAGE plpgsql AS $$ BEGIN RETURN $1 + 1000000; END; $$;
SET search_path = bar, pg_catalog;
INSERT INTO foo.t VALUES(7); -- succeeds
\c - foo
SELECT * FROM t;
i
---
7
7
(2 rows)
I'm not sure that everyone in this thread realizes just how broken it
is to depend on search_path in a functional index at all. And doubly so
if it depends on a schema other than pg_catalog in the search_path.
Let's also not forget that logical replication always uses
search_path=pg_catalog, so if you depend on a different search_path for
any function attached to the table (not just functional indexes, also
functions inside expressions or trigger functions), then those are
already broken in version 15. And if a superuser is executing
maintenance commands, there's little reason to think they'll have the
same search path as the user that created the table.
At some point in the very near future (though I realize that point may
come after version 16), we need to lock down the search path in a lot
of cases (not just maintenance commands), and I don't see any way
around that.
Regards,
Jeff Davis
On Thu, Jun 29, 2023 at 10:09:21PM -0700, Nathan Bossart wrote:
On Thu, Jun 29, 2023 at 08:53:56PM -0400, Tom Lane wrote:
I'm leaning to Robert's thought that we need to revert this for now,
and think harder about how to make it work cleanly and safely.Since it sounds like this is headed towards a revert, here's a patch for
removing MAINTAIN and pg_maintain.
I will revert this next week unless opinions change before then. I'm
currently planning to revert on both master and REL_16_STABLE, but another
option would be to keep it on master while we sort out the remaining
issues. Thoughts?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jun 30, 2023 at 11:35:46AM -0700, Nathan Bossart wrote:
On Thu, Jun 29, 2023 at 10:09:21PM -0700, Nathan Bossart wrote:
On Thu, Jun 29, 2023 at 08:53:56PM -0400, Tom Lane wrote:
I'm leaning to Robert's thought that we need to revert this for now,
and think harder about how to make it work cleanly and safely.
Another dimension of compromise could be to make MAINTAIN affect fewer
commands in v16. Un-revert the part of commit 05e1737 affecting just the
commands it still affects. For example, limit MAINTAIN and the 05e1737
behavior change to VACUUM, ANALYZE, and REINDEX. Don't worry about VACUUM or
ANALYZE failing under commit 05e1737, since they would have been failing under
autovacuum since 2018. A problem index expression breaks both autoanalyze and
REINDEX, hence the inclusion of REINDEX. The already-failing argument doesn't
apply to CLUSTER or REFRESH MATERIALIZED VIEW, so those commands could join
MAINTAIN in v17.
Since it sounds like this is headed towards a revert, here's a patch for
removing MAINTAIN and pg_maintain.I will revert this next week unless opinions change before then. I'm
currently planning to revert on both master and REL_16_STABLE, but another
option would be to keep it on master while we sort out the remaining
issues. Thoughts?
From my reading of the objections, I think they're saying that commit 05e1737
arrived too late and that MAINTAIN is unacceptable without commit 05e1737. I
think you'd conform to all objections by pushing the revert to v16 and pushing
a roll-forward of commit 05e1737 to master.
On Sun, Jul 02, 2023 at 08:57:31PM -0700, Noah Misch wrote:
Another dimension of compromise could be to make MAINTAIN affect fewer
commands in v16. Un-revert the part of commit 05e1737 affecting just the
commands it still affects. For example, limit MAINTAIN and the 05e1737
behavior change to VACUUM, ANALYZE, and REINDEX. Don't worry about VACUUM or
ANALYZE failing under commit 05e1737, since they would have been failing under
autovacuum since 2018. A problem index expression breaks both autoanalyze and
REINDEX, hence the inclusion of REINDEX. The already-failing argument doesn't
apply to CLUSTER or REFRESH MATERIALIZED VIEW, so those commands could join
MAINTAIN in v17.
I'm open to compromise if others are, but I'm skeptical that folks will be
okay with too much fancy footwork this late in the game.
Anyway, IMO your argument could extend to CLUSTER and REFRESH, too. If
we're willing to change behavior under the assumption that autovacuum
would've been failing since 2018, then why wouldn't we be willing to change
it everywhere? I suppose someone could have been manually vacuuming with a
special search_path for 5 years to avoid needing to schema-qualify their
index expressions (and would then be surprised that CLUSTER/REFRESH no
longer work), but limiting MAINTAIN to VACUUM, etc. would still break their
use-case, right?
From my reading of the objections, I think they're saying that commit 05e1737
arrived too late and that MAINTAIN is unacceptable without commit 05e1737. I
think you'd conform to all objections by pushing the revert to v16 and pushing
a roll-forward of commit 05e1737 to master.
Okay, I'll adjust my plans accordingly.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jul 03, 2023 at 11:19:14AM -0700, Nathan Bossart wrote:
On Sun, Jul 02, 2023 at 08:57:31PM -0700, Noah Misch wrote:
Another dimension of compromise could be to make MAINTAIN affect fewer
commands in v16. Un-revert the part of commit 05e1737 affecting just the
commands it still affects. For example, limit MAINTAIN and the 05e1737
behavior change to VACUUM, ANALYZE, and REINDEX. Don't worry about VACUUM or
ANALYZE failing under commit 05e1737, since they would have been failing under
autovacuum since 2018. A problem index expression breaks both autoanalyze and
REINDEX, hence the inclusion of REINDEX. The already-failing argument doesn't
apply to CLUSTER or REFRESH MATERIALIZED VIEW, so those commands could join
MAINTAIN in v17.I'm open to compromise if others are, but I'm skeptical that folks will be
okay with too much fancy footwork this late in the game.
Got it.
Anyway, IMO your argument could extend to CLUSTER and REFRESH, too. If
we're willing to change behavior under the assumption that autovacuum
would've been failing since 2018, then why wouldn't we be willing to change
it everywhere? I suppose someone could have been manually vacuuming with a
special search_path for 5 years to avoid needing to schema-qualify their
index expressions (and would then be surprised that CLUSTER/REFRESH no
longer work), but limiting MAINTAIN to VACUUM, etc. would still break their
use-case, right?
Yes, limiting MAINTAIN to VACUUM would still break a site that has used manual
VACUUM to work around associated loss of autovacuum. I'm not sympathetic to a
user who neglected to benefit from the last five years of prep time on this
issue as it affects VACUUM and ANALYZE. REFRESH runs more than index
expressions, e.g. function calls in the targetlist of the materialized view
query. Those targetlist expressions haven't been putting ERRORs in the log
during autovacuum, so REFRESH hasn't had the sort of advance warning that
VACUUM and ANALYZE got.
On Thu, 2023-06-29 at 22:09 -0700, Nathan Bossart wrote:
On Thu, Jun 29, 2023 at 08:53:56PM -0400, Tom Lane wrote:
I'm leaning to Robert's thought that we need to revert this for
now,
and think harder about how to make it work cleanly and safely.Since it sounds like this is headed towards a revert, here's a patch
for
removing MAINTAIN and pg_maintain.
It was difficult to review standalone, so I tried a quick version
myself and ended up with very similar results. The only substantial
difference was that I put back:
+ if (!vacuum_is_relation_owner(relid, classForm,
options))
+ continue;
in get_all_vacuum_rels() whereas your patch left it out -- double-check
that we're doing the right thing there.
Also remember to bump the catversion. Other than that, it looks good to
me.
Regards,
Jeff Davis
On Thu, Jul 06, 2023 at 12:55:14AM -0700, Jeff Davis wrote:
It was difficult to review standalone, so I tried a quick version
myself and ended up with very similar results.
Thanks for taking a look.
The only substantial
difference was that I put back:+ if (!vacuum_is_relation_owner(relid, classForm, options)) + continue;in get_all_vacuum_rels() whereas your patch left it out -- double-check
that we're doing the right thing there.
The privilege check was moved in d46a979, which I think still makes sense,
so I left it there. That might be why it looks like I removed it.
Also remember to bump the catversion. Other than that, it looks good to
me.
Will do.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Here is a new version of the patch that I think is ready for commit (except
it still needs a catversion bump). The only real difference from v1 is in
AdjustUpgrade.pm. From my cross-version pg_upgrade testing, I believe we
can remove the other "privilege-set discrepancy" rule as well.
Since MAINTAIN will no longer exist in v16, we'll also need the following
change applied to v17devel:
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index 843f65b448..d435812c06 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -274,7 +274,7 @@ sub adjust_old_dumpfile
$dump = _mash_view_qualifiers($dump);
}
- if ($old_version >= 14 && $old_version < 16)
+ if ($old_version >= 14 && $old_version < 17)
{
# Fix up some privilege-set discrepancies.
$dump =~
On Thu, Jul 06, 2023 at 10:20:04AM -0700, Nathan Bossart wrote:
On Thu, Jul 06, 2023 at 12:55:14AM -0700, Jeff Davis wrote:
Also remember to bump the catversion. Other than that, it looks good to
me.Will do.
Since we are only reverting from v16, the REL_16_STABLE catversion will be
bumped ahead of the one on master. AFAICT that is okay, but there is also
a chance that someone bumps the catversion on master to the same value.
I'm not sure if this is problem is worth worrying about, but I thought I'd
raise it just in case. I could bump the catversion on master to the
following value to help prevent this scenario, but I'm not wild about
adding unnecessary catversion bumps.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-Revert-MAINTAIN-privilege-and-pg_maintain-predefi.patchtext/x-diff; charset=us-asciiDownload
From c9e6c96fd7f9576470042efb196ebd12eaf66442 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 6 Jul 2023 21:35:33 -0700
Subject: [PATCH v2 1/1] Revert MAINTAIN privilege and pg_maintain predefined
role.
This reverts the following commits: 4dbdb82513, c2122aae63,
5b1a879943, 9e1e9d6560, ff9618e82a, 60684dd834, 4441fc704d,
and b5d6382496. A role with the MAINTAIN privilege may be able to
use search_path tricks to escalate privileges to the table owner.
Unfortunately, it is too late in the v16 development cycle to fix
this by restricting search_path when running maintenance commands.
As of this writing, the plan is to revert this feature from only
v16 and to apply a fix to v17devel.
Bumps catversion.
Reviewed-by: Jeff Davis
Discussion: https://postgr.es/m/E1q7j7Y-000z1H-Hr%40gemulon.postgresql.org
---
doc/src/sgml/ddl.sgml | 35 ++----
doc/src/sgml/func.sgml | 2 +-
.../sgml/ref/alter_default_privileges.sgml | 4 +-
doc/src/sgml/ref/analyze.sgml | 6 +-
doc/src/sgml/ref/cluster.sgml | 10 +-
doc/src/sgml/ref/grant.sgml | 3 +-
doc/src/sgml/ref/lock.sgml | 4 +-
.../sgml/ref/refresh_materialized_view.sgml | 5 +-
doc/src/sgml/ref/reindex.sgml | 23 ++--
doc/src/sgml/ref/revoke.sgml | 2 +-
doc/src/sgml/ref/vacuum.sgml | 6 +-
doc/src/sgml/user-manag.sgml | 12 --
src/backend/catalog/aclchk.c | 15 ---
src/backend/commands/analyze.c | 13 +-
src/backend/commands/cluster.c | 43 ++-----
src/backend/commands/indexcmds.c | 34 +++--
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/matview.c | 3 +-
src/backend/commands/tablecmds.c | 16 ++-
src/backend/commands/vacuum.c | 65 +++++-----
src/backend/utils/adt/acl.c | 8 --
src/bin/pg_dump/dumputils.c | 1 -
src/bin/pg_dump/t/002_pg_dump.pl | 2 +-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/pg_authid.dat | 5 -
src/include/commands/tablecmds.h | 5 +-
src/include/commands/vacuum.h | 5 +-
src/include/nodes/parsenodes.h | 3 +-
src/include/utils/acl.h | 5 +-
.../expected/cluster-conflict-partition.out | 8 +-
.../specs/cluster-conflict-partition.spec | 2 +-
.../perl/PostgreSQL/Test/AdjustUpgrade.pm | 11 --
src/test/regress/expected/cluster.out | 7 --
src/test/regress/expected/create_index.out | 4 +-
src/test/regress/expected/dependency.out | 22 ++--
src/test/regress/expected/privileges.out | 116 ++++--------------
src/test/regress/expected/rowsecurity.out | 34 ++---
src/test/regress/sql/cluster.sql | 5 -
src/test/regress/sql/dependency.sql | 2 +-
src/test/regress/sql/privileges.sql | 68 ----------
40 files changed, 178 insertions(+), 444 deletions(-)
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index e32f8253d0..4317995965 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1718,8 +1718,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>, <literal>USAGE</literal>, <literal>SET</literal>,
- <literal>ALTER SYSTEM</literal>, and <literal>MAINTAIN</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.
@@ -2010,19 +2010,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
</para>
</listitem>
</varlistentry>
-
- <varlistentry id="ddl-priv-maintain">
- <term><literal>MAINTAIN</literal></term>
- <listitem>
- <para>
- Allows <command>VACUUM</command>, <command>ANALYZE</command>,
- <command>CLUSTER</command>, <command>REFRESH MATERIALIZED VIEW</command>,
- <command>REINDEX</command>, and <command>LOCK TABLE</command> on a
- relation.
- </para>
- </listitem>
- </varlistentry>
- </variablelist>
+ </variablelist>
The privileges required by other commands are listed on the
reference page of the respective command.
@@ -2171,11 +2159,6 @@ REVOKE ALL ON accounts FROM PUBLIC;
<entry><literal>A</literal></entry>
<entry><literal>PARAMETER</literal></entry>
</row>
- <row>
- <entry><literal>MAINTAIN</literal></entry>
- <entry><literal>m</literal></entry>
- <entry><literal>TABLE</literal></entry>
- </row>
</tbody>
</tgroup>
</table>
@@ -2266,7 +2249,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
</row>
<row>
<entry><literal>TABLE</literal> (and table-like objects)</entry>
- <entry><literal>arwdDxtm</literal></entry>
+ <entry><literal>arwdDxt</literal></entry>
<entry>none</entry>
<entry><literal>\dp</literal></entry>
</row>
@@ -2325,11 +2308,11 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
<programlisting>
=> \dp mytable
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------+-------+------------------------+-----------------------+----------
- public | mytable | table | miriam=arwdDxtm/miriam+| col1: +|
- | | | =r/miriam +| miriam_rw=rw/miriam |
- | | | admin=arw/miriam | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------+-------+-----------------------+-----------------------+----------
+ public | mytable | table | miriam=arwdDxt/miriam+| col1: +|
+ | | | =r/miriam +| miriam_rw=rw/miriam |
+ | | | admin=arw/miriam | |
(1 row)
</programlisting>
</para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..0b62e0c828 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23545,7 +23545,7 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
are <literal>SELECT</literal>, <literal>INSERT</literal>,
<literal>UPDATE</literal>, <literal>DELETE</literal>,
<literal>TRUNCATE</literal>, <literal>REFERENCES</literal>,
- <literal>TRIGGER</literal>, and <literal>MAINTAIN</literal>.
+ and <literal>TRIGGER</literal>.
</para></entry>
</row>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index a33461fbc2..f1d54f5aa3 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -28,7 +28,7 @@ ALTER DEFAULT PRIVILEGES
<phrase>where <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> is one of:</phrase>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON TABLES
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@@ -51,7 +51,7 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
REVOKE [ GRANT OPTION FOR ]
- { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+ { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON TABLES
FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 954491b5df..aa3e9e1c5f 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -182,9 +182,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
<title>Notes</title>
<para>
- To analyze a table, one must ordinarily have the <literal>MAINTAIN</literal>
- privilege on the table. However, database owners are allowed to
+ To analyze a table, one must ordinarily be the table's owner or a
+ superuser. However, database owners are allowed to
analyze all tables in their databases, except shared catalogs.
+ (The restriction for shared catalogs means that a true database-wide
+ <command>ANALYZE</command> can only be performed by a superuser.)
<command>ANALYZE</command> will skip over any tables that the calling user
does not have permission to analyze.
</para>
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 06f3d269e6..0ed29a5c6d 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -70,8 +70,9 @@ CLUSTER [VERBOSE]
<command>CLUSTER</command> without a
<replaceable class="parameter">table_name</replaceable> reclusters all the
previously-clustered tables in the current database that the calling user
- has privileges for. This form of <command>CLUSTER</command> cannot be
- executed inside a transaction block.
+ owns, or all such tables if called by a superuser. This
+ form of <command>CLUSTER</command> cannot be executed inside a transaction
+ block.
</para>
<para>
@@ -132,11 +133,6 @@ CLUSTER [VERBOSE]
<refsect1>
<title>Notes</title>
- <para>
- To cluster a table, one must have the <literal>MAINTAIN</literal> privilege
- on the table.
- </para>
-
<para>
In cases where you are accessing single rows randomly
within a table, the actual order of the data in the
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 35bf0332c8..1ae5770fbb 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable class="parameter">schema_name</replaceable> [, ...] }
@@ -193,7 +193,6 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
<term><literal>USAGE</literal></term>
<term><literal>SET</literal></term>
<term><literal>ALTER SYSTEM</literal></term>
- <term><literal>MAINTAIN</literal></term>
<listitem>
<para>
Specific types of privileges, as defined in <xref linkend="ddl-priv"/>.
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 070855da18..6ce2518de7 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -166,8 +166,8 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
<para>
To lock a table, the user must have the right privilege for the specified
- <replaceable class="parameter">lockmode</replaceable>.
- If the user has <literal>MAINTAIN</literal>,
+ <replaceable class="parameter">lockmode</replaceable>, or be the table's
+ owner or a superuser. If the user has
<literal>UPDATE</literal>, <literal>DELETE</literal>, or
<literal>TRUNCATE</literal> privileges on the table, any <replaceable
class="parameter">lockmode</replaceable> is permitted. If the user has
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 19737668cd..675d6090f3 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -31,9 +31,8 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
<para>
<command>REFRESH MATERIALIZED VIEW</command> completely replaces the
- contents of a materialized view. To execute this command you must have the
- <literal>MAINTAIN</literal>
- privilege on the materialized view. The old contents are discarded. If
+ contents of a materialized view. To execute this command you must be the
+ owner of the materialized view. The old contents are discarded. If
<literal>WITH DATA</literal> is specified (or defaults) the backing query
is executed to provide the new data, and the materialized view is left in a
scannable state. If <literal>WITH NO DATA</literal> is specified no new
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index bef3486843..21e2e91d89 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -292,21 +292,16 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { DA
</para>
<para>
- Reindexing a single index or table requires
- having the <literal>MAINTAIN</literal> privilege on the
- table. Note that while <command>REINDEX</command> on a partitioned index or
- table requires having the <literal>MAINTAIN</literal> privilege on the
- partitioned table, such commands skip the privilege checks when processing
- the individual partitions. Reindexing a schema or database requires being the
- owner of that schema or database or having privileges of the
- <link linkend="predefined-roles-table"><literal>pg_maintain</literal></link>
- role. Note specifically that it's thus
+ Reindexing a single index or table requires being the owner of that
+ index or table. Reindexing a schema or database requires being the
+ owner of that schema or database. Note specifically that it's thus
possible for non-superusers to rebuild indexes of tables owned by
- other users. However, as a special exception,
- <command>REINDEX DATABASE</command>, <command>REINDEX SCHEMA</command>,
- and <command>REINDEX SYSTEM</command> will skip indexes on shared catalogs
- unless the user has the <literal>MAINTAIN</literal> privilege on the
- catalog.
+ other users. However, as a special exception, when
+ <command>REINDEX DATABASE</command>, <command>REINDEX SCHEMA</command>
+ or <command>REINDEX SYSTEM</command> is issued by a non-superuser,
+ indexes on shared catalogs will be skipped unless the user owns the
+ catalog (which typically won't be the case). Of course, superusers
+ can always reindex anything.
</para>
<para>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a..2db66bbf37 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -22,7 +22,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
REVOKE [ GRANT OPTION FOR ]
- { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
+ { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
ON { [ TABLE ] <replaceable class="parameter">table_name</replaceable> [, ...]
| ALL TABLES IN SCHEMA <replaceable>schema_name</replaceable> [, ...] }
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index c42bbea9e2..65c03bf829 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -444,9 +444,11 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
<title>Notes</title>
<para>
- To vacuum a table, one must ordinarily have the <literal>MAINTAIN</literal>
- privilege on the table. However, database owners are allowed to
+ To vacuum a table, one must ordinarily be the table's owner or a
+ superuser. However, database owners are allowed to
vacuum all tables in their databases, except shared catalogs.
+ (The restriction for shared catalogs means that a true database-wide
+ <command>VACUUM</command> can only be performed by a superuser.)
<command>VACUUM</command> will skip over any tables that the calling user
does not have permission to vacuum.
</para>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index e1540dd481..27c1f3d703 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -683,18 +683,6 @@ DROP ROLE doomed_role;
the <link linkend="sql-checkpoint"><command>CHECKPOINT</command></link>
command.</entry>
</row>
- <row>
- <entry>pg_maintain</entry>
- <entry>Allow executing
- <link linkend="sql-vacuum"><command>VACUUM</command></link>,
- <link linkend="sql-analyze"><command>ANALYZE</command></link>,
- <link linkend="sql-cluster"><command>CLUSTER</command></link>,
- <link linkend="sql-refreshmaterializedview"><command>REFRESH MATERIALIZED VIEW</command></link>,
- <link linkend="sql-reindex"><command>REINDEX</command></link>,
- and <link linkend="sql-lock"><command>LOCK TABLE</command></link> on all
- relations, as if having <literal>MAINTAIN</literal> rights on those
- objects, even without having it explicitly.</entry>
- </row>
<row>
<entry>pg_use_reserved_connections</entry>
<entry>Allow use of connection slots reserved via
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index bc2ad773c9..d1f5dcd8be 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -2612,8 +2612,6 @@ string_to_privilege(const char *privname)
return ACL_SET;
if (strcmp(privname, "alter system") == 0)
return ACL_ALTER_SYSTEM;
- if (strcmp(privname, "maintain") == 0)
- return ACL_MAINTAIN;
if (strcmp(privname, "rule") == 0)
return 0; /* ignore old RULE privileges */
ereport(ERROR,
@@ -2655,8 +2653,6 @@ privilege_to_string(AclMode privilege)
return "SET";
case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM";
- case ACL_MAINTAIN:
- return "MAINTAIN";
default:
elog(ERROR, "unrecognized privilege: %d", (int) privilege);
}
@@ -3388,17 +3384,6 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
has_privs_of_role(roleid, ROLE_PG_WRITE_ALL_DATA))
result |= (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE));
- /*
- * Check if ACL_MAINTAIN is being checked and, if so, and not already set
- * as part of the result, then check if the user is a member of the
- * pg_maintain role, which allows VACUUM, ANALYZE, CLUSTER, REFRESH
- * MATERIALIZED VIEW, and REINDEX on all relations.
- */
- if (mask & ACL_MAINTAIN &&
- !(result & ACL_MAINTAIN) &&
- has_privs_of_role(roleid, ROLE_PG_MAINTAIN))
- result |= ACL_MAINTAIN;
-
return result;
}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fc9a371f9b..7b2fff09fc 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -159,15 +159,16 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
/*
- * Check if relation needs to be skipped based on privileges. This check
+ * Check if relation needs to be skipped based on ownership. This check
* happens also when building the relation list to analyze for a manual
* operation, and needs to be done additionally here as ANALYZE could
- * happen across multiple transactions where privileges could have changed
- * in-between. Make sure to generate only logs for ANALYZE in this case.
+ * happen across multiple transactions where relation ownership could have
+ * changed in-between. Make sure to generate only logs for ANALYZE in
+ * this case.
*/
- if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel),
- onerel->rd_rel,
- params->options & ~VACOPT_VACUUM))
+ if (!vacuum_is_relation_owner(RelationGetRelid(onerel),
+ onerel->rd_rel,
+ params->options & VACOPT_ANALYZE))
{
relation_close(onerel, ShareUpdateExclusiveLock);
return;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 03b24ab90f..92c465c05b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -80,7 +80,6 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
static List *get_tables_to_cluster(MemoryContext cluster_context);
static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context,
Oid indexOid);
-static bool cluster_is_permitted_for_relation(Oid relid, Oid userid);
/*---------------------------------------------------------------------------
@@ -148,8 +147,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
tableOid = RangeVarGetRelidExtended(stmt->relation,
AccessExclusiveLock,
0,
- RangeVarCallbackMaintainsTable,
- NULL);
+ RangeVarCallbackOwnsTable, NULL);
rel = table_open(tableOid, NoLock);
/*
@@ -366,8 +364,8 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
*/
if (recheck)
{
- /* Check that the user still has privileges for the relation */
- if (!cluster_is_permitted_for_relation(tableOid, save_userid))
+ /* Check that the user still owns the relation */
+ if (!object_ownercheck(RelationRelationId, tableOid, save_userid))
{
relation_close(OldHeap, AccessExclusiveLock);
goto out;
@@ -1642,7 +1640,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
/*
- * Get a list of tables that the current user has privileges on and
+ * Get a list of tables that the current user owns and
* have indisclustered set. Return the list in a List * of RelToCluster
* (stored in the specified memory context), each one giving the tableOid
* and the indexOid on which the table is already clustered.
@@ -1659,8 +1657,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
List *rtcs = NIL;
/*
- * Get all indexes that have indisclustered set and that the current user
- * has the appropriate privileges for.
+ * Get all indexes that have indisclustered set and are owned by
+ * appropriate user.
*/
indRelation = table_open(IndexRelationId, AccessShareLock);
ScanKeyInit(&entry,
@@ -1674,7 +1672,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
index = (Form_pg_index) GETSTRUCT(indexTuple);
- if (!cluster_is_permitted_for_relation(index->indrelid, GetUserId()))
+ if (!object_ownercheck(RelationRelationId, index->indrelid, GetUserId()))
continue;
/* Use a permanent memory context for the result list */
@@ -1722,13 +1720,10 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
continue;
- /*
- * It's possible that the user does not have privileges to CLUSTER the
- * leaf partition despite having such privileges on the partitioned
- * table. We skip any partitions which the user is not permitted to
- * CLUSTER.
- */
- if (!cluster_is_permitted_for_relation(relid, GetUserId()))
+ /* Silently skip partitions which the user has no access to. */
+ if (!object_ownercheck(RelationRelationId, relid, GetUserId()) &&
+ (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) ||
+ IsSharedRelation(relid)))
continue;
/* Use a permanent memory context for the result list */
@@ -1744,19 +1739,3 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
return rtcs;
}
-
-/*
- * Return whether userid has privileges to CLUSTER relid. If not, this
- * function emits a WARNING.
- */
-static bool
-cluster_is_permitted_for_relation(Oid relid, Oid userid)
-{
- if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK)
- return true;
-
- ereport(WARNING,
- (errmsg("permission denied to cluster \"%s\", skipping it",
- get_rel_name(relid))));
- return false;
-}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 403f5fc143..02250ae74b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -26,7 +26,6 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_am.h"
-#include "catalog/pg_authid.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
@@ -2829,7 +2828,6 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
char relkind;
struct ReindexIndexCallbackState *state = arg;
LOCKMODE table_lockmode;
- Oid table_oid;
/*
* Lock level here should match table lock in reindex_index() for
@@ -2869,19 +2867,14 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
errmsg("\"%s\" is not an index", relation->relname)));
/* Check permissions */
- table_oid = IndexGetRelation(relId, true);
- if (OidIsValid(table_oid))
- {
- AclResult aclresult;
-
- aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, OBJECT_INDEX, relation->relname);
- }
+ if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, relation->relname);
/* Lock heap before index to avoid deadlock. */
if (relId != oldRelId)
{
+ Oid table_oid = IndexGetRelation(relId, true);
+
/*
* If the OID isn't valid, it means the index was concurrently
* dropped, which is not a problem for us; just return normally.
@@ -2916,7 +2909,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
(params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
ShareUpdateExclusiveLock : ShareLock,
0,
- RangeVarCallbackMaintainsTable, NULL);
+ RangeVarCallbackOwnsTable, NULL);
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, params, isTopLevel);
@@ -2998,8 +2991,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
{
objectOid = get_namespace_oid(objectName, false);
- if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()) &&
- !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
+ if (!object_ownercheck(NamespaceRelationId, objectOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
objectName);
}
@@ -3011,8 +3003,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("can only reindex the currently open database")));
- if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()) &&
- !has_privs_of_role(GetUserId(), ROLE_PG_MAINTAIN))
+ if (!object_ownercheck(DatabaseRelationId, objectOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
get_database_name(objectOid));
}
@@ -3084,12 +3075,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
continue;
/*
- * We already checked privileges on the database or schema, but we
- * further restrict reindexing shared catalogs to roles with the
- * MAINTAIN privilege on the relation.
+ * The table can be reindexed if the user is superuser, the table
+ * owner, or the database/schema owner (but in the latter case, only
+ * if it's not a shared relation). object_ownercheck includes the
+ * superuser case, and depending on objectKind we already know that
+ * the user has permission to run REINDEX on this database or schema
+ * per the permission checks at the beginning of this routine.
*/
if (classtuple->relisshared &&
- pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) != ACLCHECK_OK)
+ !object_ownercheck(RelationRelationId, relid, GetUserId()))
continue;
/*
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 92662cbbc8..40ef4ede26 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -284,7 +284,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
AclMode aclmask;
/* any of these privileges permit any lock mode */
- aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
+ aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
/* SELECT privileges also permit ACCESS SHARE and below */
if (lockmode <= AccessShareLock)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f9a3bdfc3a..ac2e74fa3f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -165,8 +165,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/
matviewOid = RangeVarGetRelidExtended(stmt->relation,
lockmode, 0,
- RangeVarCallbackMaintainsTable,
- NULL);
+ RangeVarCallbackOwnsTable, NULL);
matviewRel = table_open(matviewOid, NoLock);
relowner = matviewRel->rd_rel->relowner;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d985278ac6..313e058482 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16977,16 +16977,15 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
* This is intended as a callback for RangeVarGetRelidExtended(). It allows
* the relation to be locked only if (1) it's a plain or partitioned table,
* materialized view, or TOAST table and (2) the current user is the owner (or
- * the superuser) or has been granted MAINTAIN. This meets the
- * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
- * MATERIALIZED VIEW; we expose it here so that it can be used by all.
+ * the superuser). This meets the permission-checking needs of CLUSTER,
+ * REINDEX TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it
+ * can be used by all.
*/
void
-RangeVarCallbackMaintainsTable(const RangeVar *relation,
- Oid relId, Oid oldRelId, void *arg)
+RangeVarCallbackOwnsTable(const RangeVar *relation,
+ Oid relId, Oid oldRelId, void *arg)
{
char relkind;
- AclResult aclresult;
/* Nothing to do if the relation was not found. */
if (!OidIsValid(relId))
@@ -17007,9 +17006,8 @@ RangeVarCallbackMaintainsTable(const RangeVar *relation,
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
/* Check permissions */
- aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, OBJECT_TABLE, relation->relname);
+ if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)), relation->relname);
}
/*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7fe6a54c06..57ca41add2 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -697,35 +697,32 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy,
}
/*
- * Check if the current user has privileges to vacuum or analyze the relation.
- * If not, issue a WARNING log message and return false to let the caller
- * decide what to do with this relation. This routine is used to decide if a
- * relation can be processed for VACUUM or ANALYZE.
+ * Check if a given relation can be safely vacuumed or analyzed. If the
+ * user is not the relation owner, issue a WARNING log message and return
+ * false to let the caller decide what to do with this relation. This
+ * routine is used to decide if a relation can be processed for VACUUM or
+ * ANALYZE.
*/
bool
-vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
- bits32 options)
+vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple, bits32 options)
{
char *relname;
Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
/*
- * Privilege checks are bypassed in some cases (e.g., when recursing to a
- * relation's TOAST table).
- */
- if (options & VACOPT_SKIP_PRIVS)
- return true;
-
- /*----------
- * A role has privileges to vacuum or analyze the relation if any of the
- * following are true:
- * - the role owns the current database and the relation is not shared
- * - the role has the MAINTAIN privilege on the relation
- *----------
+ * Check permissions.
+ *
+ * We allow the user to vacuum or analyze a table if he is superuser, the
+ * table owner, or the database owner (but in the latter case, only if
+ * it's not a shared relation). object_ownercheck includes the superuser
+ * case.
+ *
+ * Note we choose to treat permissions failure as a WARNING and keep
+ * trying to vacuum or analyze the rest of the DB --- is this appropriate?
*/
- if ((object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) ||
- pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) == ACLCHECK_OK)
+ if (object_ownercheck(RelationRelationId, relid, GetUserId()) ||
+ (object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared))
return true;
relname = NameStr(reltuple->relname);
@@ -941,10 +938,10 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context,
classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
- * Make a returnable VacuumRelation for this rel if the user has the
- * required privileges.
+ * Make a returnable VacuumRelation for this rel if user is a proper
+ * owner.
*/
- if (vacuum_is_permitted_for_relation(relid, classForm, options))
+ if (vacuum_is_relation_owner(relid, classForm, options))
{
oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation,
@@ -1041,7 +1038,7 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
continue;
/* check permissions of relation */
- if (!vacuum_is_permitted_for_relation(relid, classForm, options))
+ if (!vacuum_is_relation_owner(relid, classForm, options))
continue;
/*
@@ -2034,15 +2031,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
}
/*
- * Check if relation needs to be skipped based on privileges. This check
+ * Check if relation needs to be skipped based on ownership. This check
* happens also when building the relation list to vacuum for a manual
* operation, and needs to be done additionally here as VACUUM could
- * happen across multiple transactions where privileges could have changed
- * in-between. Make sure to only generate logs for VACUUM in this case.
+ * happen across multiple transactions where relation ownership could have
+ * changed in-between. Make sure to only generate logs for VACUUM in this
+ * case.
*/
- if (!vacuum_is_permitted_for_relation(RelationGetRelid(rel),
- rel->rd_rel,
- params->options & ~VACOPT_ANALYZE))
+ if (!vacuum_is_relation_owner(RelationGetRelid(rel),
+ rel->rd_rel,
+ params->options & VACOPT_VACUUM))
{
relation_close(rel, lmode);
PopActiveSnapshot();
@@ -2228,14 +2226,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
{
VacuumParams toast_vacuum_params;
- /*
- * Force VACOPT_PROCESS_MAIN so vacuum_rel() processes it. Likewise,
- * set VACOPT_SKIP_PRIVS since privileges on the main relation are
- * sufficient to process it.
- */
+ /* force VACOPT_PROCESS_MAIN so vacuum_rel() processes it */
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- toast_vacuum_params.options |= VACOPT_SKIP_PRIVS;
vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index c660fd3e70..883e09393a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -332,9 +332,6 @@ aclparse(const char *s, AclItem *aip, Node *escontext)
case ACL_ALTER_SYSTEM_CHR:
read = ACL_ALTER_SYSTEM;
break;
- case ACL_MAINTAIN_CHR:
- read = ACL_MAINTAIN;
- break;
case 'R': /* ignore old RULE privileges */
read = 0;
break;
@@ -1626,7 +1623,6 @@ makeaclitem(PG_FUNCTION_ARGS)
{"CONNECT", ACL_CONNECT},
{"SET", ACL_SET},
{"ALTER SYSTEM", ACL_ALTER_SYSTEM},
- {"MAINTAIN", ACL_MAINTAIN},
{"RULE", 0}, /* ignore old RULE privileges */
{NULL, 0}
};
@@ -1735,8 +1731,6 @@ convert_aclright_to_string(int aclright)
return "SET";
case ACL_ALTER_SYSTEM:
return "ALTER SYSTEM";
- case ACL_MAINTAIN:
- return "MAINTAIN";
default:
elog(ERROR, "unrecognized aclright: %d", aclright);
return NULL;
@@ -2046,8 +2040,6 @@ convert_table_priv_string(text *priv_type_text)
{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
{"TRIGGER", ACL_TRIGGER},
{"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
- {"MAINTAIN", ACL_MAINTAIN},
- {"MAINTAIN WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_MAINTAIN)},
{"RULE", 0}, /* ignore old RULE privileges */
{"RULE WITH GRANT OPTION", 0},
{NULL, 0}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 0ea96346cb..d2851cf568 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -463,7 +463,6 @@ do { \
CONVERT_PRIV('d', "DELETE");
CONVERT_PRIV('t', "TRIGGER");
CONVERT_PRIV('D', "TRUNCATE");
- CONVERT_PRIV('m', "MAINTAIN");
}
}
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 63bb4689d4..d42243bf71 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -794,7 +794,7 @@ my %tests = (
\QREVOKE ALL ON TABLES FROM regress_dump_test_role;\E\n
\QALTER DEFAULT PRIVILEGES \E
\QFOR ROLE regress_dump_test_role \E
- \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLES TO regress_dump_test_role;\E
+ \QGRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO regress_dump_test_role;\E
/xm,
like => { %full_runs, section_post_data => 1, },
unlike => { no_privs => 1, },
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 677847e434..48cc37267d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1147,7 +1147,7 @@ static const SchemaQuery Query_for_trigger_of_table = {
#define Privilege_options_of_grant_and_revoke \
"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", \
"CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", "SET", "ALTER SYSTEM", \
-"MAINTAIN", "ALL"
+"ALL"
/* ALTER PROCEDURE options */
#define Alter_procedure_options \
@@ -3850,7 +3850,7 @@ psql_completion(const char *text, int start, int end)
if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
"DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
- "CREATE", "EXECUTE", "USAGE", "MAINTAIN", "ALL");
+ "CREATE", "EXECUTE", "USAGE", "ALL");
else if (TailMatches("GRANT"))
COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
Privilege_options_of_grant_and_revoke);
@@ -3902,7 +3902,7 @@ psql_completion(const char *text, int start, int end)
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|MAINTAIN|ALL"))
+ if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
COMPLETE_WITH("ON");
else if (TailMatches("GRANT", MatchAny))
COMPLETE_WITH("TO");
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 1d7e00b2fd..6b4a0aaaad 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -84,11 +84,6 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
-{ oid => '4549', oid_symbol => 'ROLE_PG_MAINTAIN',
- rolname => 'pg_maintain', rolsuper => 'f', rolinherit => 't',
- rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
- rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 250d89ff88..16b6126669 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -96,9 +96,8 @@ extern void AtEOSubXact_on_commit_actions(bool isCommit,
SubTransactionId mySubid,
SubTransactionId parentSubid);
-extern void RangeVarCallbackMaintainsTable(const RangeVar *relation,
- Oid relId, Oid oldRelId,
- void *arg);
+extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
+ Oid relId, Oid oldRelId, void *arg);
extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index cb5b11ab31..39fbd5f10a 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -191,7 +191,6 @@ typedef struct VacAttrStats
#define VACOPT_DISABLE_PAGE_SKIPPING 0x100 /* don't skip any pages */
#define VACOPT_SKIP_DATABASE_STATS 0x200 /* skip vac_update_datfrozenxid() */
#define VACOPT_ONLY_DATABASE_STATS 0x400 /* only vac_update_datfrozenxid() */
-#define VACOPT_SKIP_PRIVS 0x800 /* skip privilege checks */
/*
* Values used by index_cleanup and truncate params.
@@ -341,8 +340,8 @@ extern bool vacuum_get_cutoffs(Relation rel, const VacuumParams *params,
extern bool vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs);
extern void vac_update_datfrozenxid(void);
extern void vacuum_delay_point(void);
-extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
- bits32 options);
+extern bool vacuum_is_relation_owner(Oid relid, Form_pg_class reltuple,
+ bits32 options);
extern Relation vacuum_open_relation(Oid relid, RangeVar *relation,
bits32 options, bool verbose,
LOCKMODE lmode);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3bec90e52..9dca3b6528 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -94,8 +94,7 @@ typedef uint64 AclMode; /* a bitmask of privilege bits */
#define ACL_CONNECT (1<<11) /* for databases */
#define ACL_SET (1<<12) /* for configuration parameters */
#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
-#define ACL_MAINTAIN (1<<14) /* for relations */
-#define N_ACL_RIGHTS 15 /* 1 plus the last 1<<x */
+#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
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f8e1238fa2..aba1afa971 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -148,16 +148,15 @@ typedef struct ArrayType Acl;
#define ACL_CONNECT_CHR 'c'
#define ACL_SET_CHR 's'
#define ACL_ALTER_SYSTEM_CHR 'A'
-#define ACL_MAINTAIN_CHR 'm'
/* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsAm"
+#define ACL_ALL_RIGHTS_STR "arwdDxtXUCTcsA"
/*
* Bitmasks defining "all rights" for each supported object type
*/
#define ACL_ALL_RIGHTS_COLUMN (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
-#define ACL_ALL_RIGHTS_RELATION (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER|ACL_MAINTAIN)
+#define ACL_ALL_RIGHTS_RELATION (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
#define ACL_ALL_RIGHTS_SEQUENCE (ACL_USAGE|ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_DATABASE (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW (ACL_USAGE)
diff --git a/src/test/isolation/expected/cluster-conflict-partition.out b/src/test/isolation/expected/cluster-conflict-partition.out
index 7be9e56ef1..7acb675c97 100644
--- a/src/test/isolation/expected/cluster-conflict-partition.out
+++ b/src/test/isolation/expected/cluster-conflict-partition.out
@@ -3,7 +3,7 @@ Parsed test spec with 2 sessions
starting permutation: s1_begin s1_lock_parent s2_auth s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
step s1_commit: COMMIT;
step s2_cluster: <... completed>
@@ -11,7 +11,7 @@ step s2_reset: RESET ROLE;
starting permutation: s1_begin s2_auth s1_lock_parent s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
step s1_commit: COMMIT;
@@ -21,14 +21,14 @@ step s2_reset: RESET ROLE;
starting permutation: s1_begin s1_lock_child s2_auth s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
step s1_commit: COMMIT;
step s2_reset: RESET ROLE;
starting permutation: s1_begin s2_auth s1_lock_child s2_cluster s1_commit s2_reset
step s1_begin: BEGIN;
-step s2_auth: SET ROLE regress_cluster_part; SET client_min_messages = ERROR;
+step s2_auth: SET ROLE regress_cluster_part;
step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
step s1_commit: COMMIT;
diff --git a/src/test/isolation/specs/cluster-conflict-partition.spec b/src/test/isolation/specs/cluster-conflict-partition.spec
index 4d38a7f49a..5091f684a9 100644
--- a/src/test/isolation/specs/cluster-conflict-partition.spec
+++ b/src/test/isolation/specs/cluster-conflict-partition.spec
@@ -23,7 +23,7 @@ step s1_lock_child { LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
step s1_commit { COMMIT; }
session s2
-step s2_auth { SET ROLE regress_cluster_part; SET client_min_messages = ERROR; }
+step s2_auth { SET ROLE regress_cluster_part; }
step s2_cluster { CLUSTER cluster_part_tab USING cluster_part_ind; }
step s2_reset { RESET ROLE; }
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index 843f65b448..a241d2ceff 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -274,17 +274,6 @@ sub adjust_old_dumpfile
$dump = _mash_view_qualifiers($dump);
}
- if ($old_version >= 14 && $old_version < 16)
- {
- # Fix up some privilege-set discrepancies.
- $dump =~
- s {^REVOKE SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE}
- {REVOKE ALL ON TABLE}mg;
- $dump =~
- s {^(GRANT SELECT,INSERT,REFERENCES,TRIGGER,TRUNCATE),UPDATE ON TABLE}
- {$1,MAINTAIN,UPDATE ON TABLE}mg;
- }
-
if ($old_version < 14)
{
# Remove mentions of extended hash functions.
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index a13aafff0b..542c2e098c 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -352,9 +352,7 @@ INSERT INTO clstr_3 VALUES (1);
-- this user can only cluster clstr_1 and clstr_3, but the latter
-- has not been clustered
SET SESSION AUTHORIZATION regress_clstr_user;
-SET client_min_messages = ERROR; -- order of "skipping" warnings may vary
CLUSTER;
-RESET client_min_messages;
SELECT * FROM clstr_1 UNION ALL
SELECT * FROM clstr_2 UNION ALL
SELECT * FROM clstr_3;
@@ -502,17 +500,12 @@ CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
CREATE ROLE regress_ptnowner;
CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
-SET SESSION AUTHORIZATION regress_ptnowner;
-CLUSTER ptnowner USING ptnowner_i_idx;
-ERROR: permission denied for table ptnowner
-RESET SESSION AUTHORIZATION;
ALTER TABLE ptnowner OWNER TO regress_ptnowner;
CREATE TEMP TABLE ptnowner_oldnodes AS
SELECT oid, relname, relfilenode FROM pg_partition_tree('ptnowner') AS tree
JOIN pg_class AS c ON c.oid=tree.relid;
SET SESSION AUTHORIZATION regress_ptnowner;
CLUSTER ptnowner USING ptnowner_i_idx;
-WARNING: permission denied to cluster "ptnowner2", skipping it
RESET SESSION AUTHORIZATION;
SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 1473bc3175..acfd9d1f4f 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2831,9 +2831,9 @@ RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: permission denied for table pg_toast_1260
+ERROR: must be owner of table pg_toast_1260
REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: permission denied for index pg_toast_1260_index
+ERROR: must be owner of index pg_toast_1260_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 2b96720e29..6d9498cdd1 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -19,7 +19,7 @@ DETAIL: privileges for table deptest
REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
DROP USER regress_dep_user;
ERROR: role "regress_dep_user" cannot be dropped because some objects depend on it
DETAIL: privileges for table deptest
@@ -67,21 +67,21 @@ CREATE TABLE deptest (a serial primary key, b text);
GRANT ALL ON deptest1 TO regress_dep_user2;
RESET SESSION AUTHORIZATION;
\z deptest1
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+----------+-------+------------------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0 +| |
- | | | regress_dep_user1=a*r*w*d*D*x*t*m*/regress_dep_user0+| |
- | | | regress_dep_user2=arwdDxtm/regress_dep_user1 | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+----------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 +| |
+ | | | regress_dep_user1=a*r*w*d*D*x*t*/regress_dep_user0+| |
+ | | | regress_dep_user2=arwdDxt/regress_dep_user1 | |
(1 row)
DROP OWNED BY regress_dep_user1;
-- all grants revoked
\z deptest1
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+----------+-------+----------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxtm/regress_dep_user0 | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+----------+-------+---------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 | |
(1 row)
-- table was dropped
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 3e4dfcc2ec..c1e610e62f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2278,9 +2278,9 @@ SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
(1 row)
SELECT * FROM pg_input_error_info('regress_priv_user1=rY', 'aclitem');
- message | detail | hint | sql_error_code
-----------------------------------------------------------+--------+------+----------------
- invalid mode character: must be one of "arwdDxtXUCTcsAm" | | | 22P02
+ message | detail | hint | sql_error_code
+---------------------------------------------------------+--------+------+----------------
+ invalid mode character: must be one of "arwdDxtXUCTcsA" | | | 22P02
(1 row)
--
@@ -2621,38 +2621,38 @@ set session role regress_priv_user4;
grant select on dep_priv_test to regress_priv_user5;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 +| |
- | | | regress_priv_user4=r*/regress_priv_user2 +| |
- | | | regress_priv_user4=r*/regress_priv_user3 +| |
- | | | regress_priv_user5=r/regress_priv_user4 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 +| |
+ | | | regress_priv_user4=r*/regress_priv_user2 +| |
+ | | | regress_priv_user4=r*/regress_priv_user3 +| |
+ | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row)
set session role regress_priv_user2;
revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 +| |
- | | | regress_priv_user4=r*/regress_priv_user3 +| |
- | | | regress_priv_user5=r/regress_priv_user4 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 +| |
+ | | | regress_priv_user4=r*/regress_priv_user3 +| |
+ | | | regress_priv_user5=r/regress_priv_user4 | |
(1 row)
set session role regress_priv_user3;
revoke select on dep_priv_test from regress_priv_user4 cascade;
\dp dep_priv_test
Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------+---------------+-------+------------------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| |
- | | | regress_priv_user2=r*/regress_priv_user1 +| |
- | | | regress_priv_user3=r*/regress_priv_user1 | |
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+---------------+-------+-----------------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_priv_user1=arwdDxt/regress_priv_user1+| |
+ | | | regress_priv_user2=r*/regress_priv_user1 +| |
+ | | | regress_priv_user3=r*/regress_priv_user1 | |
(1 row)
set session role regress_priv_user1;
@@ -2782,20 +2782,6 @@ LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT;
\c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
--- LOCK TABLE and MAINTAIN permission
-GRANT MAINTAIN ON lock_table TO regress_locktable_user;
-SET SESSION AUTHORIZATION regress_locktable_user;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
-ROLLBACK;
-BEGIN;
-LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
-COMMIT;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
-COMMIT;
-\c
-REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-- clean up
DROP TABLE lock_table;
DROP USER regress_locktable_user;
@@ -2909,59 +2895,3 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient;
--- MAINTAIN
-CREATE ROLE regress_no_maintain;
-CREATE ROLE regress_maintain;
-CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
-CREATE TABLE maintain_test (a INT);
-CREATE INDEX ON maintain_test (a);
-GRANT MAINTAIN ON maintain_test TO regress_maintain;
-CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
-GRANT MAINTAIN ON refresh_test TO regress_maintain;
-CREATE SCHEMA reindex_test;
--- negative tests; should fail
-SET ROLE regress_no_maintain;
-VACUUM maintain_test;
-WARNING: permission denied to vacuum "maintain_test", skipping it
-ANALYZE maintain_test;
-WARNING: permission denied to analyze "maintain_test", skipping it
-VACUUM (ANALYZE) maintain_test;
-WARNING: permission denied to vacuum "maintain_test", skipping it
-CLUSTER maintain_test USING maintain_test_a_idx;
-ERROR: permission denied for table maintain_test
-REFRESH MATERIALIZED VIEW refresh_test;
-ERROR: permission denied for table refresh_test
-REINDEX TABLE maintain_test;
-ERROR: permission denied for table maintain_test
-REINDEX INDEX maintain_test_a_idx;
-ERROR: permission denied for index maintain_test_a_idx
-REINDEX SCHEMA reindex_test;
-ERROR: must be owner of schema reindex_test
-RESET ROLE;
-SET ROLE regress_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-ERROR: must be owner of schema reindex_test
-RESET ROLE;
-SET ROLE regress_maintain_all;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-DROP TABLE maintain_test;
-DROP MATERIALIZED VIEW refresh_test;
-DROP SCHEMA reindex_test;
-DROP ROLE regress_no_maintain;
-DROP ROLE regress_maintain;
-DROP ROLE regress_maintain_all;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index e278346420..4e54976618 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -93,23 +93,23 @@ CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave
CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
USING (cid <> 44);
\dp
- Access privileges
- Schema | Name | Type | Access privileges | Column privileges | Policies
---------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------
- regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| |
- | | | =arwdDxtm/regress_rls_alice | |
- regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | p1: +
- | | | =arwdDxtm/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
- | | | | | FROM uaccount +
- | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
- | | | | | p2r (RESTRICTIVE): +
- | | | | | (u): ((cid <> 44) AND (cid < 50)) +
- | | | | | to: regress_rls_dave +
- | | | | | p1r (RESTRICTIVE): +
- | | | | | (u): (cid <> 44) +
- | | | | | to: regress_rls_dave
- regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| |
- | | | =r/regress_rls_alice | |
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------------------+----------+-------+---------------------------------------------+-------------------+--------------------------------------------
+ regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =arwdDxt/regress_rls_alice | |
+ regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: +
+ | | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
+ | | | | | FROM uaccount +
+ | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
+ | | | | | p2r (RESTRICTIVE): +
+ | | | | | (u): ((cid <> 44) AND (cid < 50)) +
+ | | | | | to: regress_rls_dave +
+ | | | | | p1r (RESTRICTIVE): +
+ | | | | | (u): (cid <> 44) +
+ | | | | | to: regress_rls_dave
+ regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =r/regress_rls_alice | |
(3 rows)
\d document
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index b7115f8610..6cb9c926c0 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -145,9 +145,7 @@ INSERT INTO clstr_3 VALUES (1);
-- this user can only cluster clstr_1 and clstr_3, but the latter
-- has not been clustered
SET SESSION AUTHORIZATION regress_clstr_user;
-SET client_min_messages = ERROR; -- order of "skipping" warnings may vary
CLUSTER;
-RESET client_min_messages;
SELECT * FROM clstr_1 UNION ALL
SELECT * FROM clstr_2 UNION ALL
SELECT * FROM clstr_3;
@@ -238,9 +236,6 @@ CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1);
CREATE ROLE regress_ptnowner;
CREATE TABLE ptnowner2 PARTITION OF ptnowner FOR VALUES IN (2);
ALTER TABLE ptnowner1 OWNER TO regress_ptnowner;
-SET SESSION AUTHORIZATION regress_ptnowner;
-CLUSTER ptnowner USING ptnowner_i_idx;
-RESET SESSION AUTHORIZATION;
ALTER TABLE ptnowner OWNER TO regress_ptnowner;
CREATE TEMP TABLE ptnowner_oldnodes AS
SELECT oid, relname, relfilenode FROM pg_partition_tree('ptnowner') AS tree
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 8d74ed7122..2559c62d0b 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -21,7 +21,7 @@ REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
DROP GROUP regress_dep_group;
-- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, MAINTAIN ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
DROP USER regress_dep_user;
-- now we are OK to drop him
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 134809e8cc..bf0035d96d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1778,21 +1778,6 @@ COMMIT;
\c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
--- LOCK TABLE and MAINTAIN permission
-GRANT MAINTAIN ON lock_table TO regress_locktable_user;
-SET SESSION AUTHORIZATION regress_locktable_user;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
-ROLLBACK;
-BEGIN;
-LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
-COMMIT;
-BEGIN;
-LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
-COMMIT;
-\c
-REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-
-- clean up
DROP TABLE lock_table;
DROP USER regress_locktable_user;
@@ -1876,56 +1861,3 @@ DROP SCHEMA regress_roleoption;
DROP ROLE regress_roleoption_protagonist;
DROP ROLE regress_roleoption_donor;
DROP ROLE regress_roleoption_recipient;
-
--- MAINTAIN
-CREATE ROLE regress_no_maintain;
-CREATE ROLE regress_maintain;
-CREATE ROLE regress_maintain_all IN ROLE pg_maintain;
-
-CREATE TABLE maintain_test (a INT);
-CREATE INDEX ON maintain_test (a);
-GRANT MAINTAIN ON maintain_test TO regress_maintain;
-CREATE MATERIALIZED VIEW refresh_test AS SELECT 1;
-GRANT MAINTAIN ON refresh_test TO regress_maintain;
-CREATE SCHEMA reindex_test;
-
--- negative tests; should fail
-SET ROLE regress_no_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-SET ROLE regress_maintain;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-SET ROLE regress_maintain_all;
-VACUUM maintain_test;
-ANALYZE maintain_test;
-VACUUM (ANALYZE) maintain_test;
-CLUSTER maintain_test USING maintain_test_a_idx;
-REFRESH MATERIALIZED VIEW refresh_test;
-REINDEX TABLE maintain_test;
-REINDEX INDEX maintain_test_a_idx;
-REINDEX SCHEMA reindex_test;
-RESET ROLE;
-
-DROP TABLE maintain_test;
-DROP MATERIALIZED VIEW refresh_test;
-DROP SCHEMA reindex_test;
-DROP ROLE regress_no_maintain;
-DROP ROLE regress_maintain;
-DROP ROLE regress_maintain_all;
--
2.25.1
On Thu, 2023-07-06 at 22:14 -0700, Nathan Bossart wrote:
Since we are only reverting from v16, the REL_16_STABLE catversion
will be
bumped ahead of the one on master.
I don't object to you doing it this way, but FWIW, I'd just revert in
both branches to avoid this kind of weirdness.
Also I'm not quite sure how quickly my search_path fix will be
committed. Hopefully soon, because the current state is not great, but
it's hard for me to say for sure.
Regards,
Jeff Davis
On Fri, Jul 07, 2023 at 09:22:22AM -0700, Jeff Davis wrote:
On Thu, 2023-07-06 at 22:14 -0700, Nathan Bossart wrote:
Since we are only reverting from v16, the REL_16_STABLE catversion
will be
bumped ahead of the one on master.I don't object to you doing it this way, but FWIW, I'd just revert in
both branches to avoid this kind of weirdness.Also I'm not quite sure how quickly my search_path fix will be
committed. Hopefully soon, because the current state is not great, but
it's hard for me to say for sure.
Yeah, I guess I should just revert it in both. Given your fix will
hopefully be committed soon, I was hoping to avoid reverting and
un-reverting in quick succession to prevent affecting git-blame too much.
I found an example of a post-beta2 revert on both master and a stable
branch where Tom set the catversions to different values (20b6847,
e256312). I'll do the same here.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 07, 2023 at 09:57:10AM -0700, Nathan Bossart wrote:
Yeah, I guess I should just revert it in both. Given your fix will
hopefully be committed soon, I was hoping to avoid reverting and
un-reverting in quick succession to prevent affecting git-blame too much.I found an example of a post-beta2 revert on both master and a stable
branch where Tom set the catversions to different values (20b6847,
e256312). I'll do the same here.
reverted
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jun 30, 2023 at 3:41 AM Jeff Davis <pgsql@j-davis.com> wrote:
I'm not sure that everyone in this thread realizes just how broken it
is to depend on search_path in a functional index at all. And doubly so
if it depends on a schema other than pg_catalog in the search_path.Let's also not forget that logical replication always uses
search_path=pg_catalog, so if you depend on a different search_path for
any function attached to the table (not just functional indexes, also
functions inside expressions or trigger functions), then those are
already broken in version 15. And if a superuser is executing
maintenance commands, there's little reason to think they'll have the
same search path as the user that created the table.At some point in the very near future (though I realize that point may
come after version 16), we need to lock down the search path in a lot
of cases (not just maintenance commands), and I don't see any way
around that.
I agree. I think there are actually two interrelated problems here.
One is that virtually all code needs to run with the originally
intended search_path rather than some search_path chosen at another
time and maybe by a different user. If not, it's going to break, or
compromise security, depending on the situation. The other is that
running arbitrary code written by somebody else as yourself is
basically instant death, from a security perspective.
It's a little hard to imagine a world in which these problems don't
exist at all, but it somehow feels like the design of the system
pushes you toward doing this stuff incorrectly rather than doing it
correctly. For instance, you can imagine a system where when you run
CREATE OR REPLACE FUNCTION, the prevailing search_path is captured and
automatically included in proconfig. Then the default behavior would
be to run functions and procedures with the search_path that was in
effect when they were created, rather than what we actually have,
where it's the one in effect at execution time as it is currently.
It's a little harder to imagine something similar around all the user
switching behavior, just because we have so many ways of triggering
arbitrary code execution: views, triggers, event triggers, expression
indexes, constraints, etc. But you can maybe imagine a system where
all code associated with a table is run as the table owner in all
cases, regardless of SECURITY INVOKER/DEFINER, which I think would at
least close some holes.
The difficulty is that it's a bit hard to imagine making these kinds
of definitional changes now, because they'd probably be breaking
changes for pretty significant numbers of users. But on the other
hand, if we don't start thinking about systemic changes here, it feels
like we're just playing whack-a-mole.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 7/31/23 12:53, Robert Haas wrote:
On Fri, Jun 30, 2023 at 3:41 AM Jeff Davis <pgsql@j-davis.com> wrote:
I'm not sure that everyone in this thread realizes just how broken it
is to depend on search_path in a functional index at all. And doubly so
if it depends on a schema other than pg_catalog in the search_path.Let's also not forget that logical replication always uses
search_path=pg_catalog, so if you depend on a different search_path for
any function attached to the table (not just functional indexes, also
functions inside expressions or trigger functions), then those are
already broken in version 15. And if a superuser is executing
maintenance commands, there's little reason to think they'll have the
same search path as the user that created the table.At some point in the very near future (though I realize that point may
come after version 16), we need to lock down the search path in a lot
of cases (not just maintenance commands), and I don't see any way
around that.I agree. I think there are actually two interrelated problems here.
One is that virtually all code needs to run with the originally
intended search_path rather than some search_path chosen at another
time and maybe by a different user. If not, it's going to break, or
compromise security, depending on the situation. The other is that
running arbitrary code written by somebody else as yourself is
basically instant death, from a security perspective.
I agree too.
But the analysis of the issue needs to go one step further. Even if the
search_path does not change from the originally intended one, a newly
created function can shadow the intended one based on argument coercion
rules.
--
Joe Conway
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Mon, Jul 31, 2023 at 1:18 PM Joe Conway <mail@joeconway.com> wrote:
But the analysis of the issue needs to go one step further. Even if the
search_path does not change from the originally intended one, a newly
created function can shadow the intended one based on argument coercion
rules.
Yeah, this is a complicated issue. As the system works today, if you
include in your search_path a schema to which some other user can
write, you are pretty much agreeing to execute code provided by that
user. If that user has strictly greater privileges than you, e.g. they
are the super-user, then that's fine, because they can compromise your
account anyway, but otherwise, you're probably doomed. Not only can
they try to capture references with similarly-named objects, they can
also do things like create objects whose names are common
mis-spellings of the objects that are supposed to be there and hope
you access the wrong one by mistake. Maybe there are other attacks as
well, but even if not, I think it's already a pretty hopeless
situation. I think the UNIX equivalent would be including a directory
in your PATH that is world-writable and hoping your account will stay
secure. Not very likely.
We have already taken an important step in terms of preventing this
attack in commit b073c3ccd06e4cb845e121387a43faa8c68a7b62, which
removed PUBLIC CREATE from the public schema. Before that, we were
shipping a configuration analogous to a UNIX system where /usr/bin was
world-writable -- something which no actual UNIX system has likely
done any time in the last 40 years, because it's so clearly insane. We
could maybe go a step further by changing the default search_path to
not even include public, to further discourage people from using that
as a catch-all where everybody can just dump their objects. Right now,
anybody can revert that change with a single GRANT statement, and if
we removed public from the default search_path as well, people would
have one extra step to restore that insecure configuration. I don't
know such a change is worthwhile, though. It would still be super-easy
for users to create insecure configurations: as soon as user A can
write a schema and user B has it in the search_path, B is in a lot of
trouble if A turns out to be malicious.
One thing we might be able to do to prevent that sort of thing is to
have a feature to prevent "accidental" code execution, as in the
"function trust" mechanism proposed previously. Say I trust all users
who can SET ROLE to me and/or who inherit my privileges. Additionally
I can decide to trust users who do neither of those things by some
sort of explicit declaration. If I don't trust a user then if I do
anything that would cause code supplied by that user to get executed,
it just errors out:
ERROR: role "rhaas" should not execute arbitrary code provided by role "jconway"
HINT: If this should be allowed, use the TRUST command to permit it.
Even if we do this, I still think we need the kinds of fixes that I
mentioned earlier. An error like this is fine if you're trying to do
something to a table owned by another user and they've got a surprise
trigger on there or something, but a maintenance command like VACUUM
should find a way to work that is both secure and non-astonishing. And
we probably also still need to find ways to control search_path in a
lot more widely than we do today. Otherwise, even if stuff is
technically secure, it may just not work.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, 2023-07-31 at 16:06 -0400, Robert Haas wrote:
if you
include in your search_path a schema to which some other user can
write, you are pretty much agreeing to execute code provided by that
user.
Agreed on all counts here. I don't think it's reasonable for us to try
to make such a setup secure, and I don't think users have much need for
such a setup anyway.
One thing we might be able to do to prevent that sort of thing is to
have a feature to prevent "accidental" code execution, as in the
"function trust" mechanism proposed previously. Say I trust all users
who can SET ROLE to me and/or who inherit my privileges. Additionally
I can decide to trust users who do neither of those things by some
sort of explicit declaration. If I don't trust a user then if I do
anything that would cause code supplied by that user to get executed,
it just errors out:ERROR: role "rhaas" should not execute arbitrary code provided by
role "jconway"
HINT: If this should be allowed, use the TRUST command to permit it.
+1, though I'm not sure we need an extensive trust mechanism beyond
what we already have with the SET ROLE privilege.
And
we probably also still need to find ways to control search_path in a
lot more widely than we do today. Otherwise, even if stuff is
technically secure, it may just not work.
+1.
Regards,
Jeff Davis
On Mon, 2023-07-31 at 12:53 -0400, Robert Haas wrote:
I agree. I think there are actually two interrelated problems here.
One is that virtually all code needs to run with the originally
intended search_path rather than some search_path chosen at another
time and maybe by a different user. If not, it's going to break, or
compromise security, depending on the situation. The other is that
running arbitrary code written by somebody else as yourself is
basically instant death, from a security perspective.
Good framing.
The search_path is a particularly nasty problem in our system because
it means that users can't even trust the code that they write
themselves! A function author has no way to know how their own function
will behave under a different search_path.
It's a little hard to imagine a world in which these problems don't
exist at all, but it somehow feels like the design of the system
pushes you toward doing this stuff incorrectly rather than doing it
correctly. For instance, you can imagine a system where when you run
CREATE OR REPLACE FUNCTION, the prevailing search_path is captured
and
automatically included in proconfig.
Capturing the environment is not ideal either, in my opinion. It makes
it easy to carelessly depend on a schema that others might not have
USAGE privileges on, which would then create a runtime problem for
other callers. Also, I don't think we could just depend on the raw
search_path, we'd need to do some processing for $user, and there are
probably a few other annoyances.
It's one possibility and we don't have a lot of great options, so I
don't want to rule it out though. If nothing else it could be a
transition path to something better.
But you can maybe imagine a system where
all code associated with a table is run as the table owner in all
cases, regardless of SECURITY INVOKER/DEFINER, which I think would at
least close some holes.The difficulty is that it's a bit hard to imagine making these kinds
of definitional changes now, because they'd probably be breaking
changes for pretty significant numbers of users.
I believe we can get close to a good model with minimal breakage. And
when we make the problem small enough I believe other solutions will
emerge. We will probably have to hedge with some compatibility GUCs.
But on the other
hand, if we don't start thinking about systemic changes here, it
feels
like we're just playing whack-a-mole.
Exactly. If we can agree on where we're going then I think we can get
there.
Regards,
Jeff Davis
On Mon, 2023-07-31 at 13:17 -0400, Joe Conway wrote:
But the analysis of the issue needs to go one step further. Even if
the
search_path does not change from the originally intended one, a newly
created function can shadow the intended one based on argument
coercion
rules.
There are quite a few issues going down this path:
* The set of objects in each schema can change. Argument coercion is a
particularly subtle one, but there are other ways that it could find
the wrong object. The temp namespace also has some subtle issues.
* Schema USAGE privileges may vary over time or from caller to caller,
affecting which items in the search path are searched at all. The same
goes if theres an object access hook in place.
* $user should be resolved to a specific schema (or perhaps not in some
cases?)
* There are other GUCs and environment that can affect function
behavior. Is it worth trying to lock those down?
I agree that each of these is some potential problem, but these are
much smaller problems than allowing the caller to have complete control
over the search_path.
Regards,
Jeff Davis
On Mon, Jul 31, 2023 at 5:15 PM Jeff Davis <pgsql@j-davis.com> wrote:
ERROR: role "rhaas" should not execute arbitrary code provided by
role "jconway"
HINT: If this should be allowed, use the TRUST command to permit it.+1, though I'm not sure we need an extensive trust mechanism beyond
what we already have with the SET ROLE privilege.
FWIW, I think it would be a good idea. It might not be absolutely
mandatory but I think it would be smart.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jul 31, 2023 at 6:10 PM Jeff Davis <pgsql@j-davis.com> wrote:
Capturing the environment is not ideal either, in my opinion. It makes
it easy to carelessly depend on a schema that others might not have
USAGE privileges on, which would then create a runtime problem for
other callers. Also, I don't think we could just depend on the raw
search_path, we'd need to do some processing for $user, and there are
probably a few other annoyances.It's one possibility and we don't have a lot of great options, so I
don't want to rule it out though. If nothing else it could be a
transition path to something better.
Here is my thought about this. Right now, we basically do one of two
things. In some cases, we parse statements when they're submitted, and
then store the resulting node trees. In such cases, references are
fixed: the statements will always refer to the objects to which they
referred previously. In functions and procedures, except for the new
BEGIN ATOMIC stuff, we just store the statements as a string and they
get parsed at execution time. Then, the problem arises of statements
being possibly parsed in an environment that differs from the original
one. It can differ either by search_path being different so that we
look in different schemas, or, as you point out here, if the contents
of the schemas themselves have been modified.
I think that a lot of people would like it if we moved more in the
direction of parsing statements at object definition time. Possibly
because EDB deals with a lot of people coming from Oracle, I've heard
a lot of complaints about the PG behavior. However, there are some
fairly significant problems with that idea. First, it would break some
use cases, such as creating a temporary table and then running DML
commands on it, or more generally any use case where a function or
procedure might need to reference objects that don't exist at time of
definition. Second, while we have clear separation of parsing and
execution for queries, the same is not true for DDL commands; it's not
the case, I believe, that you can parse an arbitrary DDL command such
that all object references are resolved, and then later execute it.
We'd need to change a bunch of code to get there. Third, we'd have to
deal with dependency loops: right now, because functions and
procedures don't parse their bodies at definition time, they also
don't depend on the objects that they are going to end up accessing,
which means that a function or procedure can be restored by pg_dump
without worrying about whether those objects exist yet. That would
have to change, and that would mean creating dependency loops for
pg_dump, which we'd have to then find a way to break. I'm not trying
to say that any of these problems are intractable, but I do think
changing stuff like this would be quite a bit of work -- and that's
assuming the user impact was judged to be acceptable, which I'm not at
all sure that it would be. We'd certainly need to provide some
workaround for people who want to do stuff like create and use
temporary tables inside of a function or procedure.
Now, if we don't go in the direction of resolving everything at parse
time, then I think capturing search_path is probably the next best
thing, or at least the next best thing that I've thought up so far. It
doesn't hold constant the meaning of the code to the same degree that
parsing at definition time would do, but it gets us closer to that
than the status quo. Crucially, if the user is using a secure
search_path, then any changes to the meaning of the code that captures
that search_path will have to be made by that user or someone with a
superset of their privileges, which is a lot less serious than what
you get when there's no search_path setting at all, where the *caller*
can change the meaning of the called code. That is not, however, to
say that this idea is really good enough. To be honest, I think it's a
bit of a kludge, and dropping a kludge on top of our entire user base
and maybe also breaking a lot of things is not particularly where I
want to be. I just don't have an idea I like better at the moment.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Aug 1, 2023 at 10:42 AM Robert Haas <robertmhaas@gmail.com> wrote:
Now, if we don't go in the direction of resolving everything at parse
time, then I think capturing search_path is probably the next best
thing, or at least the next best thing that I've thought up so far.
I'd much rather strongly encourage the user to write a safe and
self-sufficient function definition. Specifically, if there is no
search_path attached to the function then the search path that will be in
place will be temp + pg_catalog only. Though I wonder whether it would be
advantageous to allow a function to have a temporary schema separate from
the session-scoped one...
They can use ALTER FUNCTION and the existing "FROM CURRENT" specification
to get back to current behavior if desired.
Going further, I could envision an "explicit" mode that both performs a
parse-time check for object existence and optionally reports an error if
the lookup happened via an inexact match - forcing lots more type casts to
occur (we'd probably need to invent something to say "must match the
anyelement function signature") but ensuring at parse time you've correctly
identified everything you intend to be using. Sure, the meanings of those
things could change but the surface is much smaller than plugging a new
function that matches earlier in the lookup resolution process.
David J.
On Tue, 2023-08-01 at 11:16 -0700, David G. Johnston wrote:
They can use ALTER FUNCTION and the existing "FROM CURRENT"
specification to get back to current behavior if desired.
The current behavior is that the search_path comes from the environment
each execution. FROM CURRENT saves the search_path at definition time
and uses that each execution.
Regards,
Jeff Davis
On Tue, Aug 1, 2023 at 2:38 PM Jeff Davis <pgsql@j-davis.com> wrote:
On Tue, 2023-08-01 at 11:16 -0700, David G. Johnston wrote:
They can use ALTER FUNCTION and the existing "FROM CURRENT"
specification to get back to current behavior if desired.The current behavior is that the search_path comes from the environment
each execution. FROM CURRENT saves the search_path at definition time
and uses that each execution.
Right...I apparently misread "create" as "the" in "when CREATE FUNCTION is
executed".
The overall point stands, it just requires defining a similar "FROM
SESSION" to allow for explicitly specifying the current default (missing)
behavior.
David J.
On Tue, 2023-08-01 at 13:41 -0400, Robert Haas wrote:
In functions and procedures, except for the new
BEGIN ATOMIC stuff, we just store the statements as a string and they
get parsed at execution time.
...
I think that a lot of people would like it if we moved more in the
direction of parsing statements at object definition time.
Do you mean that we'd introduce some BEGIN ATOMIC version of plpgsql
(and other trusted languages)?
However, there are some
fairly significant problems with that idea.
To satisfy intended use cases of things like default expressions, CHECK
constraints, index expressions, etc., there is no need to call
functions that would be subject to the problems you list.
One problem is that functions are too general a concept -- we have no
idea whether the user is trying to do something simple or trying to do
something "interesting".
Now, if we don't go in the direction of resolving everything at parse
time,
It would be useful to pursue this approach, but I don't think it will
be enough. We still need to solve our search_path problems.
then I think capturing search_path is probably the next best
thing,
+0.5.
To be honest, I think it's
a
bit of a kludge, and dropping a kludge on top of our entire user base
and maybe also breaking a lot of things is not particularly where I
want to be. I just don't have an idea I like better at the moment.
We will also be fixing things for a lot of users who just haven't run
into a problem *yet* (or perhaps did, and just don't know it).
Regards,
Jeff Davis
On Tue, 2023-08-01 at 14:47 -0700, David G. Johnston wrote:
The overall point stands, it just requires defining a similar "FROM
SESSION" to allow for explicitly specifying the current default
(missing) behavior.
That sounds useful as a way to future-proof function definitions that
intend to use the session search_path.
It seems like we're moving in the direction of search_path defaulting
to FROM CURRENT (probably with a long road of compatibility GUCs to
minimize breakage...) and everything else defaulting to FROM SESSION
(as before)?
Regards,
Jeff Davis