FOR PORTION OF vs. object_aclcheck

First seen: 2026-05-04 13:36:16+00:00 · Messages: 1 · Participants: 1

Latest Update

2026-05-06 · opus 4.7

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:

  1. Only two built-in functions can be placed in withoutPortionProc, and both have EXECUTE granted to PUBLIC by default (as do essentially all range/multirange operators' support functions).
  2. The user cannot influence which function gets chosen — transformForPortionOfClause derives 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:

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.