commit 7e22f2d245888c41b66ae214c226b4a101f27a89
Author: Ian Barwick <ian@2ndquadrant.com>
Date:   Tue Jan 12 13:40:31 2021 +0900

    Fix has_column_privilege() with attnums and non-existent columns
    
    The existence of a column should be confirmed even if the user has
    the table-level privilege, otherwise the function will happily report
    the user has privilege on a dropped or non-existent column if an
    invalid attnum is provided.

diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index c7f029e218..be5649b7ac 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -2447,7 +2447,7 @@ has_any_column_privilege_id_id(PG_FUNCTION_ARGS)
  */
 static int
 column_privilege_check(Oid tableoid, AttrNumber attnum,
-					   Oid roleid, AclMode mode)
+					   Oid roleid, AclMode mode, bool column_checked)
 {
 	AclResult	aclresult;
 	HeapTuple	attTuple;
@@ -2472,7 +2472,11 @@ column_privilege_check(Oid tableoid, AttrNumber attnum,
 
 	aclresult = pg_class_aclcheck(tableoid, roleid, mode);
 
-	if (aclresult == ACLCHECK_OK)
+	/*
+	 * Table-level privilege is present and the column has been checked by the
+	 * caller.
+	 */
+	if (aclresult == ACLCHECK_OK && column_checked == true)
 		return true;
 
 	/*
@@ -2493,6 +2497,16 @@ column_privilege_check(Oid tableoid, AttrNumber attnum,
 	}
 	ReleaseSysCache(attTuple);
 
+	/*
+	 * If the table level privilege exists, and the existence of the column has
+	 * been confirmed, we can skip the per-column check.
+	 */
+	if (aclresult == ACLCHECK_OK)
+		return true;
+
+	/*
+	 * No table privilege, so try per-column privileges.
+	 */
 	aclresult = pg_attribute_aclcheck(tableoid, attnum, roleid, mode);
 
 	return (aclresult == ACLCHECK_OK);
@@ -2521,7 +2535,7 @@ has_column_privilege_name_name_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2548,7 +2562,8 @@ has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS)
 	tableoid = convert_table_name(tablename);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
+
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2575,7 +2590,7 @@ has_column_privilege_name_id_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2600,7 +2615,7 @@ has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS)
 	roleid = get_role_oid_or_public(NameStr(*username));
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2627,7 +2642,7 @@ has_column_privilege_id_name_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2652,7 +2667,7 @@ has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS)
 	tableoid = convert_table_name(tablename);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2677,7 +2692,7 @@ has_column_privilege_id_id_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2700,7 +2715,7 @@ has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS)
 
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2729,7 +2744,7 @@ has_column_privilege_name_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2756,7 +2771,8 @@ has_column_privilege_name_attnum(PG_FUNCTION_ARGS)
 	tableoid = convert_table_name(tablename);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
+
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2783,7 +2799,7 @@ has_column_privilege_id_name(PG_FUNCTION_ARGS)
 	colattnum = convert_column_name(tableoid, column);
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, true);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
@@ -2808,7 +2824,7 @@ has_column_privilege_id_attnum(PG_FUNCTION_ARGS)
 	roleid = GetUserId();
 	mode = convert_column_priv_string(priv_type_text);
 
-	privresult = column_privilege_check(tableoid, colattnum, roleid, mode);
+	privresult = column_privilege_check(tableoid, colattnum, roleid, mode, false);
 	if (privresult < 0)
 		PG_RETURN_NULL();
 	PG_RETURN_BOOL(privresult);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 7754c20db4..284fc9f081 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1275,7 +1275,13 @@ select has_column_privilege('mytable','........pg.dropped.2........','select');
 select has_column_privilege('mytable',2::int2,'select');
  has_column_privilege 
 ----------------------
- t
+ 
+(1 row)
+
+select has_column_privilege('mytable',99::int2,'select');
+ has_column_privilege 
+----------------------
+ 
 (1 row)
 
 revoke select on table mytable from regress_priv_user3;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 4911ad4add..7b231d78f0 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -777,6 +777,7 @@ alter table mytable drop column f2;
 select has_column_privilege('mytable','f2','select');
 select has_column_privilege('mytable','........pg.dropped.2........','select');
 select has_column_privilege('mytable',2::int2,'select');
+select has_column_privilege('mytable',99::int2,'select');
 revoke select on table mytable from regress_priv_user3;
 select has_column_privilege('mytable',2::int2,'select');
 drop table mytable;
