Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b12bd48

Browse files
committed
Fix has_column_privilege function corner case
According to the comments, when an invalid or dropped column oid is passed to has_column_privilege(), the intention has always been to return NULL. However, when the caller had table level privilege the invalid/missing column was never discovered, because table permissions were checked first. Fix that by introducing extended versions of pg_attribute_acl(check|mask) and pg_class_acl(check|mask) which take a new argument, is_missing. When is_missing is NULL, the old behavior is preserved. But when is_missing is passed by the caller, no ERROR is thrown for dropped or missing columns/relations, and is_missing is flipped to true. This in turn allows has_column_privilege to check for column privileges first, providing the desired semantics. Not backpatched since it is a user visible behavioral change with no previous complaints, and the fix is a bit on the invasive side. Author: Joe Conway Reviewed-By: Tom Lane Reported by: Ian Barwick Discussion: https://postgr.es/m/flat/9b5f4311-157b-4164-7fe7-077b4fe8ed84%40joeconway.com
1 parent 86dc900 commit b12bd48

File tree

5 files changed

+142
-49
lines changed

5 files changed

+142
-49
lines changed

src/backend/catalog/aclchk.c

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3705,6 +3705,20 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
37053705
AclMode
37063706
pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid,
37073707
AclMode mask, AclMaskHow how)
3708+
{
3709+
return pg_attribute_aclmask_ext(table_oid, attnum, roleid,
3710+
mask, how, NULL);
3711+
}
3712+
3713+
/*
3714+
* Exported routine for examining a user's privileges for a column
3715+
*
3716+
* Does the bulk of the work for pg_attribute_aclmask(), and allows other
3717+
* callers to avoid the missing attribute ERROR when is_missing is non-NULL.
3718+
*/
3719+
AclMode
3720+
pg_attribute_aclmask_ext(Oid table_oid, AttrNumber attnum, Oid roleid,
3721+
AclMode mask, AclMaskHow how, bool *is_missing)
37083722
{
37093723
AclMode result;
37103724
HeapTuple classTuple;
@@ -3723,18 +3737,38 @@ pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid,
37233737
ObjectIdGetDatum(table_oid),
37243738
Int16GetDatum(attnum));
37253739
if (!HeapTupleIsValid(attTuple))
3726-
ereport(ERROR,
3727-
(errcode(ERRCODE_UNDEFINED_COLUMN),
3728-
errmsg("attribute %d of relation with OID %u does not exist",
3729-
attnum, table_oid)));
3740+
{
3741+
if (is_missing != NULL)
3742+
{
3743+
/* return "no privileges" instead of throwing an error */
3744+
*is_missing = true;
3745+
return 0;
3746+
}
3747+
else
3748+
ereport(ERROR,
3749+
(errcode(ERRCODE_UNDEFINED_COLUMN),
3750+
errmsg("attribute %d of relation with OID %u does not exist",
3751+
attnum, table_oid)));
3752+
}
3753+
37303754
attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
37313755

3732-
/* Throw error on dropped columns, too */
3756+
/* Check dropped columns, too */
37333757
if (attributeForm->attisdropped)
3734-
ereport(ERROR,
3735-
(errcode(ERRCODE_UNDEFINED_COLUMN),
3736-
errmsg("attribute %d of relation with OID %u does not exist",
3737-
attnum, table_oid)));
3758+
{
3759+
if (is_missing != NULL)
3760+
{
3761+
/* return "no privileges" instead of throwing an error */
3762+
*is_missing = true;
3763+
ReleaseSysCache(attTuple);
3764+
return 0;
3765+
}
3766+
else
3767+
ereport(ERROR,
3768+
(errcode(ERRCODE_UNDEFINED_COLUMN),
3769+
errmsg("attribute %d of relation with OID %u does not exist",
3770+
attnum, table_oid)));
3771+
}
37383772

