Hi, On Fri, Apr 10, 2026 at 12:46 AM Jim Jones <[email protected]> wrote: > > I guess a check in read_stream_begin_relation() > and in StartReadBuffersImpl() would be the best solution? If you agree, > could you add it in v16?
Having both checks might look a bit redundant since the read stream will eventually call the StartReadBuffersImpl function. On the other hand, there are many places which are checking this restriction even if subsequent functions (from bufmgr) also have this check. So, I'll keep both checks and a bit reduce the comments in the bufmgr.c . BTW, what do you think about making this comment less "concrete"? : # SELECT via index scan from other session. # Sequential scans are blocked at read_stream_begin_relation(); index scans # bypass that path entirely and reach ReadBufferExtended() in bufmgr.c # (nbtree's _bt_getbuf calls ReadBuffer directly for individual page fetches). # enable_seqscan=off forces the planner to use the index. I mean that if the described logic changes, this comment will become confusing. We can describe the test in general words. For example : # Index scans can use a different code path from the one sequential scans are # following. Make sure that we cannot access other sessions' temp tables during # index scan either. Thank you for the comments! Please, see an updated set of patches. -- Best regards, Daniil Davydov
From 07dd5ecd204420678a100b64a01347227f4fef4f Mon Sep 17 00:00:00 2001 From: Jim Jones <[email protected]> Date: Wed, 8 Apr 2026 14:56:57 +0200 Subject: [PATCH v16 2/2] Test cross-session access on temporary tables --- src/test/modules/test_misc/meson.build | 1 + .../test_misc/t/012_temp_obj_multisession.pl | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/test/modules/test_misc/t/012_temp_obj_multisession.pl diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build index 1b25d98f7f3..a54599cc301 100644 --- a/src/test/modules/test_misc/meson.build +++ b/src/test/modules/test_misc/meson.build @@ -20,6 +20,7 @@ tests += { 't/009_log_temp_files.pl', 't/010_index_concurrently_upsert.pl', 't/011_lock_stats.pl', + 't/012_temp_obj_multisession.pl', ], # The injection points are cluster-wide, so disable installcheck 'runningcheck': false, diff --git a/src/test/modules/test_misc/t/012_temp_obj_multisession.pl b/src/test/modules/test_misc/t/012_temp_obj_multisession.pl new file mode 100644 index 00000000000..4d599152740 --- /dev/null +++ b/src/test/modules/test_misc/t/012_temp_obj_multisession.pl @@ -0,0 +1,117 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use PostgreSQL::Test::BackgroundPsql; +use Test::More; + +# Set up a fresh node +my $node = PostgreSQL::Test::Cluster->new('temp_lock'); +$node->init; +$node->start; + +# Create a long-lived session +my $psql1 = $node->background_psql('postgres'); + +$psql1->query_safe( + q(CREATE TEMP TABLE foo AS SELECT 42 AS val;)); + +# Create an index so the index-scan path (ReadBufferExtended) can be tested +$psql1->query_safe( + q(CREATE INDEX ON foo(val);)); + +my $tempschema = $node->safe_psql( + 'postgres', + q{ + SELECT n.nspname + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE relname = 'foo' AND relpersistence = 't'; + } +); +chomp $tempschema; +ok($tempschema =~ /^pg_temp_\d+$/, "got temp schema: $tempschema"); + + +# SELECT TEMPORARY TABLE from other session +my ($stdout, $stderr); +$node->psql( + 'postgres', + "SELECT val FROM $tempschema.foo;", + stdout => \$stdout, + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'SELECT on other session temp table is not allowed'); + +# UPDATE TEMPORARY TABLE from other session +$node->psql( + 'postgres', + "UPDATE $tempschema.foo SET val = NULL;", + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'UPDATE on other session temp table is not allowed'); + +# DELETE records from TEMPORARY TABLE from other session +$node->psql( + 'postgres', + "DELETE FROM $tempschema.foo;", + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'DELETE on other session temp table is not allowed'); + +# TRUNCATE TEMPORARY TABLE from other session +$node->psql( + 'postgres', + "TRUNCATE TABLE $tempschema.foo;", + stderr => \$stderr +); +like($stderr, qr/cannot truncate temporary tables of other sessions/, + 'TRUNCATE on other session temp table is not allowed'); + +# INSERT INTO TEMPORARY TABLE from other session +$node->psql( + 'postgres', + "INSERT INTO $tempschema.foo VALUES (73);", + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'INSERT INTO on other session temp table is not allowed'); + +# COPY TEMPORARY TABLE from other session +$node->psql( + 'postgres', + "COPY $tempschema.foo TO STDOUT;", + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'COPY on other session temp table is blocked'); + +# SELECT via index scan from other session. +# Sequential scans are blocked at read_stream_begin_relation(); index scans +# bypass that path entirely and reach ReadBufferExtended() in bufmgr.c +# (nbtree's _bt_getbuf calls ReadBuffer directly for individual page fetches). +# enable_seqscan=off forces the planner to use the index. +$node->psql( + 'postgres', + "SET enable_seqscan = off; SELECT val FROM $tempschema.foo WHERE val = 42;", + stderr => \$stderr +); +like($stderr, qr/cannot access temporary relations of other sessions/, + 'index scan on other session temp table is not allowed (exercises ReadBufferExtended path)'); + +# DROP TEMPORARY TABLE from other session +my $ok = $node->psql( + 'postgres', + "DROP TABLE $tempschema.foo;" +); +ok($ok == 0, 'DROP TABLE executed successfully'); + +# Clean up +$psql1->quit; + +done_testing(); -- 2.43.0
From 6b25b30bc981dd6420788014face14a13bb9461d Mon Sep 17 00:00:00 2001 From: Daniil Davidov <[email protected]> Date: Fri, 10 Apr 2026 13:51:14 +0700 Subject: [PATCH v16 1/2] Prevent access to other sessions' temp tables --- src/backend/storage/aio/read_stream.c | 10 ++++++++++ src/backend/storage/buffer/bufmgr.c | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c index b6fce4e7cc6..213f6206ba2 100644 --- a/src/backend/storage/aio/read_stream.c +++ b/src/backend/storage/aio/read_stream.c @@ -776,6 +776,16 @@ read_stream_begin_impl(int flags, uint32 max_possible_buffer_limit; Oid tablespace_id; + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (rel && RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary relations of other sessions"))); + /* * Decide how many I/Os we will allow to run at the same time. That * currently means advice to the kernel to tell it that we will soon read. diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3cc0b0bdd92..653278140fb 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -795,7 +795,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum) if (RELATION_IS_OTHER_TEMP(reln)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot access temporary tables of other sessions"))); + errmsg("cannot access temporary relations of other sessions"))); /* pass it off to localbuf.c */ return PrefetchLocalBuffer(RelationGetSmgr(reln), forkNum, blockNum); @@ -936,7 +936,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, if (RELATION_IS_OTHER_TEMP(reln)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot access temporary tables of other sessions"))); + errmsg("cannot access temporary relations of other sessions"))); /* * Read the buffer, and update pgstat counters to reflect a cache hit or @@ -1317,6 +1317,12 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence, else persistence = smgr_persistence; + /* see comments in ReadBufferExtended */ + if (rel != NULL && RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary relations of other sessions"))); + if (unlikely(mode == RBM_ZERO_AND_CLEANUP_LOCK || mode == RBM_ZERO_AND_LOCK)) { @@ -1393,6 +1399,13 @@ StartReadBuffersImpl(ReadBuffersOperation *operation, io_object = IOOBJECT_RELATION; } + + /* see comments in ReadBufferExtended */ + if (operation->rel != NULL && RELATION_IS_OTHER_TEMP(operation->rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary relations of other sessions"))); + for (int i = 0; i < actual_nblocks; ++i) { bool found; -- 2.43.0
