From 8665017b0bbf42d867ebebe90a80724dfdb84847 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Tue, 16 Jun 2026 07:33:39 +0000 Subject: [PATCH v27 3/3] Add pg_attribute_aclcheck_ext to ACL tracking for dependency recording Add an attnum field to AclCheckEntry so that recheckAcl() can distinguish column-level checks from table-level checks and call the appropriate recheck function. InvalidAttrNumber means whole-object check. This also adds tracking to pg_attribute_aclcheck_ext(), so that future DDL paths using column-level privileges will automatically get TOCTOU protection without needing a pre-existing lock on the table. Currently, the only caller of pg_attribute_aclcheck_ext() that also records dependencies on the checked object is checkFkeyPermissions(), which holds ShareRowExclusiveLock on the referenced table. While this prevents concurrent REVOKE ON the table itself, it does not prevent concurrent role membership revokes. Adding tracking here provides protection against that case and future DDL callers. The remaining aclcheck functions (pg_parameter_aclcheck and pg_largeobject_aclcheck_snapshot) do not need tracking: pg_parameter_aclcheck checks GUC parameters which are not dependency targets, and pg_largeobject_aclcheck_snapshot is only called from inv_open() and has_largeobject_privilege(), neither of which records dependencies. Author: Bertrand Drouvot Reviewed-by: Jeff Davis Discussion: https://postgr.es/m/ZiYjn0eVc7pxVY45@ip-10-97-1-34.eu-west-3.compute.internal --- src/backend/catalog/aclchk.c | 7 +++++-- src/backend/catalog/pg_depend.c | 7 ++++++- src/include/catalog/aclcheck_track.h | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) 64.1% src/backend/catalog/ 35.8% src/include/catalog/ diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 02c4489f0c4..fb51ea2e869 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -3899,7 +3899,7 @@ object_aclcheck_ext(Oid classid, Oid objectid, if (object_aclmask_ext(classid, objectid, roleid, mode, ACLMASK_ANY, is_missing) != 0) { - aclcheck_track_record(classid, objectid, roleid, mode); + aclcheck_track_record(classid, objectid, InvalidAttrNumber, roleid, mode); return ACLCHECK_OK; } else @@ -3934,7 +3934,10 @@ pg_attribute_aclcheck_ext(Oid table_oid, AttrNumber attnum, { if (pg_attribute_aclmask_ext(table_oid, attnum, roleid, mode, ACLMASK_ANY, is_missing) != 0) + { + aclcheck_track_record(RelationRelationId, table_oid, attnum, roleid, mode); return ACLCHECK_OK; + } else return ACLCHECK_NO_PRIV; } @@ -4104,7 +4107,7 @@ pg_class_aclcheck_ext(Oid table_oid, Oid roleid, if (pg_class_aclmask_ext(table_oid, roleid, mode, ACLMASK_ANY, is_missing) != 0) { - aclcheck_track_record(RelationRelationId, table_oid, roleid, mode); + aclcheck_track_record(RelationRelationId, table_oid, InvalidAttrNumber, roleid, mode); return ACLCHECK_OK; } else diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 912c92e8fef..88a27fdc802 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -773,7 +773,12 @@ recheckAcl(Oid classId, Oid objectId) { AclResult aclresult; - if (classId == RelationRelationId) + if (acltable->entries[i].attnum != InvalidAttrNumber) + aclresult = pg_attribute_aclcheck(objectId, + acltable->entries[i].attnum, + acltable->entries[i].roleId, + acltable->entries[i].mode); + else if (classId == RelationRelationId) aclresult = pg_class_aclcheck(objectId, acltable->entries[i].roleId, acltable->entries[i].mode); diff --git a/src/include/catalog/aclcheck_track.h b/src/include/catalog/aclcheck_track.h index d994cf7889b..4ac65e4de5c 100644 --- a/src/include/catalog/aclcheck_track.h +++ b/src/include/catalog/aclcheck_track.h @@ -30,6 +30,7 @@ typedef struct AclCheckEntry { Oid classId; Oid objectId; + AttrNumber attnum; Oid roleId; AclMode mode; uint64 inval_count; @@ -50,11 +51,13 @@ extern void FreeTrackAclTable(TrackAclTable *acltable); /* * Record an aclcheck for later revalidation. * - * Called from object_aclcheck_ext() and pg_class_aclcheck_ext(). + * Called from object_aclcheck_ext(), pg_class_aclcheck_ext(), and + * pg_attribute_aclcheck_ext(). * Only records when inside an utility statement. */ static inline void -aclcheck_track_record(Oid classId, Oid objectId, Oid roleId, AclMode mode) +aclcheck_track_record(Oid classId, Oid objectId, AttrNumber attnum, + Oid roleId, AclMode mode) { TrackAclTable *acltable = CurrentTrackAclTable; AclCheckEntry *entry; @@ -72,6 +75,7 @@ aclcheck_track_record(Oid classId, Oid objectId, Oid roleId, AclMode mode) entry = &acltable->entries[acltable->count++]; entry->classId = classId; entry->objectId = objectId; + entry->attnum = attnum; entry->roleId = roleId; entry->mode = mode; entry->inval_count = SharedInvalidMessageCounter; -- 2.34.1