BUG #19508: pg_buffercache_pages() crashes the backend with an incompatible caller-supplied record definition
The following bug has been logged on the website:
Bug reference: 19508
Logged by: Nikita Kalinin
Email address: n.kalinin@postgrespro.ru
PostgreSQL version: 19beta1
Operating system: Fedora 44
Description:
Hello,
It appears that pg_buffercache_pages() trusts a caller-supplied record
descriptor without verifying that the declared column types match the actual
values returned by the function.
The crash is reproducible on the current master branch with a fresh cluster
after installing the extension:
CREATE EXTENSION pg_buffercache;
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;
postgres=# select version();
version
-------------------------------------------------------------------------------------------------------------
PostgreSQL 19beta1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 16.1.1
20260515 (Red Hat 16.1.1-2), 64-bit
(1 row)
The only difference from the types documented for pg_buffercache_pages() is
that isdirty is declared as text instead of boolean.
git blame points to the following commit:
commit 257c8231bf97a77378f6fedb826b1243f0a41612 (HEAD)
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue Apr 7 16:04:48 2026 +0300
Modernize and optimize pg_buffercache_pages()
Backtrace:
#0 0x00000000004b2565 in VARATT_CAN_MAKE_SHORT (PTR=<optimized out>)
at ../../../../src/include/varatt.h:419
#1 heap_compute_data_size (tupleDesc=tupleDesc@entry=0x3e5ba110,
values=values@entry=0x7ffd0dc219c0, isnull=isnull@entry=0x7ffd0dc219b4)
at heaptuple.c:239
#2 0x00000000004b3bff in heap_form_minimal_tuple
(tupleDescriptor=0x3e5ba110,
values=values@entry=0x7ffd0dc219c0, isnull=isnull@entry=0x7ffd0dc219b4,
extra=extra@entry=0)
at heaptuple.c:1434
#3 0x0000000000a6fa09 in tuplestore_putvalues (state=0x3e5cc0d8,
tdesc=<optimized out>,
values=values@entry=0x7ffd0dc219c0, isnull=isnull@entry=0x7ffd0dc219b4)
at tuplestore.c:791
#4 0x00007f180fa0447e in pg_buffercache_pages (fcinfo=<optimized out>)
at pg_buffercache_pages.c:202
#5 0x00000000006b7e35 in ExecMakeTableFunctionResult (setexpr=0x3e5b9e98,
econtext=0x3e5b9d38,
argContext=<optimized out>, expectedDesc=0x3e5ba110, randomAccess=false)
at execSRF.c:235
#6 0x00000000006ccc57 in FunctionNext (node=0x3e5b9b28) at
nodeFunctionscan.c:95
#7 0x00000000006daf22 in ExecProcNode (node=0x3e5b9b28)
at ../../../src/include/executor/executor.h:327
#8 ExecLimit (pstate=0x3e5b97b8) at nodeLimit.c:95
#9 0x00000000006ac39a in ExecProcNode (node=0x3e5b97b8)
at ../../../src/include/executor/executor.h:327
#10 ExecutePlan (queryDesc=0x3e5d7d80, operation=CMD_SELECT,
sendTuples=true, numberTuples=0,
direction=<optimized out>, dest=0x3e5e11f8) at execMain.c:1736
#11 standard_ExecutorRun (queryDesc=0x3e5d7d80, direction=<optimized out>,
count=0)
at execMain.c:377
#12 0x00000000008c61d8 in PortalRunSelect (portal=portal@entry=0x3e52f130,
forward=forward@entry=true, count=0, count@entry=9223372036854775807,
dest=dest@entry=0x3e5e11f8) at pquery.c:917
#13 0x00000000008c78be in PortalRun (portal=portal@entry=0x3e52f130,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
dest=dest@entry=0x3e5e11f8, altdest=altdest@entry=0x3e5e11f8,
qc=qc@entry=0x7ffd0dc21e20)
at pquery.c:761
#14 0x00000000008c3548 in exec_simple_query (
query_string=0x3e48b800 "SELECT *\nFROM pg_buffercache_pages() AS p(\n
bufferid integer,\n relfilenode oid,\n reltablespace oid,\n reldatabase
oid,\n relforknumber smallint,\n relblocknumber bigint,\n isdirty text,\n
usagecoun"...) at postgres.c:1290
#15 0x00000000008c5021 in PostgresMain (dbname=<optimized out>,
username=<optimized out>)
at postgres.c:4856
#16 0x00000000008bf01d in BackendMain (startup_data=<optimized out>,
startup_data_len=<optimized out>) at backend_startup.c:124
#17 0x00000000007fefae in postmaster_child_launch (child_type=<optimized
out>, child_slot=1,
startup_data=startup_data@entry=0x7ffd0dc22270,
startup_data_len=startup_data_len@entry=24,
client_sock=client_sock@entry=0x7ffd0dc22290) at launch_backend.c:268
#18 0x00000000008029b6 in BackendStartup (client_sock=0x7ffd0dc22290) at
postmaster.c:3627
#19 ServerLoop () at postmaster.c:1728
#20 0x0000000000804479 in PostmasterMain (argc=argc@entry=3,
argv=argv@entry=0x3e434fe0)
at postmaster.c:1415
#21 0x00000000004a1c18 in main (argc=3, argv=0x3e434fe0) at main.c:231
For comparison, the same query executed on REL_18_STABLE is rejected with a
regular error:
ERROR: function return row and query-specified return row do not match
DETAIL: Returned type boolean at ordinal position 7, but query expects
text.
On Fri, Jun 5, 2026 at 12:49 AM PG Bug reporting form
<noreply@postgresql.org> wrote:
The following bug has been logged on the website:
Bug reference: 19508
Logged by: Nikita Kalinin
Email address: n.kalinin@postgrespro.ru
PostgreSQL version: 19beta1
Operating system: Fedora 44
Description:Hello,
It appears that pg_buffercache_pages() trusts a caller-supplied record
descriptor without verifying that the declared column types match the actual
values returned by the function.The crash is reproducible on the current master branch with a fresh cluster
after installing the extension:
Thanks for the report! I could reproduce this as well.
git blame points to the following commit:
commit 257c8231bf97a77378f6fedb826b1243f0a41612 (HEAD)
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue Apr 7 16:04:48 2026 +0300Modernize and optimize pg_buffercache_pages()
Commit 257c8231bf9 changed pg_buffercache_pages() to materialize rows directly
into a tuplestore. As a result, the function started using the caller-supplied
RECORD descriptor as rsinfo->setDesc, so a mismatched column definition list
could cause tuplestore_putvalues() to interpret returned Datums with incorrect
types.
Before that change, pg_buffercache_pages() exposed its actual tuple descriptor
to the executor, allowing the executor's existing rowtype checks to reject
incompatible definitions with a normal error.
The attached patch restores that behavior while keeping the materialized-SRF
implementation. Thoughts?
Regards,
--
Fujii Masao
Attachments:
v1-0001-pg_buffercache-restore-rowtype-verification-in-pg.patchapplication/octet-stream; name=v1-0001-pg_buffercache-restore-rowtype-verification-in-pg.patchDownload+65-1
Hi,
On Fri, 5 Jun 2026 at 08:49, Fujii Masao <masao.fujii@gmail.com> wrote:
Commit 257c8231bf9 changed pg_buffercache_pages() to materialize rows
directly
into a tuplestore. As a result, the function started using the
caller-supplied
RECORD descriptor as rsinfo->setDesc, so a mismatched column definition
list
could cause tuplestore_putvalues() to interpret returned Datums with
incorrect
types.Before that change, pg_buffercache_pages() exposed its actual tuple
descriptor
to the executor, allowing the executor's existing rowtype checks to reject
incompatible definitions with a normal error.The attached patch restores that behavior while keeping the
materialized-SRF
implementation. Thoughts?
Thanks for the patch, Fujii-san!
I was looking into the bug last night, and the approach looks right to me.
This still means InitMaterializedSRF() briefly creates the caller-derived
descriptor before rsinfo->setDesc is replaced. That seems acceptable here:
the descriptor lives only in the per-query context, and avoiding a local
copy of InitMaterializedSRF() keeps the fix much smaller and less fragile.
One small nit: build_buffercache_pages_tupledesc() names attribute 8
"usage_count", while the existing pg_buffercache view and the test use
"usagecount". This probably does not affect the tupledesc_match() check,
but I think it would be better to keep the existing spelling for
consistency.
Regards,
Ayush
On Fri, Jun 5, 2026 at 12:42 PM Ayush Tiwari
<ayushtiwari.slg01@gmail.com> wrote:
One small nit: build_buffercache_pages_tupledesc() names attribute 8
"usage_count", while the existing pg_buffercache view and the test use
"usagecount". This probably does not affect the tupledesc_match() check,
but I think it would be better to keep the existing spelling for
consistency.
Agreed. I've fixed that and attached an updated version of the patch.
Regards,
--
Fujii Masao
Attachments:
v2-0001-pg_buffercache-restore-rowtype-verification-in-pg.patchapplication/octet-stream; name=v2-0001-pg_buffercache-restore-rowtype-verification-in-pg.patchDownload+65-1
Hi,
On Fri, 5 Jun, 2026, 13:28 Fujii Masao, <masao.fujii@gmail.com> wrote:
On Fri, Jun 5, 2026 at 12:42 PM Ayush Tiwari
<ayushtiwari.slg01@gmail.com> wrote:One small nit: build_buffercache_pages_tupledesc() names attribute 8
"usage_count", while the existing pg_buffercache view and the test use
"usagecount". This probably does not affect the tupledesc_match() check,
but I think it would be better to keep the existing spelling for
consistency.Agreed. I've fixed that and attached an updated version of the patch.
LGTM.
Regards,
Ayush
Show quoted text
Hi Fujii-san,
On Fri, Jun 5, 2026 at 8:49 AM Fujii Masao <masao.fujii@gmail.com> wrote:
On Fri, Jun 5, 2026 at 12:49 AM PG Bug reporting form
<noreply@postgresql.org> wrote:The following bug has been logged on the website:
Bug reference: 19508
Logged by: Nikita Kalinin
Email address: n.kalinin@postgrespro.ru
PostgreSQL version: 19beta1
Operating system: Fedora 44
Description:Hello,
It appears that pg_buffercache_pages() trusts a caller-supplied record
descriptor without verifying that the declared column types match the actual
values returned by the function.The crash is reproducible on the current master branch with a fresh cluster
after installing the extension:Thanks for the report! I could reproduce this as well.
git blame points to the following commit:
commit 257c8231bf97a77378f6fedb826b1243f0a41612 (HEAD)
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue Apr 7 16:04:48 2026 +0300Modernize and optimize pg_buffercache_pages()
Commit 257c8231bf9 changed pg_buffercache_pages() to materialize rows directly
into a tuplestore. As a result, the function started using the caller-supplied
RECORD descriptor as rsinfo->setDesc, so a mismatched column definition list
could cause tuplestore_putvalues() to interpret returned Datums with incorrect
types.Before that change, pg_buffercache_pages() exposed its actual tuple descriptor
to the executor, allowing the executor's existing rowtype checks to reject
incompatible definitions with a normal error.The attached patch restores that behavior while keeping the materialized-SRF
implementation. Thoughts?
pg_buffercache_pages uses RETURNS SETOF RECORD whereas other
extensions like pgstattuple define explicit IN/OUT parameters at the
SQL level. Is there a specific reason this pattern was kept, or is it
simply a legacy design that hasn't been modernized? Had we followed
the IN/OUT parameter style, this sort of issue could have been
avoided, no?
--
With Regards,
Ashutosh Sharma.
On Sat, Jun 6, 2026 at 12:29 AM Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
pg_buffercache_pages uses RETURNS SETOF RECORD whereas other
extensions like pgstattuple define explicit IN/OUT parameters at the
SQL level. Is there a specific reason this pattern was kept, or is it
simply a legacy design that hasn't been modernized? Had we followed
the IN/OUT parameter style, this sort of issue could have been
avoided, no?
Probably yes. But if we do that, we would likely need to bump pg_buffercache
version. I'm not sure that's worthwhile just for this change.
Regards,
--
Fujii Masao
On Fri, Jun 5, 2026 at 5:48 PM Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:
Agreed. I've fixed that and attached an updated version of the patch.
LGTM.
Barring any objections, I will commit the patch.
Regards,
--
Fujii Masao
On Fri, Jun 5, 2026 at 9:08 PM Fujii Masao <masao.fujii@gmail.com> wrote:
On Sat, Jun 6, 2026 at 12:29 AM Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
pg_buffercache_pages uses RETURNS SETOF RECORD whereas other
extensions like pgstattuple define explicit IN/OUT parameters at the
SQL level. Is there a specific reason this pattern was kept, or is it
simply a legacy design that hasn't been modernized? Had we followed
the IN/OUT parameter style, this sort of issue could have been
avoided, no?Probably yes. But if we do that, we would likely need to bump pg_buffercache
version. I'm not sure that's worthwhile just for this change.
Okay, that makes perfect sense, thanks for the confirmation.
--
With Regards,
Ashutosh Sharma.
On Tue, Jun 9, 2026 at 8:44 AM Fujii Masao <masao.fujii@gmail.com> wrote:
On Fri, Jun 5, 2026 at 5:48 PM Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:
Agreed. I've fixed that and attached an updated version of the patch.
LGTM.
Barring any objections, I will commit the patch.
I've pushed the patch. Thanks!
Regards,
--
Fujii Masao