]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
pg_buffercache: restore rowtype verification in pg_buffercache_pages()
authorFujii Masao <fujii@postgresql.org>
Wed, 10 Jun 2026 03:32:59 +0000 (12:32 +0900)
committerFujii Masao <fujii@postgresql.org>
Wed, 10 Jun 2026 03:32:59 +0000 (12:32 +0900)
Commit 257c8231bf9 changed pg_buffercache_pages() to materialize its output
directly into a tuplestore. As a result, the function ended up trusting
a caller-supplied RECORD descriptors. That could lead to crashes
if the supplied row definition did not match the actual returned values,
for example by passing bool Datums to tuplestore_putvalues() with
an incompatible descriptor.

Fix this by constructing the correct tuple descriptor for
pg_buffercache_pages() and assigning it to
rsinfo->setDesc after InitMaterializedSRF(). This restores the executor's
tupledesc_match() verification, so incompatible caller-supplied
row definitions are rejected with an error, as before commit 257c8231bf9.

Bug: #19508
Reported-by: Nikita Kalinin <n.kalinin@postgrespro.ru>
Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Ashutosh Sharma <ashu.coek88@gmail.com>
Discussion: https://postgr.es/m/19508-e5f188183279219b@postgresql.org

contrib/pg_buffercache/expected/pg_buffercache.out
contrib/pg_buffercache/pg_buffercache_pages.c
contrib/pg_buffercache/sql/pg_buffercache.sql

index 886dea770f62613ecada62b9a9d69597ae9b3a30..c52a8491ff95b801e0d41b1d34d021ecd63dc8cd 100644 (file)
@@ -73,6 +73,14 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
  t
 (1 row)
 
+SELECT *
+FROM pg_buffercache_pages() AS p
+       (bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid,
+        relforknumber smallint, relblocknumber bigint, isdirty text,
+        usagecount smallint)
+LIMIT 1;
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned type boolean at ordinal position 7, but query expects text.
 RESET role;
 ------
 ---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
index bf2e6c972202d6434391b709e21109f033275094..510455998aa749babffc575276f719f4e2d174b9 100644 (file)
@@ -59,6 +59,8 @@ typedef struct
        BufferCacheOsPagesRec *record;
 } BufferCacheOsPagesContext;
 
+static TupleDesc build_buffercache_pages_tupledesc(int natts);
+
 
 /*
  * Function returning data from the shared buffer cache - buffer number,
@@ -86,6 +88,8 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 {
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
        TupleDesc       expected_tupledesc;
+       TupleDesc       actual_tupledesc;
+       MemoryContext oldcontext;
        int                     i;
 
        /*
@@ -105,6 +109,21 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 
        InitMaterializedSRF(fcinfo, 0);
 
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+       actual_tupledesc = build_buffercache_pages_tupledesc(expected_tupledesc->natts);
+       MemoryContextSwitchTo(oldcontext);
+
+       /*
+        * Override the caller-supplied descriptor with the tuple descriptor that
+        * matches the values we actually return, so executor-side
+        * tupledesc_match() can verify the caller's row definition.
+        *
+        * Do not free the previous rsinfo->setDesc here: for RECORD results it
+        * can alias rsinfo->expectedDesc, which the executor still needs to
+        * reference.
+        */
+       rsinfo->setDesc = actual_tupledesc;
+
        /*
         * Scan through all the buffers, adding one row for each of the buffers to
         * the tuplestore.
@@ -205,6 +224,38 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
        return (Datum) 0;
 }
 
+static TupleDesc
+build_buffercache_pages_tupledesc(int natts)
+{
+       TupleDesc       tupledesc;
+
+       tupledesc = CreateTemplateTupleDesc(natts);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
+                                          INT4OID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
+                                          OIDOID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
+                                          OIDOID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
+                                          OIDOID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relforknumber",
+                                          INT2OID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 6, "relblocknumber",
+                                          INT8OID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 7, "isdirty",
+                                          BOOLOID, -1, 0);
+       TupleDescInitEntry(tupledesc, (AttrNumber) 8, "usagecount",
+                                          INT2OID, -1, 0);
+
+       if (natts == NUM_BUFFERCACHE_PAGES_ELEM)
+               TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends",
+                                                  INT4OID, -1, 0);
+
+       TupleDescFinalize(tupledesc);
+
+       return BlessTupleDesc(tupledesc);
+}
+
 /*
  * Inquire about OS pages mappings for shared buffers, with NUMA information,
  * optionally.
index 127d604905ca089e02634e0ea4c94b61b30dd3bf..be89b5f5a3a96f5a3981235a8a537d6dfb9edd00 100644 (file)
@@ -34,6 +34,12 @@ SELECT count(*) > 0 FROM pg_buffercache;
 SELECT count(*) > 0 FROM pg_buffercache_os_pages;
 SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary();
 SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
+SELECT *
+FROM pg_buffercache_pages() AS p
+       (bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid,
+        relforknumber smallint, relblocknumber bigint, isdirty text,
+        usagecount smallint)
+LIMIT 1;
 RESET role;