37393773
aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
37403774
&isNull);
@@ -3790,6 +3824,19 @@ pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid,
37903824
AclMode
37913825
pg_class_aclmask(Oid table_oid, Oid roleid,
37923826
AclMode mask, AclMaskHow how)
3827+
{
3828+
return pg_class_aclmask_ext(table_oid, roleid, mask, how, NULL);
3829+
}
3830+
3831+
/*
3832+
* Exported routine for examining a user's privileges for a table
3833+
*
3834+
* Does the bulk of the work for pg_class_aclmask(), and allows other
3835+
* callers to avoid the missing relation ERROR when is_missing is non-NULL.
3836+
*/
3837+
AclMode
3838+
pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
3839+
AclMaskHow how, bool *is_missing)
37933840
{
37943841
AclMode result;
37953842
HeapTuple tuple;
@@ -3804,10 +3851,20 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
38043851
*/
38053852
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
38063853
if (!HeapTupleIsValid(tuple))
3807-
ereport(ERROR,
3808-
(errcode(ERRCODE_UNDEFINED_TABLE),
3809-
errmsg("relation with OID %u does not exist",
3810-
table_oid)));
3854+
{
3855+
if (is_missing != NULL)
3856+
{
3857+
/* return "no privileges" instead of throwing an error */
3858+
*is_missing = true;
3859+
return 0;
3860+
}
3861+
else
3862+
ereport(ERROR,
3863+
(errcode(ERRCODE_UNDEFINED_TABLE),
3864+
errmsg("relation with OID %u does not exist",
3865+
table_oid)));
3866+
}
3867+
38113868
classForm = (Form_pg_class) GETSTRUCT(tuple);
38123869

38133870
/*
@@ -4468,7 +4525,22 @@ AclResult
44684525
pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
44694526
Oid roleid, AclMode mode)
44704527
{
4471-
if (pg_attribute_aclmask(table_oid, attnum, roleid, mode, ACLMASK_ANY) != 0)
4528+
return pg_attribute_aclcheck_ext(table_oid, attnum, roleid, mode, NULL);
4529+
}
4530+
4531+
4532+
/*
4533+
* Exported routine for checking a user's access privileges to a column
4534+
*
4535+
* Does the bulk of the work for pg_attribute_aclcheck(), and allows other
4536+
* callers to avoid the missing attribute ERROR when is_missing is non-NULL.
4537+
*/
4538+
AclResult
4539+
pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum,
4540+
Oid roleid, AclMode mode, bool *is_missing)
4541+
{
4542+
if (pg_attribute_aclmask_ext(table_oid, attnum, roleid, mode,
4543+
ACLMASK_ANY, is_missing) != 0)
44724544
return ACLCHECK_OK;
44734545
else
44744546
return ACLCHECK_NO_PRIV;
@@ -4581,7 +4653,21 @@ pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode,
45814653
AclResult
45824654
pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode)
45834655
{
4584-
if (pg_class_aclmask(table_oid, roleid, mode, ACLMASK_ANY) != 0)
4656+
return pg_class_aclcheck_ext(table_oid, roleid, mode, NULL);
4657+
}
4658+
4659+
/*
4660+
* Exported routine for checking a user's access privileges to a table
4661+
*
4662+
* Does the bulk of the work for pg_class_aclcheck(), and allows other
4663+
* callers to avoid the missing relation ERROR when is_missing is non-NULL.
4664+
*/
4665+
AclResult
4666+
pg_class_aclcheck_ext(Oid table_oid, Oid roleid,
4667+
AclMode mode, bool *is_missing)
4668+
{
4669+
if (pg_class_aclmask_ext(table_oid, roleid, mode,
4670+
ACLMASK_ANY, is_missing) != 0)
45854671
return ACLCHECK_OK;
45864672
else
45874673
return ACLCHECK_NO_PRIV;

src/backend/utils/adt/acl.c

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,8 +2444,7 @@ column_privilege_check(Oid tableoid, AttrNumber attnum,
24442444
Oid roleid, AclMode mode)
24452445
{
24462446
AclResult aclresult;
2447-
HeapTuple attTuple;
2448-
Form_pg_attribute attributeForm;
2447+
bool is_missing = false;
24492448

24502449
/*
24512450
* If convert_column_name failed, we can just return -1 immediately.
@@ -2454,42 +2453,25 @@ column_privilege_check(Oid tableoid, AttrNumber attnum,
24542453
return -1;
24552454

24562455
/*
2457-
* First check if we have the privilege at the table level. We check
2458-
* existence of the pg_class row before risking calling pg_class_aclcheck.
2459-
* Note: it might seem there's a race condition against concurrent DROP,
2460-
* but really it's safe because there will be no syscache flush between
2461-
* here and there. So if we see the row in the syscache, so will
2462-
* pg_class_aclcheck.
2456+
* Check for column-level privileges first. This serves in
2457+
* part as a check on whether the column even exists, so we
2458+
* need to do it before checking table-level privilege.
24632459
*/
2464-
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid)))
2460+
aclresult = pg_attribute_aclcheck_ext(tableoid, attnum, roleid,
2461+
mode, &is_missing);
2462+
if (aclresult == ACLCHECK_OK)
2463+
return 1;
2464+
else if (is_missing)
24652465
return -1;
24662466

