Hi,

Posting rebased set of patches.

--
Best regards,
Daniil Davydov
From 3dc50f99139e7d83b54c97eed6d10d0a96702c05 Mon Sep 17 00:00:00 2001
From: Daniil Davidov <[email protected]>
Date: Tue, 28 Oct 2025 20:22:48 +0700
Subject: [PATCH v12 1/2] Fix accessing other sessions temp tables

---
 src/backend/catalog/namespace.c  | 56 ++++++++++++++++++++------------
 src/backend/commands/tablecmds.c |  3 +-
 src/backend/nodes/makefuncs.c    |  6 +++-
 src/backend/parser/gram.y        | 11 ++++++-
 src/include/catalog/namespace.h  |  2 ++
 5 files changed, 55 insertions(+), 23 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 56b87d878e8..b672bcf5f63 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -500,28 +500,44 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 		 */
 		if (relation->relpersistence == RELPERSISTENCE_TEMP)
 		{
-			if (!OidIsValid(myTempNamespace))
-				relId = InvalidOid; /* this probably can't happen? */
-			else
-			{
-				if (relation->schemaname)
-				{
-					Oid			namespaceId;
+			Oid	namespaceId;
 
-					namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+			if (relation->schemaname)
+			{
+				namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
 
+				/*
+				 * If the user has specified an existing temporary schema
+				 * owned by another user.
+				 */
+				if (OidIsValid(namespaceId) && namespaceId != myTempNamespace)
+				{
 					/*
-					 * For missing_ok, allow a non-existent schema name to
-					 * return InvalidOid.
+					 * We don't allow users to access temp tables of other
+					 * sessions except for the case of dropping tables.
 					 */
-					if (namespaceId != myTempNamespace)
+					if (!(flags & RVR_OTHER_TEMP_OK))
 						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-								 errmsg("temporary tables cannot specify a schema name")));
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot access temporary relations of other sessions")));
 				}
+			}
+			else
+			{
+				namespaceId = myTempNamespace;
 
-				relId = get_relname_relid(relation->relname, myTempNamespace);
+				/*
+				 * If this table was recognized as temporary, it means that we
+				 * found it because backend's temporary namespace was specified
+				 * in search_path. Thus, MyTempNamespace must contain valid oid.
+				 */
+				Assert(OidIsValid(namespaceId));
 			}
+
+			if (missing_ok && !OidIsValid(namespaceId))
+				relId = InvalidOid;
+			else
+				relId = get_relname_relid(relation->relname, namespaceId);
 		}
 		else if (relation->schemaname)
 		{
@@ -3625,21 +3641,19 @@ get_namespace_oid(const char *nspname, bool missing_ok)
 RangeVar *
 makeRangeVarFromNameList(const List *names)
 {
-	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
+	RangeVar   *rel;
 
 	switch (list_length(names))
 	{
 		case 1:
-			rel->relname = strVal(linitial(names));
+			rel = makeRangeVar(NULL, strVal(linitial(names)), -1);
 			break;
 		case 2:
-			rel->schemaname = strVal(linitial(names));
-			rel->relname = strVal(lsecond(names));
+			rel = makeRangeVar(strVal(linitial(names)), strVal(lsecond(names)), -1);
 			break;
 		case 3:
+			rel = makeRangeVar(strVal(lsecond(names)), strVal(lthird(names)), -1);
 			rel->catalogname = strVal(linitial(names));
-			rel->schemaname = strVal(lsecond(names));
-			rel->relname = strVal(lthird(names));
 			break;
 		default:
 			ereport(ERROR,
@@ -3846,6 +3860,8 @@ GetTempNamespaceProcNumber(Oid namespaceId)
 		return INVALID_PROC_NUMBER; /* no such namespace? */
 	if (strncmp(nspname, "pg_temp_", 8) == 0)
 		result = atoi(nspname + 8);
+	else if (strcmp(nspname, "pg_temp") == 0)
+		result = MyProcNumber;
 	else if (strncmp(nspname, "pg_toast_temp_", 14) == 0)
 		result = atoi(nspname + 14);
 	else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b04b0dbd2a0..a1544b86221 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1639,7 +1639,8 @@ RemoveRelations(DropStmt *drop)
 		state.heapOid = InvalidOid;
 		state.partParentOid = InvalidOid;
 
-		relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
+		relOid = RangeVarGetRelidExtended(rel, lockmode,
+										  RVR_MISSING_OK | RVR_OTHER_TEMP_OK,
 										  RangeVarCallbackForDropRelation,
 										  &state);
 
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 2caec621d73..40f93239c8c 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -478,10 +478,14 @@ makeRangeVar(char *schemaname, char *relname, int location)
 	r->schemaname = schemaname;
 	r->relname = relname;
 	r->inh = true;
-	r->relpersistence = RELPERSISTENCE_PERMANENT;
 	r->alias = NULL;
 	r->location = location;
 
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	return r;
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c567252acc4..299adebae37 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -19597,7 +19597,11 @@ makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner)
 			break;
 	}
 
-	r->relpersistence = RELPERSISTENCE_PERMANENT;
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	r->location = position;
 
 	return r;
@@ -19637,6 +19641,11 @@ makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
 			break;
 	}
 
+	if (r->schemaname && strncmp(r->schemaname, "pg_temp", 7) == 0)
+		r->relpersistence = RELPERSISTENCE_TEMP;
+	else
+		r->relpersistence = RELPERSISTENCE_PERMANENT;
+
 	return r;
 }
 
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 1a25973685c..bf627ab6577 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -90,6 +90,8 @@ typedef enum RVROption
 	RVR_MISSING_OK = 1 << 0,	/* don't error if relation doesn't exist */
 	RVR_NOWAIT = 1 << 1,		/* error if relation cannot be locked */
 	RVR_SKIP_LOCKED = 1 << 2,	/* skip if relation cannot be locked */
