From 6a3562b4ac8917c8b577797e5468416a90cc04f5 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat 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