2467-
aclresult = pg_class_aclcheck(tableoid, roleid, mode);
2468-
2467+
/* Next check if we have the privilege at the table level */
2468+
aclresult = pg_class_aclcheck_ext(tableoid, roleid, mode, &is_missing);
24692469
if (aclresult == ACLCHECK_OK)
2470-
return true;
2471-
2472-
/*
2473-
* No table privilege, so try per-column privileges. Again, we have to
2474-
* check for dropped attribute first, and we rely on the syscache not to
2475-
* notice a concurrent drop before pg_attribute_aclcheck fetches the row.
2476-
*/
2477-
attTuple = SearchSysCache2(ATTNUM,
2478-
ObjectIdGetDatum(tableoid),
2479-
Int16GetDatum(attnum));
2480-
if (!HeapTupleIsValid(attTuple))
2481-
return -1;
2482-
attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
2483-
if (attributeForm->attisdropped)
2484-
{
2485-
ReleaseSysCache(attTuple);
2470+
return 1;
2471+
else if (is_missing)
24862472
return -1;
2487-
}
2488-
ReleaseSysCache(attTuple);
2489-
2490-
aclresult = pg_attribute_aclcheck(tableoid, attnum, roleid, mode);
2491-
2492-
return (aclresult == ACLCHECK_OK);
2473+
else
2474+
return 0;
24932475
}
24942476

24952477
/*

src/include/utils/acl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,14 @@ extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid);
233233

234234
extern AclMode pg_attribute_aclmask(Oid table_oid, AttrNumber attnum,
235235
Oid roleid, AclMode mask, AclMaskHow how);
236+
extern AclMode pg_attribute_aclmask_ext(Oid table_oid, AttrNumber attnum,
237+
Oid roleid, AclMode mask,
238+
AclMaskHow how, bool *is_missing);
236239
extern AclMode pg_class_aclmask(Oid table_oid, Oid roleid,
237240
AclMode mask, AclMaskHow how);
241+
extern AclMode pg_class_aclmask_ext(Oid table_oid, Oid roleid,
242+
AclMode mask, AclMaskHow how,
243+
bool *is_missing);
238244
extern AclMode pg_database_aclmask(Oid db_oid, Oid roleid,
239245
AclMode mask, AclMaskHow how);
240246
extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid,
@@ -256,9 +262,14 @@ extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
256262

257263
extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
258264
Oid roleid, AclMode mode);
265+
extern AclResult pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum,
266+
Oid roleid, AclMode mode,
267+
bool *is_missing);
259268
extern AclResult pg_attribute_aclcheck_all(Oid table_oid, Oid roleid,
260269
AclMode mode, AclMaskHow how);
261270
extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
271+
extern AclResult pg_class_aclcheck_ext(Oid table_oid, Oid roleid,
272+
AclMode mode, bool *is_missing);
262273
extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
263274
extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
264275
extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);

src/test/regress/expected/privileges.out

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1362,7 +1362,13 @@ select has_column_privilege('mytable','........pg.dropped.2........','select');
13621362
select has_column_privilege('mytable',2::int2,'select');
13631363
has_column_privilege
13641364
----------------------
1365-
t
1365+
1366+
(1 row)
1367+
1368+
select has_column_privilege('mytable',99::int2,'select');
1369+
has_column_privilege
1370+
----------------------
1371+
13661372
(1 row)
13671373

13681374
revoke select on table mytable from regress_priv_user3;
@@ -1372,6 +1378,12 @@ select has_column_privilege('mytable',2::int2,'select');
13721378

13731379
(1 row)
13741380

1381+
select has_column_privilege('mytable',99::int2,'select');
1382+
has_column_privilege
1383+
----------------------
1384+
1385+
(1 row)
1386+
13751387
drop table mytable;
13761388
-- Grant options
13771389
SET SESSION AUTHORIZATION regress_priv_user1;

src/test/regress/sql/privileges.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,8 +836,10 @@ alter table mytable drop column f2;
836836
select has_column_privilege('mytable','f2','select');
837837
select has_column_privilege('mytable','........pg.dropped.2........','select');
838838
select has_column_privilege('mytable',2::int2,'select');
839+
select has_column_privilege('mytable',99::int2,'select');
839840
revoke select on table mytable from regress_priv_user3;
840841
select has_column_privilege('mytable',2::int2,'select');
842+
select has_column_privilege('mytable',99::int2,'select');
841843
drop table mytable;
842844

843845
-- Grant options

0 commit comments

Comments
 (0)