+	RVR_OTHER_TEMP_OK = 1 << 3	/* don't error if relation is temp relation of
+								   other session (needed for DROP command) */
 }			RVROption;
 
 typedef void (*RangeVarGetRelidCallback) (const RangeVar *relation, Oid relId,
-- 
2.43.0

From 09113ecb70104c59c5da6187462058a8bc654a75 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Fri, 26 Sep 2025 12:58:19 +0200
Subject: [PATCH v12 2/2] Test cross-session access restrictions on temporary
 tables

These tests create a temporary table in one session and verify that another
session cannot perform SELECT, UPDATE, DELETE, TRUNCATE, INSERT, ALTER, COPY,
or LOCK operations on that table. It also confirms that DROP TABLE from
another session succeeds.
---
 src/test/modules/test_misc/meson.build        |   1 +
 .../test_misc/t/011_temp_obj_multisession.pl  | 126 ++++++++++++++++++
 2 files changed, 127 insertions(+)
 create mode 100644 src/test/modules/test_misc/t/011_temp_obj_multisession.pl

diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6e8db1621a7..356121673a1 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -19,6 +19,7 @@ tests += {
       't/008_replslot_single_user.pl',
       't/009_log_temp_files.pl',
       't/010_index_concurrently_upsert.pl',
+      't/011_temp_obj_multisession.pl',
     ],
     # The injection points are cluster-wide, so disable installcheck
     'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/011_temp_obj_multisession.pl b/src/test/modules/test_misc/t/011_temp_obj_multisession.pl
new file mode 100644
index 00000000000..9b0deaaf485
--- /dev/null
+++ b/src/test/modules/test_misc/t/011_temp_obj_multisession.pl
@@ -0,0 +1,126 @@
+# Copyright (c) 2025, 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;));
+
+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;",
+    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 access temporary relations 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');
+
+# ALTER TABLE .. RENAME TEMPORARY TABLE from other session
+$node->psql(
+    'postgres',
+    "ALTER TABLE $tempschema.foo RENAME TO bar;",
+    stderr => \$stderr
+);
+like($stderr, qr/cannot access temporary relations of other sessions/,
+     'ALTER TABLE ... RENAME on other session temp table is blocked');
+
+# ALTER TABLE .. ADD COLUMN in TEMPORARY TABLE from other session
+$node->psql(
+    'postgres',
+    "ALTER TABLE $tempschema.foo ADD COLUMN bar int;",
+    stderr => \$stderr
+);
+like($stderr, qr/cannot access temporary relations of other sessions/,
+     'ALTER TABLE ... ADD COLUMN on other session temp table is blocked');
+
+# COPY TEMPORARY TABLE from other session
+$node->psql(
+    'postgres',
+    "COPY $tempschema.foo TO '/tmp/x';",
+    stderr => \$stderr
+);
+like($stderr, qr/cannot access temporary relations of other sessions/,
+     'COPY on other session temp table is blocked');
+
+# LOCK TEMPORARY TABLE from other session
+$node->psql(
+    'postgres',
+    "BEGIN; LOCK TABLE $tempschema.foo IN ACCESS EXCLUSIVE MODE;",
+    stderr => \$stderr
+);
+like($stderr, qr/cannot access temporary relations of other sessions/,
+     'LOCK on other session temp table is blocked');
+
+# 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

Reply via email to