From 1a1aa7c374a6b8a72883768aab258d8b8cb8c9e4 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Thu, 25 Jun 2026 04:16:03 +0000
Subject: [PATCH v28 4/5] Discard ACL tracking entries from aborted
 subtransactions

Store SubTransactionId in each AclCheckEntry at recording time. On
subtransaction abort, AtEOSubXact_AclTrack() discards entries whose subxid
is >= the aborting subtransaction's ID.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Discussion: https://postgr.es/m/ZiYjn0eVc7pxVY45@ip-10-97-1-34.eu-west-3.compute.internal
---
 src/backend/access/transam/xact.c    |  2 ++
 src/backend/catalog/aclchk.c         | 24 ++++++++++++++++++++++++
 src/include/catalog/aclcheck_track.h |  4 ++++
 3 files changed, 30 insertions(+)
   9.0% src/backend/access/transam/
  73.2% src/backend/catalog/
  17.7% src/include/catalog/

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index de4cf96eaa2..90bee71d670 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -32,6 +32,7 @@
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "catalog/aclcheck_track.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_enum.h"
@@ -5386,6 +5387,7 @@ AbortSubTransaction(void)
 		if (FullTransactionIdIsValid(s->fullTransactionId))
 			AtSubAbort_childXids();
 
+		AtEOSubXact_AclTrack(false, s->subTransactionId);
 		CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, s->subTransactionId,
 							 s->parent->subTransactionId);
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index fb51ea2e869..5bd4ecf7130 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -5075,3 +5075,27 @@ FreeTrackAclTable(TrackAclTable *acltable)
 	pfree(acltable->entries);
 	pfree(acltable);
 }
+
+/*
+ * AtEOSubXact_AclTrack
+ *
+ * At subtransaction abort, discard any ACL tracking entries that were added
+ * during the aborted subtransaction. This prevents stale entries from causing
+ * false permission-denied errors in recheckAcl().
+ *
+ * At subtransaction commit, do nothing, entries from committed subtransactions
+ * are valid and should be kept.
+ */
+void
+AtEOSubXact_AclTrack(bool isCommit, SubTransactionId mySubid)
+{
+	TrackAclTable *acltable = CurrentTrackAclTable;
+
+	if (acltable == NULL || isCommit)
+		return;
+
+	/* Discard entries from the aborting subtransaction and any nested ones */
+	while (acltable->count > 0 &&
+		   acltable->entries[acltable->count - 1].subxid >= mySubid)
+		acltable->count--;
+}
diff --git a/src/include/catalog/aclcheck_track.h b/src/include/catalog/aclcheck_track.h
index 4ac65e4de5c..27f6c47eb43 100644
--- a/src/include/catalog/aclcheck_track.h
+++ b/src/include/catalog/aclcheck_track.h
@@ -23,6 +23,7 @@
 #ifndef ACLCHECK_TRACK_H
 #define ACLCHECK_TRACK_H
 
+#include "access/xact.h"
 #include "storage/sinval.h"
 #include "utils/acl.h"
 
@@ -34,6 +35,7 @@ typedef struct AclCheckEntry
 	Oid			roleId;
 	AclMode		mode;
 	uint64		inval_count;
+	SubTransactionId subxid;
 } AclCheckEntry;
 
 typedef struct TrackAclTable
@@ -47,6 +49,7 @@ extern TrackAclTable *CurrentTrackAclTable;
 
 extern TrackAclTable *CreateTrackAclTable(void);
 extern void FreeTrackAclTable(TrackAclTable *acltable);
+extern void AtEOSubXact_AclTrack(bool isCommit, SubTransactionId mySubid);
 
 /*
  * Record an aclcheck for later revalidation.
@@ -79,6 +82,7 @@ aclcheck_track_record(Oid classId, Oid objectId, AttrNumber attnum,
 	entry->roleId = roleId;
 	entry->mode = mode;
 	entry->inval_count = SharedInvalidMessageCounter;
+	entry->subxid = GetCurrentSubTransactionId();
 }
 
 #endif							/* ACLCHECK_TRACK_H */
-- 
2.34.1

