[fix](nereids) reject correlated subqueries in CREATE ROW POLICY USING clause - #67540
Open
sinhaparth5 wants to merge 1 commit into
Open
[fix](nereids) reject correlated subqueries in CREATE ROW POLICY USING clause#67540sinhaparth5 wants to merge 1 commit into
sinhaparth5 wants to merge 1 commit into
Conversation
…G clause CREATE ROW POLICY with a correlated EXISTS subquery in the USING clause silently succeeded while storing nothing, so an admin could believe a row-level filter was active when it wasn't enforced at all. Exists (and the other SubqueryExpr subclasses, InSubquery and ScalarSubquery) implement LeafExpression, so their inner subquery plan isn't a child in the expression tree. CreatePolicyCommand .validate() checks that every column referenced in the USING clause exists on the target table by walking the predicate with wherePredicate.foreach(), but foreach() never descends into a LeafExpression's subquery plan, so everything inside an EXISTS was invisible to it. The predicate got stored as written. Later, when the policy is injected into a query plan, the subquery's reference back to the outer table can't be resolved there (the outer table is out of scope by that point), and the policy quietly turns into a no-op instead of raising an error. Fix: when validate()'s walk reaches a SubqueryExpr, manually walk into its query plan (via Plan.getExpressions() and Plan.foreach(), since foreach() won't do this on its own) looking for an UnboundSlot whose column exists on the table the policy is being created on. If one turns up, reject the CREATE ROW POLICY right away instead of storing a policy that will never fire. This is Option A from the issue: reject correlated subqueries at DDL time, rather than Option B (make correlation actually resolve end to end). It's name-based, not a real bind, so it can also reject a subquery whose own local table happens to share a column name with the outer table even when that reference would resolve locally (a self-join, say). That's an accepted trade-off: a false rejection here is safe, a silently dropped security policy is not. Added CreatePolicyCommandTest.java: a plain predicate (allowed), an uncorrelated EXISTS (allowed), and a correlated EXISTS referencing the outer table (rejected). Also made CreatePolicyCommand.validate() public (it was private) so the test can call it directly. DropRowPolicyCommand.validate() is already public, so this just matches its sibling. Fixes apache#62729
sinhaparth5
requested review from
924060929,
englefly,
morrySnow and
starocean999
as code owners
September 4, 2026 15:48
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What problem does this PR solve?
Issue Number: close #62729
Problem Summary:
This is a security issue.
CREATE ROW POLICYwith a correlatedEXISTSsubquery in theUSINGclause returns success but the policy is never stored or enforced, so an admin can believe a row-level filter is active when every row is actually visible.Exists(and the otherSubqueryExprsubclasses,InSubqueryandScalarSubquery) implementLeafExpression, so their inner subquery plan is not a child in the expression tree.CreatePolicyCommand.validate()checks that every column referenced in theUSINGclause exists on the target table by walking the predicate withwherePredicate.foreach(), butforeach()never descends into aLeafExpression's subquery plan, so anything inside anEXISTSis invisible to it. The predicate gets stored as written. Later, when the policy is injected into a query plan, the subquery's reference back to the outer table can't be resolved there (the outer table is out of scope by that point), and the policy silently becomes a no-op instead of raising an error.Fix: when
validate()'s walk reaches aSubqueryExpr, manually walk into its query plan (viaPlan.getExpressions()andPlan.foreach(), sinceforeach()won't do this on its own) looking for anUnboundSlotwhose column exists on the table the policy is being created on. If one turns up, reject theCREATE ROW POLICYstatement immediately instead of storing a policy that will never fire.This is Option A from the issue (reject correlated subqueries at DDL time), chosen over Option B (make correlation actually resolve end to end) as the safer, lower-risk fix. It's name-based rather than a real bind, so it can also reject a subquery whose own local table happens to share a column name with the outer table even when that reference would resolve locally (a self-join, for example). That's an intentional trade-off: a false rejection here is safe, a silently dropped security policy is not.
Also made
CreatePolicyCommand.validate()public (it was private) so the added test can call it directly.DropRowPolicyCommand.validate()is already public, so this just matches its sibling.Release note
Reject
CREATE ROW POLICYstatements whoseUSINGclause contains a subquery correlated to the outer table, instead of silently accepting the statement and never enforcing the policy.Check List (For Author)
Added
CreatePolicyCommandTest.javawith three cases, run viamvn -pl fe-common,fe-core -am test -Dtest=CreatePolicyCommandTest: a plain predicate (allowed), an uncorrelatedEXISTS(allowed), and a correlatedEXISTSreferencing the outer table (rejected). All 3 pass.Behavior changed:
CREATE ROW POLICYnow rejects aUSINGclause whose subquery is correlated to the outer table (previously accepted silently, with the policy never enforced).Does this need documentation?