From 08eb37da4c4c8b29209573d499bbb9c3f5cb7525 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 5 Mar 2009 17:30:29 +0000
Subject: [PATCH] Fix column privilege checking for cases where parent and
 child have different attribute numbering.  Also, a parent whole-row reference
 should not require select privilege on child columns that aren't inherited
 from the parent. Problem diagnosed by KaiGai Kohei, though this isn't exactly
 his patch.

---
 src/backend/optimizer/prep/prepunion.c   | 71 +++++++++++++++++++++++-
 src/test/regress/expected/privileges.out | 45 +++++++++++++++
 src/test/regress/sql/privileges.sql      | 26 +++++++++
 3 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 13d08eef3ee..43aa515ae5e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.166 2009/02/25 03:30:37 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.167 2009/03/05 17:30:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,6 +30,7 @@
 
 
 #include "access/heapam.h"
+#include "access/sysattr.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
@@ -95,6 +96,8 @@ static void make_inh_translation_list(Relation oldrelation,
 						  Relation newrelation,
 						  Index newvarno,
 						  List **translated_vars);
+static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
+									  List *translated_vars);
 static Node *adjust_appendrel_attrs_mutator(Node *node,
 							   AppendRelInfo *context);
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
@@ -1295,6 +1298,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		appinfo->parent_reloid = parentOID;
 		appinfos = lappend(appinfos, appinfo);
 
+		/*
+		 * Translate the column permissions bitmaps to the child's attnums
+		 * (we have to build the translated_vars list before we can do this).
+		 * But if this is the parent table, leave copyObject's result alone.
+		 */
+		if (childOID != parentOID)
+		{
+			childrte->selectedCols = translate_col_privs(rte->selectedCols,
+														 appinfo->translated_vars);
+			childrte->modifiedCols = translate_col_privs(rte->modifiedCols,
+														 appinfo->translated_vars);
+		}
+
 		/*
 		 * Build a RowMarkClause if parent is marked FOR UPDATE/SHARE.
 		 */
@@ -1437,6 +1453,59 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
 	*translated_vars = vars;
 }
 
