agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedTighten ACL check in repack_is_permitted_for_relation()
136+ messages / 3 participants
[nested] [flat]
* Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-04 08:13 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
0 siblings, 2 replies; 136+ messages in thread
From: Bharath Rupireddy @ 2026-08-04 08:13 UTC (permalink / raw)
To: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>; +Cc: Álvaro Herrera <alvherre@kurilemu.de>
Hi,
(CC-ing Álvaro Herrera for some thoughts)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext() to
silently skip a concurrently-dropped relation. That's wrong for a
caller that may already hold a lock on the relation whose ACL is
checked, where missing a relation is not fine, and it makes the
single-relation REPACK cases more brittle
(https://www.postgresql.org/message-id/akPhEffRipH4isWF@nathan). So
only detect a missing relation where that's expected, following the
fix for vacuum_is_permitted_for_relation() in commit 824d5f6. The new
missing_ok behavior is limited to get_tables_to_repack() and
get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation that
prevents it from being concurrently dropped, so this commit also adds
an assertion to that effect.
I posted this in the thread but starting a new discussion to get some
quick thoughts:
https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
Please find the attached patch for review.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
Attachments:
[application/octet-stream] v2-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch (4.9K, ../../CALj2ACVExk=_m=7nuwVE5Nb3kwsYm1498NSO=u0uOOSWjDCdTQ@mail.gmail.com/2-v2-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch)
download | inline diff:
From fbba58f7382f04b615fcd46e9ec3b132260ecfb3 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Tue, 4 Aug 2026 06:24:19 +0000
Subject: [PATCH v2] Tighten ACL check in repack_is_permitted_for_relation().
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6.
The new missing_ok behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40mail.gmail.com
---
src/backend/commands/repack.c | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index dde56fb1e8d..e686e9b077f 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool missing_ok);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -674,7 +675,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, false))
{
relation_close(OldHeap, lmode);
return false;
@@ -2148,7 +2149,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), true))
continue;
/* Use a permanent memory context for the result list */
@@ -2185,7 +2186,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), true))
continue;
/* Use a permanent memory context for the result list */
@@ -2314,7 +2315,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), true))
continue;
/* Use a permanent memory context for the result list */
@@ -2336,24 +2337,37 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
* Return whether userid has privileges to execute REPACK on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
- *
* If the relation does exist but the user doesn't have the required
* privs, emit a WARNING and return false. Otherwise, return true.
+ *
+ * If missing_ok is true, we silently return false if the relation is
+ * concurrently dropped. Callers without a lock on the relation must specify
+ * missing_ok; all others must hold at least AccessShareLock.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool missing_ok)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(missing_ok ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
- result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+ result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN,
+ missing_ok ? &is_missing : NULL);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. Note that this
+ * is only reachable when the caller specified missing_ok.
+ */
if (is_missing)
+ {
+ Assert(missing_ok);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
^ permalink raw reply [nested|flat] 136+ messages in thread
* Re: Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-14 03:51 Michael Paquier <michael@paquier.xyz>
parent: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
1 sibling, 1 reply; 136+ messages in thread
From: Michael Paquier @ 2026-08-14 03:51 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>; Álvaro Herrera <alvherre@kurilemu.de>
On Tue, Aug 04, 2026 at 01:13:00AM -0700, Bharath Rupireddy wrote:
> I posted this in the thread but starting a new discussion to get some
> quick thoughts:
> https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
>
> Please find the attached patch for review.
Alvaro, could you comment please?
Perhaps this is something that should be an open item for v19 due to
the fact that it is new code and it can make a REPACK operation more
disruptive depending on unlocky circumstances and concurrent activity?
If Alvaro thinks differently, that's fine, as REPACK is his feature.
it just seems to me like one possible option on the table.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (832B, ../../an6QxAYaS6cVjTEL@paquier.xyz/2-signature.asc)
download
^ permalink raw reply [nested|flat] 136+ messages in thread
* Re: Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 03:15 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Michael Paquier <michael@paquier.xyz>
0 siblings, 0 replies; 136+ messages in thread
From: Bharath Rupireddy @ 2026-08-17 03:15 UTC (permalink / raw)
To: Michael Paquier <michael@paquier.xyz>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>; Álvaro Herrera <alvherre@kurilemu.de>
Hi,
On Thu, Aug 13, 2026 at 8:51 PM Michael Paquier <michael@paquier.xyz> wrote:
>
> On Tue, Aug 04, 2026 at 01:13:00AM -0700, Bharath Rupireddy wrote:
> > I posted this in the thread but starting a new discussion to get some
> > quick thoughts:
> > https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
> >
> > Please find the attached patch for review.
>
> Alvaro, could you comment please?
>
> Perhaps this is something that should be an open item for v19 due to
> the fact that it is new code and it can make a REPACK operation more
> disruptive depending on unlocky circumstances and concurrent activity?
> If Alvaro thinks differently, that's fine, as REPACK is his feature.
> it just seems to me like one possible option on the table.
I added this as an open item for PG19 to get some thoughts before the
release. Thanks.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:53 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:53 UTC (permalink / raw)
repack_is_permitted_for_relation() uses pg_class_aclcheck_ext()
to silently skip a concurrently-dropped relation. That's wrong
for a caller that may already hold a lock on the relation whose
ACL is checked, where missing a relation is not fine, and it
makes the single-relation REPACK and CLUSTER cases more brittle.
So only detect a missing relation where that's expected,
following the fix for vacuum_is_permitted_for_relation() in
commit 824d5f6241ea.
The new already_locked behavior is limited to get_tables_to_repack()
and get_tables_to_repack_partitioned(). All other callers of
repack_is_permitted_for_relation() hold a lock on the relation
that prevents it from being concurrently dropped, so this commit
also adds an assertion to that effect.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACX3pyuRS8%2B%2B6L20cJUMRTf_qbbVp69J1btJ3y6%3D77e5gw%40ma...
---
src/backend/commands/repack.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..66b88e28c2e 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -173,7 +173,8 @@ static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
Relation rel,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
- Oid relid, Oid userid);
+ Oid relid, Oid userid,
+ bool already_locked);
static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -681,7 +682,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
/* Check that the user still has privileges for the relation */
- if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
+ if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
{
relation_close(OldHeap, lmode);
return false;
@@ -2155,7 +2156,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2192,7 +2193,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2321,7 +2322,7 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
* if so.
*/
if (!repack_is_permitted_for_relation(stmt->command, table_oid,
- GetUserId()))
+ GetUserId(), false))
continue;
/* Use a permanent memory context for the result list */
@@ -2341,26 +2342,38 @@ get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
/*
- * Return whether userid has privileges to execute REPACK on relid.
+ * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
*
- * Caller may not have a lock on the relation, so it could have been
- * dropped concurrently. In that case, silently return false.
+ * The relation may already be locked by caller, in which case it cannot
+ * possibly go missing; otherwise it can have been removed recently. If
+ * it's been removed, silently return false. If the relation does exist but
+ * the user doesn't have the required privs, emit a WARNING and return false.
*
- * If the relation does exist but the user doesn't have the required
- * privs, emit a WARNING and return false. Otherwise, return true.
+ * Otherwise, return true.
*/
static bool
-repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
+repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
+ bool already_locked)
{
bool is_missing = false;
AclResult result;
char *relname;
Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
+ Assert(!already_locked ||
+ CheckRelationOidLockedByMe(relid, AccessShareLock, true));
result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+
+ /*
+ * If the relation was concurrently dropped, nothing to do. This is only
+ * reachable when the caller doesn't already have a lock on the relation.
+ */
if (is_missing)
+ {
+ Assert(!already_locked);
return false;
+ }
if (result == ACLCHECK_OK)
return true;
--
2.47.3
--lagmlhwoe2cxax2o--
^ permalink raw reply [nested|flat] 136+ messages in thread
* Re: Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 15:56 Álvaro Herrera <alvherre@kurilemu.de>
parent: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
1 sibling, 1 reply; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-17 15:56 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Hello
On 2026-Aug-04, Bharath Rupireddy wrote:
> repack_is_permitted_for_relation() uses pg_class_aclcheck_ext() to
> silently skip a concurrently-dropped relation. That's wrong for a
> caller that may already hold a lock on the relation whose ACL is
> checked, where missing a relation is not fine, and it makes the
> single-relation REPACK cases more brittle
> (https://www.postgresql.org/message-id/akPhEffRipH4isWF@nathan). So
> only detect a missing relation where that's expected, following the
> fix for vacuum_is_permitted_for_relation() in commit 824d5f6.
That makes sense. I think "missing OK" is a bit weird as an argument
here though; I prefer it as "already locked", inverting the boolean.
What do you think of this formulation?
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Learn about compilers. Then everything looks like either a compiler or
a database, and now you have two problems but one of them is fun."
https://twitter.com/thingskatedid/status/1456027786158776329
Attachments:
[text/x-diff] v3-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch (0B, ../../aoMus7tN9VHnB-Jr@alvherre.pgsql/2-v3-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch)
download
^ permalink raw reply [nested|flat] 136+ messages in thread
* Re: Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-17 17:13 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 1 reply; 136+ messages in thread
From: Bharath Rupireddy @ 2026-08-17 17:13 UTC (permalink / raw)
To: Álvaro Herrera <alvherre@kurilemu.de>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Hi,
On Mon, Aug 17, 2026 at 8:56 AM Álvaro Herrera <alvherre@kurilemu.de> wrote:
>
> On 2026-Aug-04, Bharath Rupireddy wrote:
>
> > repack_is_permitted_for_relation() uses pg_class_aclcheck_ext() to
> > silently skip a concurrently-dropped relation. That's wrong for a
> > caller that may already hold a lock on the relation whose ACL is
> > checked, where missing a relation is not fine, and it makes the
> > single-relation REPACK cases more brittle
> > (https://www.postgresql.org/message-id/akPhEffRipH4isWF@nathan). So
> > only detect a missing relation where that's expected, following the
> > fix for vacuum_is_permitted_for_relation() in commit 824d5f6.
>
> That makes sense. I think "missing OK" is a bit weird as an argument
> here though; I prefer it as "already locked", inverting the boolean.
> What do you think of this formulation?
Thanks. That works for me. The v3 patch LGTM. pgindent, make check,
and make check-world are all good.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 136+ messages in thread
* Re: Tighten ACL check in repack_is_permitted_for_relation()
@ 2026-08-19 10:37 Álvaro Herrera <alvherre@kurilemu.de>
parent: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
0 siblings, 0 replies; 136+ messages in thread
From: Álvaro Herrera @ 2026-08-19 10:37 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
On 2026-Aug-17, Bharath Rupireddy wrote:
> Thanks. That works for me. The v3 patch LGTM. pgindent, make check,
> and make check-world are all good.
Thanks! Pushed.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Curiosity is the hope that something wondrous waits just out of sight,
for we are surrounded by a garden of miracles" (Namron)
(Marjorie Liu and Sana Takeda, Monstress: Inferno)
^ permalink raw reply [nested|flat] 136+ messages in thread
end of thread, other threads:[~2026-08-19 10:37 UTC | newest]
Thread overview: 136+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-08-04 08:13 Tighten ACL check in repack_is_permitted_for_relation() Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-14 03:51 ` Michael Paquier <michael@paquier.xyz>
2026-08-17 03:15 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-17 15:56 ` Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 17:13 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-19 10:37 ` Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
2026-08-17 15:53 [PATCH v3] Tighten ACL check in repack_is_permitted_for_relation() Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox