Skip to content

[fix](fe) Fix Ranger column-level privilege bypass when CTE combined …#61741

Open
smith1000 wants to merge 2 commits intoapache:masterfrom
smith1000:fix/cte-privilege-bypass-master
Open

[fix](fe) Fix Ranger column-level privilege bypass when CTE combined …#61741
smith1000 wants to merge 2 commits intoapache:masterfrom
smith1000:fix/cte-privilege-bypass-master

Conversation

@smith1000
Copy link

What problem does this PR solve?

Issue Number: close #61631

Problem Summary: When a CTE (WITH ... AS) is referenced multiple times in a
JOIN query and is not inlined (due to inlineCTEReferencedThreshold), the
CheckPrivileges rule only runs once per statement because the privChecked flag
is stored on StatementContext which is shared across all CascadesContext
subtrees. After the outer query's CascadesContext marks privChecked=true,
the CTE producer subtree's CascadesContext skips privilege checking entirely,
allowing users without column-level access to bypass Ranger authorization.

The fix moves the privChecked flag from StatementContext to CascadesContext,
ensuring each subtree (including CTE producer subtrees) performs its own
independent privilege check.

Release note

Fixed a security issue where Ranger column-level privileges could be bypassed
when using CTE (WITH ... AS) combined with JOIN queries. Users without proper
column access permissions could read restricted columns through CTE+JOIN
patterns.

Check List (For Author)

  • Test: Manual test (verified with Ranger 2.7.0 + Doris 4.0.3 environment)
  • Behavior changed: No
  • Does this need documentation: No

@Thearas
Copy link
Contributor

Thearas commented Mar 26, 2026

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morrySnow
Copy link
Contributor

add test please

@smith1000
Copy link
Author

sure

geshengli added 2 commits March 26, 2026 13:40
…with JOIN

### What problem does this PR solve?

Issue Number: close apache#61631

Problem Summary: When a CTE (WITH ... AS) is referenced multiple times in a
JOIN query and is not inlined (due to inlineCTEReferencedThreshold), the
CheckPrivileges rule only runs once per statement because the privChecked flag
is stored on StatementContext which is shared across all CascadesContext
subtrees. After the outer query's CascadesContext marks privChecked=true,
the CTE producer subtree's CascadesContext skips privilege checking entirely,
allowing users without column-level access to bypass Ranger authorization.

The fix moves the privChecked flag from StatementContext to CascadesContext,
ensuring each subtree (including CTE producer subtrees) performs its own
independent privilege check.

### Release note

Fixed a security issue where Ranger column-level privileges could be bypassed
when using CTE (WITH ... AS) combined with JOIN queries. Users without proper
column access permissions could read restricted columns through CTE+JOIN
patterns.

### Check List (For Author)

- Test: Manual test (verified with Ranger 2.7.0 + Doris 4.0.3 environment)
- Behavior changed: No
- Does this need documentation: No
Add test cases to TestCheckPrivileges to verify that column-level privileges
are properly enforced when CTE is referenced multiple times via JOIN. The CTE
won't be inlined due to inlineCTEReferencedThreshold, exercising the
per-CascadesContext privilege checking path.

Test scenarios:
- CTE + LEFT JOIN on fully-privileged table (should succeed)
- CTE + LEFT JOIN accessing restricted column (should be denied)
- CTE + LEFT JOIN accessing only allowed columns (should succeed)
- CTE + INNER JOIN accessing restricted column (should be denied)
@smith1000 smith1000 force-pushed the fix/cte-privilege-bypass-master branch from 214c8ba to cd94a32 Compare March 26, 2026 05:43
@smith1000
Copy link
Author

@morrySnow Test cases added. Please take a look

@924060929
Copy link
Contributor

924060929 commented Mar 26, 2026

Hi, @smith1000,

It is expected that the entire query statement checks permissions only once, which helps prevent redundant permission checks. In addition, placing the permission flag in the StatementContext is intentional.

Consider this scenario: a user has access to view_a, but does not have access to table_b inside view_a. In this case, two CascadeContexts are generated—one for the scope outside view_a, and one for the scope inside view_a. The expected behavior is that if the user has access to view_a, they can bypass the permission check on table_b and query it through the view.

This requirement implies that the permission information needs to be shared in a common place (i.e., StatementContext).

Therefore, for this issue, the expected behavior is to traverse the outermost query. If a CTE reference is encountered, the traversal should continue into the CTE to perform a complete permission check. Once the entire traversal is finished, the StatementContext should be marked as having completed the permission check.

@924060929
Copy link
Contributor

/review

@github-actions
Copy link
Contributor

Code Review Summary

Verdict: No issues found. The change is correct, minimal, and well-tested.

Change Overview

This PR fixes a security bug (Ranger column-level privilege bypass with CTE+JOIN) by moving the privChecked flag from StatementContext (shared across all CTE subtrees) to CascadesContext (per-subtree). This ensures each CTE subtree gets its own independent privilege check.

Critical Checkpoint Conclusions

  • Goal and correctness: The goal is to fix a privilege bypass when CTE is referenced multiple times via JOIN (preventing CTE inlining). The fix correctly moves privChecked to CascadesContext, since RewriteCteChildren creates a new CascadesContext per CTE subtree (via newSubtreeContext()), each starting with privChecked = false. Tests prove the fix works.

  • Modification minimality: The change touches 4 files with +41/-14 lines. It is focused solely on moving the flag and adding tests. No unnecessary changes.

  • Concurrency: privChecked is a non-volatile boolean on CascadesContext. This is safe because each CascadesContext is created and used within a single thread during the rewrite phase. No concurrent access concerns.

  • View privilege regression risk: The original comment ("Only enter once, if repeated, the permissions of the table in the view will be checked") referred to avoiding re-checking after InlineLogicalView exposes base tables. This is still safe because: (1) CheckPrivileges runs before InlineLogicalView in the same topic block in Rewriter.java:497-507, (2) the per-CascadesContext guard prevents re-execution within the same context, and (3) CheckPrivileges appears only once in the entire job list (CTE_CHILDREN_REWRITE_JOBS_BEFORE_SUB_PATH_PUSH_DOWN).

  • Other callers: isPrivChecked/setPrivChecked are only called from CheckPrivileges.rewriteRoot(). The CreateMaterializedViewCommand.validate() also invokes CheckPrivileges but uses its own fresh CascadesContext, so the fix is compatible.

  • Parallel code paths: No other parallel code paths affected. The change is isolated to the Nereids privilege checking mechanism.

  • Incompatible changes: None. No storage format, function symbol, or protocol changes.

  • Configuration items: None added.

  • Test coverage: Four new CTE+JOIN test cases added covering: (1) fully-privileged CTE+LEFT JOIN succeeds, (2) restricted column CTE+LEFT JOIN denied, (3) allowed-columns-only CTE+LEFT JOIN succeeds, (4) restricted column CTE+INNER JOIN denied. Tests exercise the per-CascadesContext privilege checking path since multi-reference CTEs trigger RewriteCteChildren with separate subtree contexts. Existing view and base table privilege tests continue to validate no regression.

  • Performance: No performance impact. The privChecked flag is a simple boolean check.

  • Other issues: None identified.

@smith1000
Copy link
Author

hi, @924060929 ,
Thank you for the detailed review and explanation. I will submit a new commit later

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] Ranger column-level privilege bypass when CTE (WITH ... AS) is combined with JOIN

4 participants