Skip to content

Commit 01c7a87

Browse files
committed
Fix corner-case failures in has_foo_privilege() family of functions.
The variants of these functions that take numeric inputs (OIDs or column numbers) are supposed to return NULL rather than failing on bad input; this rule reduces problems with snapshot skew when queries apply the functions to all rows of a catalog. has_column_privilege() had careless handling of the case where the table OID didn't exist. You might get something like this: select has_column_privilege(9999,'nosuchcol','select'); ERROR: column "nosuchcol" of relation "(null)" does not exist or you might get a crash, depending on the platform's printf's response to a null string pointer. In addition, while applying the column-number variant to a dropped column returned NULL as desired, applying the column-name variant did not: select has_column_privilege('mytable','........pg.dropped.2........','select'); ERROR: column "........pg.dropped.2........" of relation "mytable" does not exist It seems better to make this case return NULL as well. Also, the OID-accepting variants of has_foreign_data_wrapper_privilege, has_server_privilege, and has_tablespace_privilege didn't follow the principle of returning NULL for nonexistent OIDs. Superusers got TRUE, everybody else got an error. Per investigation of Jaime Casanova's report of a new crash in HEAD. These behaviors have been like this for a long time, so back-patch to all supported branches. Patch by me; thanks to Stephen Frost for discussion and review Discussion: https://postgr.es/m/CAJGNTeP=-6Gyqq5TN9OvYEydi7Fv1oGyYj650LGTnW44oAzYCg@mail.gmail.com
1 parent 4b2fb12 commit 01c7a87

File tree

3 files changed

+159
-10
lines changed

3 files changed

+159
-10
lines changed

src/backend/utils/adt/acl.c

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,8 +2450,12 @@ has_any_column_privilege_id_id(PG_FUNCTION_ARGS)
24502450
*
24512451
* The result is a boolean value: true if user has the indicated
24522452
* privilege, false if not. The variants that take a relation OID
2453-
* and an integer attnum return NULL (rather than throwing an error)
2454-
* if the column doesn't exist or is dropped.
2453+
* return NULL (rather than throwing an error) if that relation OID
2454+
* doesn't exist. Likewise, the variants that take an integer attnum
2455+
* return NULL (rather than throwing an error) if there is no such
2456+
* pg_attribute entry. All variants return NULL if an attisdropped
2457+
* column is selected. These rules are meant to avoid unnecessary
2458+
* failures in queries that scan pg_attribute.
24552459
*/
24562460

