On 27/08/26 12:46, Nathan Bossart wrote:
The REPACK decoding worker doesn't pass BGWORKER_BYPASS_ALLOWCONN like
parallel.c does, so it can fail to start. Here is a reproducer:
CREATE DATABASE d;
REVOKE CONNECT ON DATABASE d FROM PUBLIC;
\c d
CREATE ROLE r NOLOGIN;
CREATE TABLE t (a INT PRIMARY KEY);
ALTER TABLE t OWNER TO r;
REPACK t; -- works
REPACK (CONCURRENTLY) t; -- fails
That last command produces the following output:
ERROR: permission denied for database "d"
DETAIL: User does not have CONNECT privilege.
CONTEXT: REPACK decoding worker
This one seems easy enough to fix.
I'm attaching a patch to use BGWORKER_BYPASS_ALLOWCONN on
BackgroundWorkerInitializeConnectionByOid as parallel.c.
I also added a test case for it.
--
Matheus Alcantara
EDB: https://www.enterprisedb.com
From 6df6235fc91eee8be5bac876716539bbd57ad7b5 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <[email protected]>
Date: Thu, 27 Aug 2026 15:07:39 -0300
Subject: [PATCH] Let the REPACK decoding worker bypass connection privilege
checks
REPACK (CONCURRENTLY) launches a background worker to decode the changes
made while the table is being rewritten. Since cluster_rel() switches
the current user to the owner of the table being repacked, that is the
role the worker connects as, and the worker asked for a bypass of the
LOGIN check only. The CONNECT privilege on the database was therefore
still checked against the table owner, who has no reason to have it, so
the command could fail with
ERROR: permission denied for database "d"
DETAIL: User does not have CONNECT privilege.
CONTEXT: REPACK decoding worker
Pass BGWORKER_BYPASS_ALLOWCONN as well, as parallel.c already does for
parallel workers: the backend that launched the worker is connected to
the database already, and the worker only decodes changes on its behalf.
Reported-by: Nathan Bossart <[email protected]>
Author: Matheus Alcantara <[email protected]>
Discussion: https://www.postgresql.org/message-id/apBbzFd_EYAfHV45@nathan
---
contrib/test_decoding/expected/repack.out | 11 +++++++++++
contrib/test_decoding/sql/repack.sql | 12 ++++++++++++
src/backend/commands/repack_worker.c | 9 ++++++++-
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/contrib/test_decoding/expected/repack.out
b/contrib/test_decoding/expected/repack.out
index 5ddc63238c5..4fa945b178f 100644
--- a/contrib/test_decoding/expected/repack.out
+++ b/contrib/test_decoding/expected/repack.out
@@ -29,6 +29,17 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
DROP TABLE ptnowner;
DROP ROLE regress_ptnowner;
+-- The decoding worker runs as the owner of the table being repacked, but it
+-- should not need the CONNECT privilege on the database.
+CREATE ROLE regress_repack_noconn NOLOGIN;
+CREATE TABLE repack_noconn (i int PRIMARY KEY);
+ALTER TABLE repack_noconn OWNER TO regress_repack_noconn;
+SELECT current_database() AS datname \gset
+REVOKE CONNECT ON DATABASE :"datname" FROM PUBLIC;
+REPACK (CONCURRENTLY) repack_noconn;
+GRANT CONNECT ON DATABASE :"datname" TO PUBLIC;
+DROP TABLE repack_noconn;
+DROP ROLE regress_repack_noconn;
-- Verify that REPACK (CONCURRENTLY) doesn't lose "attmissingval" columns
CREATE TABLE rpk_missing (id int PRIMARY KEY);
INSERT INTO rpk_missing SELECT generate_series(1, 3);
diff --git a/contrib/test_decoding/sql/repack.sql
b/contrib/test_decoding/sql/repack.sql
index f461f5479f4..c5245982bb1 100644
--- a/contrib/test_decoding/sql/repack.sql
+++ b/contrib/test_decoding/sql/repack.sql
@@ -24,6 +24,18 @@ SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
DROP TABLE ptnowner;
DROP ROLE regress_ptnowner;
+-- The decoding worker runs as the owner of the table being repacked, but it
+-- should not need the CONNECT privilege on the database.
+CREATE ROLE regress_repack_noconn NOLOGIN;
+CREATE TABLE repack_noconn (i int PRIMARY KEY);
+ALTER TABLE repack_noconn OWNER TO regress_repack_noconn;
+SELECT current_database() AS datname \gset
+REVOKE CONNECT ON DATABASE :"datname" FROM PUBLIC;
+REPACK (CONCURRENTLY) repack_noconn;
+GRANT CONNECT ON DATABASE :"datname" TO PUBLIC;
+DROP TABLE repack_noconn;
+DROP ROLE regress_repack_noconn;
+
-- Verify that REPACK (CONCURRENTLY) doesn't lose "attmissingval" columns
CREATE TABLE rpk_missing (id int PRIMARY KEY);
INSERT INTO rpk_missing SELECT generate_series(1, 3);
diff --git a/src/backend/commands/repack_worker.c
b/src/backend/commands/repack_worker.c
index af7e2a94764..a49b681059f 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -101,8 +101,15 @@ RepackWorkerMain(Datum main_arg)
pq_set_parallel_leader(shared->backend_pid,
shared->backend_proc_number);
- /* Connect to the database. LOGIN is not required. */
+ /*
+ * Connect to the database. Like parallel workers do, we skip the
+ * connection authorization checks. The backend that launched us is
+ * already connected to this database, and the role we run as is the
owner
+ * of the table being repacked, which needs neither the LOGIN attribute
+ * nor the CONNECT privilege for our purposes.
+ */
BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid,
+
BGWORKER_BYPASS_ALLOWCONN |
BGWORKER_BYPASS_ROLELOGINCHECK);
/*
--
2.50.1 (Apple Git-155)