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 1v7Fwd-00HUsf-LX for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Oct 2025 16:26:15 +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 1v7Fwb-00D9ct-FG for pgsql-hackers@arkaria.postgresql.org; Fri, 10 Oct 2025 16:26:14 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1v7Fwa-00D9cl-Uj for pgsql-hackers@lists.postgresql.org; Fri, 10 Oct 2025 16:26:14 +0000 Received: from mail-io1-xd35.google.com ([2607:f8b0:4864:20::d35]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1v7FwZ-001413-1O for pgsql-hackers@postgresql.org; Fri, 10 Oct 2025 16:26:12 +0000 Received: by mail-io1-xd35.google.com with SMTP id ca18e2360f4ac-917be46c59bso233222839f.1 for ; Fri, 10 Oct 2025 09:26:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1760113571; x=1760718371; 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=Gfl/X9EbuvoHBEbLOzU6ki6dfiyPqFDJut+Myr9vJLo=; b=QhVN2kNfVqGyQ7Dbp+FYPxQD0HhzosUFVFvYlpQpQNU/e7iR+RvuBqoU9WRHPLiIkA bSp9qWSOeYKl2glZ4TCH2YjPhdYqysW4sr8qAOoK3KeT+o2yzO+b22F8GpKuCj1/6rPN pA3NPg+k1zyAM1ZgCmpgnqcxJZr3VgKxsOeI7mL/ezPxExxae5gjW1//Pq1nLUcq2R06 lflyg15cY0CSLrX65txU5m4RIg8ur41qrh7EVhkP1tZ65NtJULbHkeDdUyAqb/O4ze/P KTXTf4gS8A+3Wkyer6bQP+nbPqoEOFg4K0F/iprnpNaglUdpyc35JsqH1Ig6EmpM3OQA 1dzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1760113571; x=1760718371; 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=Gfl/X9EbuvoHBEbLOzU6ki6dfiyPqFDJut+Myr9vJLo=; b=drZXtq9NQwFzu4W4Gx/eGjmUUJmuZ1FMXFvxSVALrFIHPTaaK+alwcBJEriKHNnXKr Yftdqo5pNPliKssOWtK1TQ/TDt9RB6MHA1AeFS9hGV4kgry4rfK5MfcvdLZ57ogk1JBO U+HdV1DWtLDCcqGDkQIwDcXqtlr5wziwvwqx1PnTK1la+kFD6hLVjykPP75XNkIK7rYn THg0mfguEd6TMKGsk7S90/NAP348ttvH1oQZwB2lHH9DhV7X9nhoXJ0BQkhPu1F3dsuW mLaaFqRh3suvppHLflGV8oyxKrsfrriOvzOybJFv+lAL9X6yhLxI1Z4AB6Vq9DVgBA3Z 1vjA== X-Forwarded-Encrypted: i=1; AJvYcCVbY6q3vqL3WkV04Uimpqy06NXifVDDGDwqiDi/QSFv140toKN2qJLvAj3cOuzlUC4I4faUVj/fmi39+Z2y@postgresql.org X-Gm-Message-State: AOJu0Yx+luOiaefs58Gp+XhwecGjOJiQOXDbbVIvA/8AXR2rQJRis8YL OFbn2kOsG6X3F5OQaaQdN1Sg0LyVOXBeasJdJi5juoz/j5YMP0dpIqhd X-Gm-Gg: ASbGncv4W/Z5ecwaTQmA01xILgkpxmP4UHloqfKUJFKbNUbeb3YFhKDwEN45Ebs9uZS rHXaeaVwDcrKS2UgQdN9iivjWfgET26s74TByDKQZD1qwtAIRMbbt+O0ptk4iuv0GK3hQruX23L eUoLYOmkZrEoiDkLMbPOA9pNxtdYIECfruFaQ9VG9BNmBIfRyuvsMavTCsFFbOtAuQ0ZVAX9nEK GIs8aJjTngxMPk/Z8Cy15hS1ceo/QxEh2UBMY/DBoilr1wH3lcZpnlX4k/h8Jf6HzKQme1dokIY NVlXZ73og2+EXeR87LEEtkYL7M/MhnSXg+XbPnBWkvqOXmnErPk2btVhgw2JPOJsodpOqEsklG9 en8BfivycD0QeXJF28MzJ20Ro+PTAHBzB0hCXFdG3tLVq8wALB6Lyrjip4doMXxOmqh3xKE8RQA 8ASxD+oGtyQx1nOfel644aYYWaRj93Ap6SEEdCS7DW3A== X-Google-Smtp-Source: AGHT+IGT4P3MPMiisJbKOxhwzlJngan8qsaK+kD+6RMAm8lpK9goR0MskSC2NmUwrkOIzY2QXS8MUA== X-Received: by 2002:a05:6e02:1fca:b0:42d:8268:5016 with SMTP id e9e14a558f8ab-42f8741068bmr140040165ab.30.1760113570321; Fri, 10 Oct 2025 09:26:10 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id 8926c6da1cb9f-58f6d9059aasm1013635173.32.2025.10.10.09.26.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Oct 2025 09:26:09 -0700 (PDT) Date: Fri, 10 Oct 2025 11:26:08 -0500 From: Nathan Bossart To: Jeff Davis Cc: Tom Lane , Ayush Vatsa , Robert Haas , "David G. Johnston" , PostgreSQL Hackers Subject: Re: Clarification on Role Access Rights to Table Indexes Message-ID: References: <279947.1741535285@sss.pgh.pa.us> <3432170.1758730414@sss.pgh.pa.us> <8af53c6e8992aa706e63aafe60a3bcf100b524d1.camel@j-davis.com> <7b0e2774cdcc8f522ac82f64a8d7266f353a5094.camel@j-davis.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="fHRxQ/rtkbDAFFP4" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --fHRxQ/rtkbDAFFP4 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Thu, Oct 09, 2025 at 04:18:03PM -0500, Nathan Bossart wrote: > There's a similar pattern in get_rel_from_relname() in dblink.c, which also > seems to only be used with an AccessShareLock (like pg_prewarm). My best > guess from reading lots of code, commit messages, and old e-mails in the > archives is that the original check-privileges-before-locking work was > never completed. I added an 0004 that changes dblink to use RangeVarGetRelidExtended(). > I'm currently leaning towards continuing with v4 of the patch set. 0001 > and 0003 are a little weird in that a concurrent change could lead to a > "could not find parent table" ERROR, but IIUC that is an extremely remote > possibility. After sleeping on it, I still think this is the right call. In any case, I've spent way too much time on this stuff, so I plan to commit the attached soon. -- nathan --fHRxQ/rtkbDAFFP4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v5-0001-fix-priv-checks-in-stats-code.patch From 252991d120be8b50c5c3898deee8a723e3c02c02 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:24:28 -0500 Subject: [PATCH v5 1/4] 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) --fHRxQ/rtkbDAFFP4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v5-0002-fix-priv-checks-in-index-code.patch From 700ef73e8c373c57ab1b502d04176b4445442fb1 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:24:36 -0500 Subject: [PATCH v5 2/4] 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) --fHRxQ/rtkbDAFFP4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v5-0003-fix-priv-checks-in-pg_prewarm.patch From d197dec65e1a41c3f0a74b256eb7f9a2ed7c00c1 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 24 Sep 2025 09:47:02 -0500 Subject: [PATCH v5 3/4] 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) --fHRxQ/rtkbDAFFP4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v5-0004-avoid-locking-before-privilege-checks-in-dblink.patch From 8a096e9bf7154df9393f6920579996d7dfe8958e Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 10 Oct 2025 09:37:51 -0500 Subject: [PATCH v5 4/4] avoid locking before privilege checks in dblink --- contrib/dblink/dblink.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 0cf4c27f2e9..1e7696beb50 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -2460,6 +2460,21 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk return NULL; } +static void +RangeVarCallbackForDblink(const RangeVar *relation, + Oid relId, Oid oldRelId, void *arg) +{ + AclResult aclresult; + + if (!OidIsValid(relId)) + return; + + aclresult = pg_class_aclcheck(relId, GetUserId(), *((AclMode *) arg)); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relId)), + relation->relname); +} + /* * Open the relation named by relname_text, acquire specified type of lock, * verify we have specified permissions. @@ -2469,19 +2484,13 @@ static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode) { RangeVar *relvar; - Relation rel; - AclResult aclresult; + Oid relid; relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text)); - rel = table_openrv(relvar, lockmode); - - aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), - aclmode); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), - RelationGetRelationName(rel)); + relid = RangeVarGetRelidExtended(relvar, lockmode, 0, + RangeVarCallbackForDblink, &aclmode); - return rel; + return table_open(relid, NoLock); } /* -- 2.39.5 (Apple Git-154) --fHRxQ/rtkbDAFFP4--