0001-Negative-RelfilenumberMap-cache-entries-fro-20250918.patch
text/x-patch
Filename: 0001-Negative-RelfilenumberMap-cache-entries-fro-20250918.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Negative RelfilenumberMap cache entries from pg_filenode_relation()
| File | + | − |
|---|---|---|
| contrib/pg_prewarm/autoprewarm.c | 5 | 1 |
| src/backend/replication/logical/reorderbuffer.c | 6 | 1 |
| src/backend/utils/adt/dbsize.c | 5 | 1 |
| src/backend/utils/cache/relfilenumbermap.c | 13 | 5 |
| src/include/utils/relfilenumbermap.h | 2 | 1 |
From 6a3562b4ac8917c8b577797e5468416a90cc04f5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Thu, 18 Sep 2025 17:24:09 +0530
Subject: [PATCH] Negative RelfilenumberMap cache entries from
pg_filenode_relation()
RelidByRelfilenumber() adds negative entries to the cache. It has three
users, logical replication, autoprewarm and pg_filenode_relation(). The
first two need negative entries in the cache in case they happen to
lookup non-existent mapping again and again. However such mappings will
be smaller in number and usually come from some database object e.g. WAL
or autoprewarm metadata.
But pg_filenode_relation(), which is SQL callable, may be invoked many
times with invalid tablespace and relfilenode pairs, causing the cache
to be bloated with negative cache entries. This can be used as a denial
of service attack since any user can execute it. This commit avoids such
a bloat.
Reported by: Ashutosh Bapat
Author: Ashutosh Bapat
---
contrib/pg_prewarm/autoprewarm.c | 6 +++++-
.../replication/logical/reorderbuffer.c | 7 ++++++-
src/backend/utils/adt/dbsize.c | 6 +++++-
src/backend/utils/cache/relfilenumbermap.c | 18 +++++++++++++-----
src/include/utils/relfilenumbermap.h | 3 ++-
5 files changed, 31 insertions(+), 9 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 8b68dafc261..9732495eaa4 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -540,7 +540,11 @@ autoprewarm_database_main(Datum main_arg)
StartTransactionCommand();
- reloid = RelidByRelfilenumber(blk.tablespace, blk.filenumber);
+ /*
+ * Cache negative entries in case we end up looking up a non-existent
+ * mapping again and again.
+ */
+ reloid = RelidByRelfilenumber(blk.tablespace, blk.filenumber, true);
if (!OidIsValid(reloid) ||
(rel = try_relation_open(reloid, AccessShareLock)) == NULL)
{
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 4736f993c37..b85f426e19f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2326,8 +2326,13 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
case REORDER_BUFFER_CHANGE_DELETE:
Assert(snapshot_now);
+ /*
+ * Cache negative entries in case we end up looking up a
+ * non-existent mapping again and again.
+ */
reloid = RelidByRelfilenumber(change->data.tp.rlocator.spcOid,
- change->data.tp.rlocator.relNumber);
+ change->data.tp.rlocator.relNumber,
+ true);
/*
* Mapped catalog tuple without data, emitted while
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 894d226541f..8bf22daa4c2 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -955,7 +955,11 @@ pg_filenode_relation(PG_FUNCTION_ARGS)
if (!RelFileNumberIsValid(relfilenumber))
PG_RETURN_NULL();
- heaprel = RelidByRelfilenumber(reltablespace, relfilenumber);
+ /*
+ * A malicious user invoke the SQL function with negative entries many
+ * times to consume memory as a DoS attach. Avoid that.
+ */
+ heaprel = RelidByRelfilenumber(reltablespace, relfilenumber, false);
if (!OidIsValid(heaprel))
PG_RETURN_NULL();
diff --git a/src/backend/utils/cache/relfilenumbermap.c b/src/backend/utils/cache/relfilenumbermap.c
index 0b6f9cf3fa1..0aed3058350 100644
--- a/src/backend/utils/cache/relfilenumbermap.c
+++ b/src/backend/utils/cache/relfilenumbermap.c
@@ -135,10 +135,13 @@ InitializeRelfilenumberMap(void)
* identify a temporary relation would require a backend's proc number, which
* we do not know about. Hence, this function ignores this case.
*
+ * If cache_negative is true, negative cache entries (mapping to InvalidOid)
+ * will be stored in the cache. If false, they won't be cached.
+ *
* Returns InvalidOid if no relation matching the criteria could be found.
*/
Oid
-RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
+RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber, bool cache_negative)
{
RelfilenumberMapKey key;
RelfilenumberMapEntry *entry;
@@ -239,11 +242,16 @@ RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
* Only enter entry into cache now, our opening of pg_class could have
* caused cache invalidations to be executed which would have deleted a
* new entry if we had entered it above.
+ *
+ * Cache valid entries always, and negative entries only if requested.
*/
- entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
- if (found)
- elog(ERROR, "corrupted hashtable");
- entry->relid = relid;
+ if (OidIsValid(relid) || cache_negative)
+ {
+ entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
+ if (found)
+ elog(ERROR, "corrupted hashtable");
+ entry->relid = relid;
+ }
return relid;
}
diff --git a/src/include/utils/relfilenumbermap.h b/src/include/utils/relfilenumbermap.h
index 86fadefe499..5799a3257d3 100644
--- a/src/include/utils/relfilenumbermap.h
+++ b/src/include/utils/relfilenumbermap.h
@@ -16,6 +16,7 @@
#include "common/relpath.h"
extern Oid RelidByRelfilenumber(Oid reltablespace,
- RelFileNumber relfilenumber);
+ RelFileNumber relfilenumber,
+ bool cache_negative);
#endif /* RELFILENUMBERMAP_H */
base-commit: 2e66cae935c2e0f7ce9bab6b65ddeb7806f4de7c
--
2.34.1