From b6a9184c9202a84c5fa925cb87924b47bbe8aeca Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Thu, 9 Jul 2026 15:06:39 +0200 Subject: [PATCH] Fix relcache's equalPolicy() to compare the permissive/restrictive flag equalPolicy() is used by equalRSDesc(), which RelationRebuildRelation() consults when rebuilding an open relation's relcache entry in place (refcount > 0) to decide whether the entry's existing row-security descriptor can be kept as-is or must be treated as changed. It compared polcmd, hassublinks, policy_name, roles, qual, and with_check_qual, but never permissive, so two policies that are identical in every other respect but differ in their PERMISSIVE/RESTRICTIVE designation were reported as equal, and the stale descriptor (with the old designation) would be kept. This is reachable without any unsupported catalog surgery: ALTER POLICY has no way to change a policy's PERMISSIVE/RESTRICTIVE designation, but DROP POLICY followed by CREATE POLICY of the same name, roles, command, and quals, differing only in AS PERMISSIVE/RESTRICTIVE, hits exactly this case, and would leave the relcache out of sync with how the policy should now combine with others (PERMISSIVE policies are ORed together; RESTRICTIVE policies are ANDed with the rest). Note that RelationRebuildRelation(), and thus this comparison, is only reached while the relation is still open (refcount > 0) at the moment its invalidation is locally processed; if nothing holds it open at that point, RelationClearRelation() just deletes the entry outright and it gets rebuilt from scratch on next access, sidestepping the comparison entirely. Consequently this isn't reachable through a straightforward sequence of ordinary SQL statements: closing and reopening the relation between statements (the usual behavior) avoids the bug, and a second session cannot hold the relation open across the DROP/CREATE either, since both require AccessExclusiveLock. As with ab6d1cd26eb, which fixed the same kind of omission in this function for the USING qual, this is a fix for an internal cache-consistency invariant rather than something with a simple externally observable trigger, and is submitted without a reproduction script for that reason. --- src/backend/utils/cache/relcache.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index fb4e042be8a..19c4ff6e75e 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -980,6 +980,8 @@ equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2) if (policy1->polcmd != policy2->polcmd) return false; + if (policy1->permissive != policy2->permissive) + return false; if (policy1->hassublinks != policy2->hassublinks) return false; if (strcmp(policy1->policy_name, policy2->policy_name) != 0) -- 2.50.1 (Apple Git-155)