Skip to content

ORCA: Fix use-after-free in flatten_join_alias_var_optimizer and incorrect HAVING decorrelation#1619

Closed
yjhjstz wants to merge 2 commits intomainfrom
yjhjstz/fix/orca-use-after-free-having-decorrelation
Closed

ORCA: Fix use-after-free in flatten_join_alias_var_optimizer and incorrect HAVING decorrelation#1619
yjhjstz wants to merge 2 commits intomainfrom
yjhjstz/fix/orca-use-after-free-having-decorrelation

Conversation

@yjhjstz
Copy link
Member

@yjhjstz yjhjstz commented Mar 13, 2026

Summary

  • Fix use-after-free in flatten_join_alias_var_optimizer where unconditional pfree/list_free freed live nodes when flatten_join_alias_vars returned the same pointer unchanged
  • Fix ORCA incorrectly decorrelating GROUP BY () HAVING <outer_ref> subqueries, which returned 0 instead of NULL when the HAVING condition was false

Root Cause

use-after-free (clauses.c): pfree(havingQual) freed the v.c Var node unconditionally. The freed memory was reused by palloc for a T_List (nodeTag=596). When copyObjectImpl later read havingQual, it copied the wrong node type, causing ORCA to encounter an unexpected RangeTblEntry and fall back to the Postgres planner — which masked the second bug.

ORCA decorrelation (CSubqueryHandler.cpp): After fixing the use-after-free, ORCA no longer fell back and attempted to decorrelate the subquery. It converted GROUP BY () HAVING <outer_ref> into Left Outer Join + COALESCE(count(*), 0), incorrectly returning 0 instead of NULL when the HAVING condition was false.

Fix

  1. Guard all pfree/list_free calls in flatten_join_alias_var_optimizer with pointer-equality checks
  2. Detect correlated CLogicalSelect above CLogicalGbAgg[GROUP BY()] in Psd() and force m_fCorrelatedExecution = true to use SubPlan instead of decorrelation
  3. Update groupingsets_optimizer.out expected output for the new ORCA SubPlan format

Test plan

  • groupingsets test passes with correct result (f | NULL, t | 9)
  • EXPLAIN confirms ORCA SubPlan execution instead of Postgres planner fallback

Fixes #1618

Copilot AI review requested due to automatic review settings March 13, 2026 19:54
yjhjstz added 2 commits March 13, 2026 12:54
Guard pfree/list_free calls with pointer-equality checks to avoid
freeing live nodes when flatten_join_alias_vars returns the same
pointer unchanged (e.g., outer-reference Vars with varlevelsup > 0).

The unconditional pfree(havingQual) freed the Var node, whose memory
was later reused by palloc for a T_List. copyObjectImpl then copied
the wrong node type into havingQual, causing ORCA to encounter an
unexpected RangeTblEntry and fall back to the Postgres planner.

Applies the same guard pattern to all six fields: targetList,
returningList, havingQual, scatterClause, limitOffset, limitCount.

Reported-in: #1618
Force correlated execution (SubPlan) for scalar subqueries with
GROUP BY () and a correlated HAVING clause. Previously ORCA
decorrelated such subqueries into Left Outer Join + COALESCE(count,0),
which incorrectly returned 0 instead of NULL when the HAVING
condition was false.

Add FHasCorrelatedSelectAboveGbAgg() to detect the pattern where
NormalizeHaving() has converted the HAVING clause into a
CLogicalSelect with outer refs above a CLogicalGbAgg with empty
grouping columns. When detected, set m_fCorrelatedExecution = true
in Psd() to bypass the COALESCE decorrelation path.

Update groupingsets_optimizer.out expected output to reflect the
new ORCA SubPlan format instead of Postgres planner fallback.

Reported-in: #1618
@yjhjstz yjhjstz force-pushed the yjhjstz/fix/orca-use-after-free-having-decorrelation branch from 69a2a53 to 9031693 Compare March 13, 2026 19:55
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes two ORCA-related correctness issues: a use-after-free in query normalization (flatten_join_alias_var_optimizer) and an incorrect decorrelation for scalar subqueries of the form GROUP BY () HAVING <outer_ref>, which could produce 0 instead of NULL.

Changes:

  • Guard pfree()/list_free() in flatten_join_alias_var_optimizer() to avoid freeing nodes when flatten_join_alias_vars() returns the same pointer.
  • Add ORCA detection logic intended to prevent decorrelation for correlated GROUP BY () HAVING <outer_ref> scalar subqueries and avoid applying COALESCE(count, 0) in that case.
  • Update groupingsets_optimizer.out expected output to reflect GPORCA SubPlan execution.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/backend/optimizer/util/clauses.c Prevents UAF by only freeing old pointers when flattening returns a different node/list.
src/backend/gporca/libgpopt/src/xforms/CSubqueryHandler.cpp Adds pattern detection to avoid incorrect decorrelation/coalesce semantics for specific HAVING-over-empty-GbAgg cases.
src/test/regress/expected/groupingsets_optimizer.out Updates expected EXPLAIN output to match the new GPORCA plan shape (SubPlan path).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +366 to +376
if (COperator::EopLogicalSelect == pexpr->Pop()->Eopid() &&
pexpr->HasOuterRefs())
{
// The Select has outer references somewhere in its subtree.
// Check whether they originate from the filter (child 1) rather
// than from the logical child (child 0). If the logical child has
// no outer refs but the Select as a whole does, the outer refs must
// come from the filter predicate — exactly the correlated-HAVING
// pattern we want to detect.
CExpression *pexprLogicalChild = (*pexpr)[0];
if (!pexprLogicalChild->HasOuterRefs() &&
Comment on lines +859 to +870
// When GROUP BY () has a correlated HAVING clause (now represented as a
// CLogicalSelect with outer refs sitting above the GbAgg), the subquery
// must return NULL — not 0 — when the HAVING condition is false.
// Applying COALESCE(count,0) would incorrectly convert that NULL to 0,
// so we skip the special count(*) semantics in that case.
BOOL fCorrelatedHavingAboveEmptyGby =
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size() &&
FHasCorrelatedSelectAboveGbAgg((*pexprSubquery)[0]));

if (fGeneratedByQuantified ||
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size()))
(fHasCountAggMatchingColumn && 0 == pgbAgg->Pdrgpcr()->Size() &&
!fCorrelatedHavingAboveEmptyGby))
@yjhjstz yjhjstz closed this Mar 13, 2026
@yjhjstz yjhjstz deleted the yjhjstz/fix/orca-use-after-free-having-decorrelation branch March 13, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: flatten_join_alias_var_optimizer unconditional pfree causes use-after-free, triggering ORCA fallback via T_List type confusion

2 participants