From cfe1a262bc5d9ea5b8cd8eba6005c52b56e2ebbf Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 23 Dec 2025 09:56:30 +0800
Subject: [PATCH v2 1/3] add RangeTblEntry to CreatePolicyStmt
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently CreatePolicy is only directly called through parser.  But imagine
copying policies from one table to another table (CREATE TABLE LIKE INCLUDING
POLICIES) or changing a column's data type (ALTER COLUMN SET DATA TYPE). In these
case, CreatePolicy will be called indirectly, either via the parser, or by
constructing a CreatePolicyStmt node from catalog metadata.

These indirectly called CreatePolicy may cause repeated lookup CreatePolicyStmt->table
RangeVar.  (The relation already exists, but lookup up again via RangeVar).

Generally we should avoid look up the same less-than-fully-qualified name
multiple times, we might get different answers due to concurrent activity, and
that might create a security vulnerability, along the lines of CVE-2014-0062.

To avoid that, Add add a RangeTblEntry Node to CreatePolicyStmt, So we can
record the Relation Oid advance.

discussion: https://postgr.es/m/CACJufxE42vysVEDEmaoBGmGYLZTCgUAwh_h-c9dcSLDtD5jE3g@mail.gmail.com
discussion: https://postgr.es/m/CACJufxFuEOB-i2z2qhyCG=dGwDf7g6Fs_o8cz=BUi76UuUFSOA@mail.gmail.com
---
 src/backend/commands/policy.c  | 21 ++++++++++++++++-----
 src/backend/parser/gram.y      |  1 +
 src/include/nodes/parsenodes.h | 10 ++++++++++
 3 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 5bd5f8c9968..a47744962e9 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -623,11 +623,22 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	memset(values, 0, sizeof(values));
 	memset(isnull, 0, sizeof(isnull));
 
-	/* Get id of table.  Also handles permissions checks. */
-	table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
-										0,
-										RangeVarCallbackForPolicy,
-										stmt);
+	if (stmt->rte != NULL)
+		table_id = stmt->rte->relid;
+	else
+	{
+		/* Get id of table.  Also handles permissions checks. */
+		table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+											0,
+											RangeVarCallbackForPolicy,
+											stmt);
+
+		/* Create a range table entry. */
+		stmt->rte = makeNode(RangeTblEntry);
+		stmt->rte->rtekind = RTE_RELATION;
+		stmt->rte->relid = table_id;
+		stmt->rte->rellockmode = AccessExclusiveLock;
+	}
 
 	/* Open target_table to build quals. No additional lock is necessary. */
 	target_table = relation_open(table_id, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..f6d46f08c22 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6023,6 +6023,7 @@ CreatePolicyStmt:
 
 					n->policy_name = $3;
 					n->table = $5;
+					n->rte = NULL;
 					n->permissive = $6;
 					n->cmd_name = $7;
 					n->roles = $8;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..7acbd2bf72c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3094,6 +3094,16 @@ typedef struct CreatePolicyStmt
 	NodeTag		type;
 	char	   *policy_name;	/* Policy's name */
 	RangeVar   *table;			/* the table name the policy applies to */
+
+	/*
+	 * RangeTblEntry for the table. This is useful for avoid repeated name
+	 * lookups issue. If CreatePolicyStmt.table has been looked up, we should
+	 * not rely on it to resolve the relation again, use this rte field
+	 * instead. This is useful when calling CreatePolicy not directly from
+	 * parser.
+	 */
+	RangeTblEntry *rte;
+
 	char	   *cmd_name;		/* the command name the policy applies to */
 	bool		permissive;		/* restrictive or permissive policy */
 	List	   *roles;			/* the roles associated with the policy */
-- 
2.34.1