24572461
/*
@@ -2468,6 +2472,12 @@ column_privilege_check(Oid tableoid, AttrNumber attnum,
24682472
HeapTuple attTuple;
24692473
Form_pg_attribute attributeForm;
24702474

2475+
/*
2476+
* If convert_column_name failed, we can just return -1 immediately.
2477+
*/
2478+
if (attnum == InvalidAttrNumber)
2479+
return -1;
2480+
24712481
/*
24722482
* First check if we have the privilege at the table level. We check
24732483
* existence of the pg_class row before risking calling pg_class_aclcheck.
@@ -2829,21 +2839,59 @@ has_column_privilege_id_attnum(PG_FUNCTION_ARGS)
28292839

28302840
/*
28312841
* Given a table OID and a column name expressed as a string, look it up
2832-
* and return the column number
2842+
* and return the column number. Returns InvalidAttrNumber in cases
2843+
* where caller should return NULL instead of failing.
28332844
*/
28342845
static AttrNumber
28352846
convert_column_name(Oid tableoid, text *column)
28362847
{
2837-
AttrNumber attnum;
28382848
char *colname;
2849+
HeapTuple attTuple;
2850+
AttrNumber attnum;
28392851

28402852
colname = text_to_cstring(column);
2841-
attnum = get_attnum(tableoid, colname);
2842-
if (attnum == InvalidAttrNumber)
2843-
ereport(ERROR,
2844-
(errcode(ERRCODE_UNDEFINED_COLUMN),
2845-
errmsg("column \"%s\" of relation \"%s\" does not exist",
2846-
colname, get_rel_name(tableoid))));
2853+
2854+
/*
2855+
* We don't use get_attnum() here because it will report that dropped
2856+
* columns don't exist. We need to treat dropped columns differently from
2857+
* nonexistent columns.
2858+
*/
2859+
attTuple = SearchSysCache2(ATTNAME,
2860+
ObjectIdGetDatum(tableoid),
2861+
CStringGetDatum(colname));
2862+
if (HeapTupleIsValid(attTuple))
2863+
{
2864+
Form_pg_attribute attributeForm;
2865+
2866+
attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
2867+
/* We want to return NULL for dropped columns */
2868+
if (attributeForm->attisdropped)
2869+
attnum = InvalidAttrNumber;
2870+
else
2871+
attnum = attributeForm->attnum;
2872+
ReleaseSysCache(attTuple);
2873+
}
2874+
else
2875+
{
2876+
char *tablename = get_rel_name(tableoid);
2877+
2878+
/*
2879+
* If the table OID is bogus, or it's just been dropped, we'll get
2880+
* NULL back. In such cases we want has_column_privilege to return
2881+
* NULL too, so just return InvalidAttrNumber.
2882+
*/
2883+
if (tablename != NULL)
2884+
{
2885+
/* tableoid exists, colname does not, so throw error */
2886+
ereport(ERROR,
2887+
(errcode(ERRCODE_UNDEFINED_COLUMN),
2888+
errmsg("column \"%s\" of relation \"%s\" does not exist",
2889+
colname, tablename)));
2890+
}
2891+
/* tableoid doesn't exist, so act like attisdropped case */
2892+
attnum = InvalidAttrNumber;
2893+
}
2894+
28472895
pfree(colname);
28482896
return attnum;
28492897
}
@@ -3147,6 +3195,9 @@ has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS)
31473195
roleid = get_role_oid_or_public(NameStr(*username));
31483196
mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
31493197

3198+
if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid)))
3199+
PG_RETURN_NULL();
3200+
31503201
aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
31513202

31523203
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -3170,6 +3221,9 @@ has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS)
31703221
roleid = GetUserId();
31713222
mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
31723223

3224+
if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid)))
3225+
PG_RETURN_NULL();
3226+
31733227
aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
31743228

31753229
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -3214,6 +3268,9 @@ has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS)
32143268

32153269
mode = convert_foreign_data_wrapper_priv_string(priv_type_text);
32163270

3271+
if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid)))
3272+
PG_RETURN_NULL();
3273+
32173274
aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
32183275

32193276
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -3913,6 +3970,9 @@ has_server_privilege_name_id(PG_FUNCTION_ARGS)
39133970
roleid = get_role_oid_or_public(NameStr(*username));
39143971
mode = convert_server_priv_string(priv_type_text);
39153972

3973+
if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid)))
3974+
PG_RETURN_NULL();
3975+
39163976
aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
39173977

39183978
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -3936,6 +3996,9 @@ has_server_privilege_id(PG_FUNCTION_ARGS)
39363996
roleid = GetUserId();
39373997
mode = convert_server_priv_string(priv_type_text);
39383998

3999+
if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid)))
4000+
PG_RETURN_NULL();
4001+
39394002
aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
39404003

39414004
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -3980,6 +4043,9 @@ has_server_privilege_id_id(PG_FUNCTION_ARGS)
39804043

39814044
mode = convert_server_priv_string(priv_type_text);
39824045

4046+
if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid)))
4047+
PG_RETURN_NULL();
4048+
39834049
aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
39844050

39854051
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -4095,6 +4161,9 @@ has_tablespace_privilege_name_id(PG_FUNCTION_ARGS)
40954161
roleid = get_role_oid_or_public(NameStr(*username));
40964162
mode = convert_tablespace_priv_string(priv_type_text);
40974163

4164+
if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid)))
4165+
PG_RETURN_NULL();
4166+
40984167
aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
40994168

41004169
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -4118,6 +4187,9 @@ has_tablespace_privilege_id(PG_FUNCTION_ARGS)
41184187
roleid = GetUserId();
41194188
mode = convert_tablespace_priv_string(priv_type_text);
41204189

