FOR PORTION OF vs. object_aclcheck — Analysis
Context and Architectural Background
FOR PORTION OF is the SQL:2011 temporal-update feature that allows UPDATE/DELETE to affect only a sub-range of a row's application-time period. When a row's period only partially overlaps the target range, the executor must synthesize "leftover" rows covering the portions that were not modified. In PostgreSQL's implementation (committed for v18), this synthesis is performed by ExecForPortionOfLeftovers in the executor, which subtracts the targeted range from the original period using a "without-portion" function — currently hard-wired by the planner/parser to either range_minus_multi (F_RANGE_MINUS_MULTI) or multirange_minus_multi (F_MULTIRANGE_MINUS_MULTI).
The function OID to call is stashed in ForPortionOfExpr.withoutPortionProc during parse analysis (transformForPortionOfClause) and turned into an FmgrInfo at executor startup via fmgr_info.
The Core Problem Robert Haas Raises
ExecForPortionOfLeftovers calls fmgr_info(withoutPortionProc, ...) and later invokes the function without ever consulting object_aclcheck(OBJECT_FUNCTION, ..., ACL_EXECUTE) against the current user. This is a deviation from PostgreSQL's general invariant that any SQL-reachable function call should be gated by EXECUTE privilege on the function.
Today this is latent/harmless because:
- Only two built-in functions can be placed in
withoutPortionProc, and both haveEXECUTEgranted toPUBLICby default (as do essentially all range/multirange operators' support functions). - The user cannot influence which function gets chosen —
transformForPortionOfClausederives it purely from the column's type (range vs. multirange), not from anything user-controllable.
So the missing check is not currently a vulnerability. Haas's concern is forward-looking and is anchored by an explicit XXX comment already in the code:
"Find a more extensible way to look up the function, permitting user-defined types. An opclass support function doesn't make sense, since there is no index involved. Perhaps a type support function."
If that TODO is ever acted on — e.g., dispatching through a type-support function so that user-defined range-like types can participate in FOR PORTION OF — then the OID lodged in withoutPortionProc becomes (potentially) attacker-influenced, at which point the absence of an ACL check transitions from cosmetic to a CVE-class bug.
Design Tradeoff
Haas frames this as a defense-in-depth question with asymmetric payoff:
- Cost of adding the check now: essentially zero — one
object_aclcheckcall at executor-initialization time, negligible overhead, and semantically consistent with how every other user-invokable function in the system is gated. - Cost of not adding it: today nothing; tomorrow, depending on how the TODO is resolved, a security bug that an audit might miss because the surrounding code "looks fine."
He also highlights a secondary but legitimate consequence: if a DBA were to REVOKE EXECUTE ... ON FUNCTION range_minus_multi(...) FROM PUBLIC, a reasonable operator expectation is that the function becomes uncallable through all SQL-reachable paths. Today FOR PORTION OF would silently continue to call it, which violates the principle of least surprise for the ACL system. Even if the DBA didn't predict that consequence, Haas argues the consistent behavior (enforce the ACL) is the defensible one.
Where the Check Belongs
The natural placement is in ExecForPortionOfLeftovers (or its init path) just before fmgr_info, mirroring patterns used elsewhere in the executor — e.g., ExecInitFunctionScan, aggregate/window function setup, and the type I/O paths that do object_aclcheck(OBJECT_FUNCTION, funcid, GetUserId(), ACL_EXECUTE). Doing it at plan-startup (rather than per-row) keeps the cost O(1) per query.
An alternative would be to push the check into parse analysis (transformForPortionOfClause), but that is weaker: privileges can change between PREPARE and EXECUTE, and cached plans would bypass a parse-time-only check. Executor-time checks are the established convention precisely for this reason, and Haas's phrasing ("the user actually executing the plan is currently in possession of the required ACL_EXECUTE permission") signals he expects the executor-side placement.
Broader Pattern
This fits a recurring hardening motif in recent PostgreSQL development: when the system embeds a function OID into a plan node on behalf of the user, the executor should re-verify ACL_EXECUTE at runtime rather than trust that the OID was "safe" at planning time. Similar reasoning has driven ACL checks around RLS-expanded expressions, default expressions, and generated columns. withoutPortionProc was simply overlooked because the current hard-coded OIDs made the oversight invisible.
Status
The thread as presented contains only Haas's opening message — a "Thoughts?" solicitation rather than a patch. No counter-arguments, alternative designs, or commits are recorded here. Given Haas's committer status and the near-zero cost of the proposed change, the likely resolution is a small hardening patch adding object_aclcheck in ExecForPortionOfLeftovers, with no user-visible behavior change under default ACLs.