+/*
+ * translate_col_privs
+ *	  Translate a bitmapset representing per-column privileges from the
+ *	  parent rel's attribute numbering to the child's.
+ *
+ * The only surprise here is that we don't translate a parent whole-row
+ * reference into a child whole-row reference.  That would mean requiring
+ * permissions on all child columns, which is overly strict, since the
+ * query is really only going to reference the inherited columns.  Instead
+ * we set the per-column bits for all inherited columns.
+ */
+static Bitmapset *
+translate_col_privs(const Bitmapset *parent_privs,
+					List *translated_vars)
+{
+	Bitmapset  *child_privs = NULL;
+	bool		whole_row;
+	int			attno;
+	ListCell   *lc;
+
+	/* System attributes have the same numbers in all tables */
+	for (attno = FirstLowInvalidHeapAttributeNumber+1; attno < 0; attno++)
+	{
+		if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber,
+						  parent_privs))
+			child_privs = bms_add_member(child_privs,
+										 attno - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* Check if parent has whole-row reference */
+	whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber,
+							  parent_privs);
+
+	/* And now translate the regular user attributes, using the vars list */
+	attno = InvalidAttrNumber;
+	foreach(lc, translated_vars)
+	{
+		Var	   *var = (Var *) lfirst(lc);
+
+		attno++;
+		if (var == NULL)		/* ignore dropped columns */
+			continue;
+		Assert(IsA(var, Var));
+		if (whole_row ||
+			bms_is_member(attno - FirstLowInvalidHeapAttributeNumber,
+						  parent_privs))
+			child_privs = bms_add_member(child_privs,
+										 var->varattno - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	return child_privs;
+}
+
 /*
  * adjust_appendrel_attrs
  *	  Copy the specified query or expression and translate Vars referring
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 8129f5b915b..a17ff59d0c8 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -393,6 +393,48 @@ SET SESSION AUTHORIZATION regressuser3;
 DELETE FROM atest5 WHERE one = 1; -- fail
 ERROR:  permission denied for relation atest5
 DELETE FROM atest5 WHERE two = 2; -- ok
+-- check inheritance cases
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE atestp1 (f1 int, f2 int) WITH OIDS;
+CREATE TABLE atestp2 (fx int, fy int) WITH OIDS;
+CREATE TABLE atestc (fz int) INHERITS (atestp1, atestp2);
+GRANT SELECT(fx,fy,oid) ON atestp2 TO regressuser2;
+GRANT SELECT(fx) ON atestc TO regressuser2;
+SET SESSION AUTHORIZATION regressuser2;
+SELECT fx FROM atestp2; -- ok
+ fx 
+----
+(0 rows)
+
+SELECT fy FROM atestp2; -- fail, no privilege on atestc.fy
+ERROR:  permission denied for relation atestc
+SELECT atestp2 FROM atestp2; -- fail, no privilege on atestc.fy
+ERROR:  permission denied for relation atestc
+SELECT oid FROM atestp2; -- fail, no privilege on atestc.oid
+ERROR:  permission denied for relation atestc
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT(fy,oid) ON atestc TO regressuser2;
+SET SESSION AUTHORIZATION regressuser2;
+SELECT fx FROM atestp2; -- still ok
+ fx 
+----
+(0 rows)
+
+SELECT fy FROM atestp2; -- ok
+ fy 
+----
+(0 rows)
+
+SELECT atestp2 FROM atestp2; -- ok
+ atestp2 
+---------
+(0 rows)
+
+SELECT oid FROM atestp2; -- ok
+ oid 
+-----
+(0 rows)
+
 -- privileges on functions, languages
 -- switch to superuser
 \c -
@@ -791,6 +833,9 @@ DROP TABLE atest3;
 DROP TABLE atest4;
 DROP TABLE atest5;
 DROP TABLE atest6;
+DROP TABLE atestc;
+DROP TABLE atestp1;
+DROP TABLE atestp2;
 DROP GROUP regressgroup1;
 DROP GROUP regressgroup2;
 REVOKE USAGE ON LANGUAGE sql FROM regressuser1;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 2316d116162..5aa1012f3f6 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -267,6 +267,29 @@ SET SESSION AUTHORIZATION regressuser3;
 DELETE FROM atest5 WHERE one = 1; -- fail
 DELETE FROM atest5 WHERE two = 2; -- ok
 
+-- check inheritance cases
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE atestp1 (f1 int, f2 int) WITH OIDS;
+CREATE TABLE atestp2 (fx int, fy int) WITH OIDS;
+CREATE TABLE atestc (fz int) INHERITS (atestp1, atestp2);
+GRANT SELECT(fx,fy,oid) ON atestp2 TO regressuser2;
+GRANT SELECT(fx) ON atestc TO regressuser2;
+
+SET SESSION AUTHORIZATION regressuser2;
+SELECT fx FROM atestp2; -- ok
+SELECT fy FROM atestp2; -- fail, no privilege on atestc.fy
+SELECT atestp2 FROM atestp2; -- fail, no privilege on atestc.fy
+SELECT oid FROM atestp2; -- fail, no privilege on atestc.oid
+
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT(fy,oid) ON atestc TO regressuser2;
+
+SET SESSION AUTHORIZATION regressuser2;
+SELECT fx FROM atestp2; -- still ok
+SELECT fy FROM atestp2; -- ok
+SELECT atestp2 FROM atestp2; -- ok
+SELECT oid FROM atestp2; -- ok
+
 -- privileges on functions, languages
 
 -- switch to superuser
@@ -466,6 +489,9 @@ DROP TABLE atest3;
 DROP TABLE atest4;
 DROP TABLE atest5;
 DROP TABLE atest6;
+DROP TABLE atestc;
+DROP TABLE atestp1;
+DROP TABLE atestp2;
 
 DROP GROUP regressgroup1;
 DROP GROUP regressgroup2;
-- 
GitLab