4190+
if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid)))
4191+
PG_RETURN_NULL();
4192+
41214193
aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
41224194

41234195
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
@@ -4162,6 +4234,9 @@ has_tablespace_privilege_id_id(PG_FUNCTION_ARGS)
41624234

41634235
mode = convert_tablespace_priv_string(priv_type_text);
41644236

4237+
if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid)))
4238+
PG_RETURN_NULL();
4239+
41654240
aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
41664241

41674242
PG_RETURN_BOOL(aclresult == ACLCHECK_OK);

src/test/regress/expected/privileges.out

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,63 @@ from (select oid from pg_class where relname = 'atest1') as t1;
10361036
f
10371037
(1 row)
10381038

1039+
-- has_column_privilege function
1040+
-- bad-input checks (as non-super-user)
1041+
select has_column_privilege('pg_authid',NULL,'select');
1042+
has_column_privilege
1043+
----------------------
1044+
1045+
(1 row)
1046+
1047+
select has_column_privilege('pg_authid','nosuchcol','select');
1048+
ERROR: column "nosuchcol" of relation "pg_authid" does not exist
1049+
select has_column_privilege(9999,'nosuchcol','select');
1050+
has_column_privilege
1051+
----------------------
1052+
1053+
(1 row)
1054+
1055+
select has_column_privilege(9999,99::int2,'select');
1056+
has_column_privilege
1057+
----------------------
1058+
1059+
(1 row)
1060+
1061+
select has_column_privilege('pg_authid',99::int2,'select');
1062+
has_column_privilege
1063+
----------------------
1064+
1065+
(1 row)
1066+
1067+
select has_column_privilege(9999,99::int2,'select');
1068+
has_column_privilege
1069+
----------------------
1070+
1071+
(1 row)
1072+
1073+
create temp table mytable(f1 int, f2 int, f3 int);
1074+
alter table mytable drop column f2;
1075+
select has_column_privilege('mytable','f2','select');
1076+
ERROR: column "f2" of relation "mytable" does not exist
1077+
select has_column_privilege('mytable','........pg.dropped.2........','select');
1078+
has_column_privilege
1079+
----------------------
1080+
1081+
(1 row)
1082+
1083+
select has_column_privilege('mytable',2::int2,'select');
1084+
has_column_privilege
1085+
----------------------
1086+
t
1087+
(1 row)
1088+
1089+
revoke select on table mytable from regressuser3;
1090+
select has_column_privilege('mytable',2::int2,'select');
1091+
has_column_privilege
1092+
----------------------
1093+
1094+
(1 row)
1095+
10391096
-- Grant options
10401097
SET SESSION AUTHORIZATION regressuser1;
10411098
CREATE TABLE atest4 (a int);

src/test/regress/sql/privileges.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,23 @@ from (select oid from pg_class where relname = 'atest1') as t1;
657657
select has_table_privilege(t1.oid,'trigger')
658658
from (select oid from pg_class where relname = 'atest1') as t1;
659659

660+
-- has_column_privilege function
661+
662+
-- bad-input checks (as non-super-user)
663+
select has_column_privilege('pg_authid',NULL,'select');
664+
select has_column_privilege('pg_authid','nosuchcol','select');
665+
select has_column_privilege(9999,'nosuchcol','select');
666+
select has_column_privilege(9999,99::int2,'select');
667+
select has_column_privilege('pg_authid',99::int2,'select');
668+
select has_column_privilege(9999,99::int2,'select');
669+
670+
create temp table mytable(f1 int, f2 int, f3 int);
671+
alter table mytable drop column f2;
672+
select has_column_privilege('mytable','f2','select');
673+
select has_column_privilege('mytable','........pg.dropped.2........','select');
674+
select has_column_privilege('mytable',2::int2,'select');
675+
revoke select on table mytable from regressuser3;
676+
select has_column_privilege('mytable',2::int2,'select');
660677

661678
-- Grant options
662679

0 commit comments

Comments
 (0)