Skip to content

Commit 6d73983

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 0353f48 commit 6d73983

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
@@ -1079,6 +1079,63 @@ from (select oid from pg_class where relname = 'atest1') as t1;
10791079
f
10801080
(1 row)
10811081

1082+
-- has_column_privilege function
1083+
-- bad-input checks (as non-super-user)
1084+
select has_column_privilege('pg_authid',NULL,'select');
1085+
has_column_privilege
1086+
----------------------
1087+
1088+
(1 row)
1089+
1090+
select has_column_privilege('pg_authid','nosuchcol','select');
1091+
ERROR: column "nosuchcol" of relation "pg_authid" does not exist
1092+
select has_column_privilege(9999,'nosuchcol','select');
1093+
has_column_privilege
1094+
----------------------
1095+
1096+
(1 row)
1097+
1098+
select has_column_privilege(9999,99::int2,'select');
1099+
has_column_privilege
1100+
----------------------
1101+
1102+
(1 row)
1103+
1104+
select has_column_privilege('pg_authid',99::int2,'select');
1105+
has_column_privilege
1106+
----------------------
1107+
1108+
(1 row)
1109+
1110+
select has_column_privilege(9999,99::int2,'select');
1111+
has_column_privilege
1112+
----------------------
1113+
1114+
(1 row)
1115+
1116+
create temp table mytable(f1 int, f2 int, f3 int);
1117+
alter table mytable drop column f2;
1118+
select has_column_privilege('mytable','f2','select');
1119+
ERROR: column "f2" of relation "mytable" does not exist
1120+
select has_column_privilege('mytable','........pg.dropped.2........','select');
1121+
has_column_privilege
1122+
----------------------
1123+
1124+
(1 row)
1125+
1126+
select has_column_privilege('mytable',2::int2,'select');
1127+
has_column_privilege
1128+
----------------------
1129+
t
1130+
(1 row)
1131+
1132+
revoke select on table mytable from regress_user3;
1133+
select has_column_privilege('mytable',2::int2,'select');
1134+
has_column_privilege
1135+
----------------------
1136+
1137+
(1 row)
1138+
10821139
-- Grant options
10831140
SET SESSION AUTHORIZATION regress_user1;
10841141
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
@@ -689,6 +689,23 @@ from (select oid from pg_class where relname = 'atest1') as t1;
689689
select has_table_privilege(t1.oid,'trigger')
690690
from (select oid from pg_class where relname = 'atest1') as t1;
691691

692+
-- has_column_privilege function
693+
694+
-- bad-input checks (as non-super-user)
695+
select has_column_privilege('pg_authid',NULL,'select');
696+
select has_column_privilege('pg_authid','nosuchcol','select');
697+
select has_column_privilege(9999,'nosuchcol','select');
698+
select has_column_privilege(9999,99::int2,'select');
699+
select has_column_privilege('pg_authid',99::int2,'select');
700+
select has_column_privilege(9999,99::int2,'select');
701+
702+
create temp table mytable(f1 int, f2 int, f3 int);
703+
alter table mytable drop column f2;
704+
select has_column_privilege('mytable','f2','select');
705+
select has_column_privilege('mytable','........pg.dropped.2........','select');
706+
select has_column_privilege('mytable',2::int2,'select');
707+
revoke select on table mytable from regress_user3;
708+
select has_column_privilege('mytable',2::int2,'select');
692709

693710
-- Grant options
694711

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy