public inbox for [email protected]
help / color / mirror / Atom feedFrom: Nathan Bossart <[email protected]>
To: Tom Lane <[email protected]>
Cc: Ayush Vatsa <[email protected]>
Cc: Robert Haas <[email protected]>
Cc: David G. Johnston <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: Clarification on Role Access Rights to Table Indexes
Date: Wed, 24 Sep 2025 11:52:09 -0500
Message-ID: <aNQhuRQfD3PlpeuT@nathan> (raw)
In-Reply-To: <[email protected]>
References: <CACX+KaO4R9QDxbPSxSB0jNXFsqA6Jf=UPS+tyUvT_YvuP_grVA@mail.gmail.com>
<Z8yxsm9ZWVkHlPbV@nathan>
<CACX+KaP+6U9jf=GT4wpR7TvRvSMtTAhz=vP2Zr+ZdUFVZzqNsA@mail.gmail.com>
<Z8y9RTT-vU6oVI_Y@nathan>
<[email protected]>
<Z8zwVmGzXyDdkAXj@nathan>
<[email protected]>
<Z88CB-vDehJ9rW8u@nathan>
<aNQVIVKarUipPcnW@nathan>
<[email protected]>
On Wed, Sep 24, 2025 at 12:13:34PM -0400, Tom Lane wrote:
> Nathan Bossart <[email protected]> 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
From 9cf6e507808cbd4a5c8b93422881ec078ce66f60 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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)
From 3f6d161c46456b95dfae949ecbe44bd028b6a37d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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)
From 2c1da2d3db13603da4b2cded4a2dcf0d86614ca0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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 <unistd.h>
#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)
Attachments:
[text/plain] v4-0001-fix-priv-checks-in-stats-code.patch (1.1K, 2-v4-0001-fix-priv-checks-in-stats-code.patch)
download | inline diff:
From 9cf6e507808cbd4a5c8b93422881ec078ce66f60 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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)
[text/plain] v4-0002-fix-priv-checks-in-index-code.patch (2.7K, 3-v4-0002-fix-priv-checks-in-index-code.patch)
download | inline diff:
From 3f6d161c46456b95dfae949ecbe44bd028b6a37d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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)
[text/plain] v4-0003-fix-priv-checks-in-pg_prewarm.patch (4.4K, 4-v4-0003-fix-priv-checks-in-pg_prewarm.patch)
download | inline diff:
From 2c1da2d3db13603da4b2cded4a2dcf0d86614ca0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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 <unistd.h>
#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)
view thread (12+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Clarification on Role Access Rights to Table Indexes
In-Reply-To: <aNQhuRQfD3PlpeuT@nathan>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox