Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1v1Sj4-008vGK-CZ for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Sep 2025 16:52:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1v1Sj3-00Edt9-1y for pgsql-hackers@arkaria.postgresql.org; Wed, 24 Sep 2025 16:52:17 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1v1Sj2-00Edt1-Mg for pgsql-hackers@lists.postgresql.org; Wed, 24 Sep 2025 16:52:16 +0000 Received: from mail-io1-xd35.google.com ([2607:f8b0:4864:20::d35]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1v1Siy-002f3O-1b for pgsql-hackers@postgresql.org; Wed, 24 Sep 2025 16:52:16 +0000 Received: by mail-io1-xd35.google.com with SMTP id ca18e2360f4ac-8877b60f7a5so3931139f.3 for ; Wed, 24 Sep 2025 09:52:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758732732; x=1759337532; darn=postgresql.org; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=54YIiW6FSB2CSUwcPIJ3XAwlv4L9OU3Mp+xfx06Zwyg=; b=NOBN5jB15rBtXWeaBf93NhYu8U1BNJXISh1Vim3sxoExLEQZ4BaDEZZIfAY1hRdfjJ +F+i+pIh2poii6qbi2aNYt270JHHaN+GQIy7HzRLuDX68tJvDrrIUmUoo3H98UeV2IhI o1ij0NEjTOJqUTwfPVUur5XtZXaoKU+5iNmjrfjrYqVXreuPXBalv9PmPto7fkBrtwTU k7uwLwcbeYZUDoznV9MJC5hVQLsYHl2ash3tY+B0bq+56Hp9SgjjK90LJDrszFdJH0Cq wwUZTGQX+Rd4zKwdf690YE4Mrc9P3AvomGCdzVRfy5qvqqJrXlrjF+ZYVqQO+rErYZ93 C8Cw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758732732; x=1759337532; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=54YIiW6FSB2CSUwcPIJ3XAwlv4L9OU3Mp+xfx06Zwyg=; b=w9nbuLP0/aBRFGGTWZ1iQTy1kkmDu/6TnWGE8aHgNanHnOqq+1Y0sn2ks5XjM/wpCG Bjuhh5PPAlUDCrXLwpvOuhximbvOqQ5zFPFORePYIx++i+OrT6MHVziIMR1yh1TL5iND qYnpSTfdI3p0bQFljc0doNMnaHJYCfK4vWvs1XjbqeDaPTqtGNH4OJMm6CPzp+p6dXLT AJ69nDrJp2njzAO9DCOAsANfu6wXOGU24op5yb58VWDliqW3fmsBm+ikbNVRr2UYfqka RYraTLnQPwogtCA+23UHzvOXrntDrTUdJ8izqJdOa2LPe8OE+GcbNyKvXF81EF7CSghG Ci7w== X-Forwarded-Encrypted: i=1; AJvYcCVDscVw/c9FPrUBYJdh8wPX2fhbDgD6JfVWTbv7I4XdOw/4Qe6nQ4hS+6OKrD3XBPG4h8QHgCBDLxgibnbV@postgresql.org X-Gm-Message-State: AOJu0Yw1fUfhJMCudfSP6GZGMRe0Vao2rFQsfsyTpQgS5AAicTa/7IgB MQOdJail4837jCqyjgAM+1IBp01nC6a5ov0AVu33BXdv+MSgnAUA6F+C X-Gm-Gg: ASbGnct5gDz6XyggiS3wfJxTxHh3agC8uuZoxYOraWjJVc9bOnJdbJyZaHc/EOhFLhy ZK/0yB8o6hmSZBOwZI0348tBsGzoTWztH6QoRqfeNc6t86Ot9cpt/CAfo5mGMdqLkyxHzZSKXCx DDSqJxWF8YD4vQHBWapBiMVrTGDhna1E3ITaL1sbFwubuNcwW6qTmm4KBlvdzjNRAk33r1IwQz5 iuI5x/NLibb42Cy2O8Svew5RhHq6aDh9GDSFQUQ2nhj4YNiolQOv2U8Jmh4oI7kYvZpn71/QM1n bNlL62O+yGtHKsI3cKFCaOW49EmBnf8Ko9CQLUB71qoWbKj1Bg7/OqkHkuc2m88Idsl4m2T4zKC o86gHNmZe9l9Gm1D/pj21ZDuRf3HWuoX6ApLZz3lbdToBeP50ZEhZcs1B3BE0pI0GA28eBru7y5 mAZEHQpGsWRUw1 X-Google-Smtp-Source: AGHT+IHXOc75emrlo4hVESej075T6HxVyY7ojh7Ha8xEOHIPkelZT0NLpeo1U3EEvJnHRrJ8gx9rqw== X-Received: by 2002:a05:6e02:3e93:b0:3f6:5e71:1519 with SMTP id e9e14a558f8ab-425955fce57mr5068505ab.4.1758732731911; Wed, 24 Sep 2025 09:52:11 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id e9e14a558f8ab-4244a36ad61sm84777775ab.6.2025.09.24.09.52.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 24 Sep 2025 09:52:11 -0700 (PDT) Date: Wed, 24 Sep 2025 11:52:09 -0500 From: Nathan Bossart To: Tom Lane Cc: Ayush Vatsa , Robert Haas , "David G. Johnston" , PostgreSQL Hackers Subject: Re: Clarification on Role Access Rights to Table Indexes Message-ID: References: <149429.1741472260@sss.pgh.pa.us> <279947.1741535285@sss.pgh.pa.us> <3432170.1758730414@sss.pgh.pa.us> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="mOdMvbysYmLnv0P7" Content-Disposition: inline In-Reply-To: <3432170.1758730414@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --mOdMvbysYmLnv0P7 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Wed, Sep 24, 2025 at 12:13:34PM -0400, Tom Lane wrote: > Nathan Bossart writes: >> * RangeVarCallbackForReindexIndex() was checking privileges on the table >> before locking it, so I reversed it in 0002. > > Don't we do that intentionally, to make sure someone can't cause DOS > on a table they have no privileges on? Ah, right. I switched it back in v4. -- nathan --mOdMvbysYmLnv0P7 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v4-0001-fix-priv-checks-in-stats-code.patch From 9cf6e507808cbd4a5c8b93422881ec078ce66f60 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:24:28 -0500 Subject: [PATCH v4 1/3] fix priv checks in stats code --- src/backend/statistics/stat_utils.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index ef7e5168bed..8b8203a58e3 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -189,6 +189,17 @@ stats_lock_check_privileges(Oid reloid) Assert(index->rd_index && index->rd_index->indrelid == table_oid); + /* + * Since we did the IndexGetRelation() call above without any lock, + * it's barely possible that a race against an index drop/recreation + * could have netted us the wrong table. + */ + if (table_oid != IndexGetRelation(index_oid, true)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("could not find parent table of index \"%s\"", + RelationGetRelationName(index)))); + /* retain lock on index */ relation_close(index, NoLock); } -- 2.39.5 (Apple Git-154) --mOdMvbysYmLnv0P7 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v4-0002-fix-priv-checks-in-index-code.patch From 3f6d161c46456b95dfae949ecbe44bd028b6a37d Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:24:36 -0500 Subject: [PATCH v4 2/3] fix priv checks in index code --- src/backend/commands/indexcmds.c | 41 ++++++++++++-------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index ca2bde62e82..2a6e58eb0de 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -2976,6 +2976,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, struct ReindexIndexCallbackState *state = arg; LOCKMODE table_lockmode; Oid table_oid; + AclResult aclresult; /* * Lock level here should match table lock in reindex_index() for @@ -2985,12 +2986,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ? ShareUpdateExclusiveLock : ShareLock; - /* - * If we previously locked some other index's heap, and the name we're - * looking up no longer refers to that relation, release the now-useless - * lock. - */ - if (relId != oldRelId && OidIsValid(oldRelId)) + /* Unlock any previously locked heap. */ + if (OidIsValid(state->locked_table_oid)) { UnlockRelationOid(state->locked_table_oid, table_lockmode); state->locked_table_oid = InvalidOid; @@ -3014,30 +3011,22 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index", relation->relname))); - /* Check permissions */ + /* + * If the OID isn't valid, it means the index was concurrently dropped, + * which is not a problem for us; just return normally. + */ table_oid = IndexGetRelation(relId, true); - if (OidIsValid(table_oid)) - { - AclResult aclresult; + if (!OidIsValid(table_oid)) + return; - aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, OBJECT_INDEX, relation->relname); - } + /* Check permissions */ + aclresult = pg_class_aclcheck(table_oid, GetUserId(), ACL_MAINTAIN); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_INDEX, relation->relname); /* Lock heap before index to avoid deadlock. */ - if (relId != oldRelId) - { - /* - * If the OID isn't valid, it means the index was concurrently - * dropped, which is not a problem for us; just return normally. - */ - if (OidIsValid(table_oid)) - { - LockRelationOid(table_oid, table_lockmode); - state->locked_table_oid = table_oid; - } - } + LockRelationOid(table_oid, table_lockmode); + state->locked_table_oid = table_oid; } /* -- 2.39.5 (Apple Git-154) --mOdMvbysYmLnv0P7 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v4-0003-fix-priv-checks-in-pg_prewarm.patch From 2c1da2d3db13603da4b2cded4a2dcf0d86614ca0 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:47:02 -0500 Subject: [PATCH v4 3/3] fix priv checks in pg_prewarm --- contrib/pg_prewarm/pg_prewarm.c | 34 +++++++++++++++++++++++++++++-- contrib/pg_prewarm/t/001_basic.pl | 29 +++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c index b968933ea8b..810a291204b 100644 --- a/contrib/pg_prewarm/pg_prewarm.c +++ b/contrib/pg_prewarm/pg_prewarm.c @@ -16,9 +16,11 @@ #include #include "access/relation.h" +#include "catalog/index.h" #include "fmgr.h" #include "miscadmin.h" #include "storage/bufmgr.h" +#include "storage/lmgr.h" #include "storage/read_stream.h" #include "storage/smgr.h" #include "utils/acl.h" @@ -71,6 +73,8 @@ pg_prewarm(PG_FUNCTION_ARGS) char *ttype; PrewarmType ptype; AclResult aclresult; + char relkind; + Oid privOid; /* Basic sanity checking. */ if (PG_ARGISNULL(0)) @@ -107,8 +111,31 @@ pg_prewarm(PG_FUNCTION_ARGS) forkNumber = forkname_to_number(forkString); /* Open relation and check privileges. */ + relkind = get_rel_relkind(relOid); + if (relkind == RELKIND_INDEX || + relkind == RELKIND_PARTITIONED_INDEX) + { + privOid = IndexGetRelation(relOid, false); + LockRelationOid(privOid, AccessShareLock); + } + else + privOid = relOid; + rel = relation_open(relOid, AccessShareLock); - aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_SELECT); + + /* + * If we did the IndexGetRelation() call above, it's barely possible that + * a race against an index drop/recreation could have netted us the wrong + * table. + */ + if (privOid != relOid && + privOid != IndexGetRelation(relOid, true)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("could not find parent table of index \"%s\"", + RelationGetRelationName(rel)))); + + aclresult = pg_class_aclcheck(privOid, GetUserId(), ACL_SELECT); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid)); @@ -233,8 +260,11 @@ pg_prewarm(PG_FUNCTION_ARGS) read_stream_end(stream); } - /* Close relation, release lock. */ + /* Close relation, release locks. */ relation_close(rel, AccessShareLock); + if (privOid != relOid) + UnlockRelationOid(privOid, AccessShareLock); + PG_RETURN_INT64(blocks_done); } diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl index 0a8259d3678..a77ab67d29e 100644 --- a/contrib/pg_prewarm/t/001_basic.pl +++ b/contrib/pg_prewarm/t/001_basic.pl @@ -23,7 +23,9 @@ $node->start; $node->safe_psql("postgres", "CREATE EXTENSION pg_prewarm;\n" . "CREATE TABLE test(c1 int);\n" - . "INSERT INTO test SELECT generate_series(1, 100);"); + . "INSERT INTO test SELECT generate_series(1, 100);\n" + . "CREATE INDEX test_idx ON test(c1);\n" + . "CREATE ROLE test_user LOGIN;"); # test read mode my $result = @@ -42,6 +44,31 @@ ok( ( $stdout =~ qr/^[1-9][0-9]*$/ or $stderr =~ qr/prefetch is not supported by this build/), 'prefetch mode succeeded'); +# test_user should be unable to prewarm table/index without privileges +($cmdret, $stdout, $stderr) = + $node->psql( + "postgres", "SELECT pg_prewarm('test');", + extra_params => [ '--username' => 'test_user' ]); +ok($stderr =~ /permission denied for table test/, 'pg_prewarm failed as expected'); +($cmdret, $stdout, $stderr) = + $node->psql( + "postgres", "SELECT pg_prewarm('test_idx');", + extra_params => [ '--username' => 'test_user' ]); +ok($stderr =~ /permission denied for index test_idx/, 'pg_prewarm failed as expected'); + +# test_user should be able to prewarm table/index with privileges +$node->safe_psql("postgres", "GRANT SELECT ON test TO test_user;"); +$result = + $node->safe_psql( + "postgres", "SELECT pg_prewarm('test');", + extra_params => [ '--username' => 'test_user' ]); +like($result, qr/^[1-9][0-9]*$/, 'pg_prewarm succeeded as expected'); +$result = + $node->safe_psql( + "postgres", "SELECT pg_prewarm('test_idx');", + extra_params => [ '--username' => 'test_user' ]); +like($result, qr/^[1-9][0-9]*$/, 'pg_prewarm succeeded as expected'); + # test autoprewarm_dump_now() $result = $node->safe_psql("postgres", "SELECT autoprewarm_dump_now();"); like($result, qr/^[1-9][0-9]*$/, 'autoprewarm_dump_now succeeded'); -- 2.39.5 (Apple Git-154) --mOdMvbysYmLnv0P7--