Skip to content

SSA: Tolerate multiple variables being read at the same CFG node - #22744

Open
asgerf wants to merge 13 commits into
github:mainfrom
asgerf:ssa/multiple-reads-v2
Open

asgerf wants to merge 13 commits into
github:mainfrom
asgerf:ssa/multiple-reads-v2

Conversation

@asgerf

@asgerf asgerf commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes an issue with the SSA data-flow integration library that occurs when multiple variables are read at the same CFG node.

This situation happens when it is instantiated from the VariableCapture library. It also happens in unified due to how we use synthetic reads to mark post-update positions.

The assumption has a subtle presence in this bit of code:

    private class ReadNodeImpl extends ExprNodeImpl {
      private BasicBlock bb_;
      private int i_;

      ReadNodeImpl() {
        variableRead(bb_, i_, v_, true) and
        this.getExpr().hasCfgNode(bb_, i_)
      }

Since this is only bound to the expression, the variableRead join can fan out to multiple variables when not uniquely determined by the CFG node. Even if Expr is constructed to be unique to the variable, the correlation is forgotten in this join and they get mixed up anyway. The fix was to make TExprNode unique to a specific variable.

The real culprit is the Expr class from the data-flow integration input. I propose we remove Expr entirely and replace ExprNode with ReadNode: a canonical representative for a (bb,i,v) triple from the variableRead input, and likewise for PostUpdateNode. But such a change is too large for this PR as it requires language-specific refactorings. But the fix is essentially a precursor to this solution: when Expr is a CFG node, ReadNode is effectively a (bb,i,v) triple as it rightfully should be.

Comment on lines +2150 to +2152
/**
* The given `read` reads multiple variables at once. `var` is bound to one of them.
*/
asgerf added 5 commits October 2, 2026 16:22
These consistency violations originate from the VariableCapture instantation
in C#, JS, Python, and Ruby.
The previously-added consistency errors are gone.
Unified also had consistency errors from its LocalSSA instantiation, due
to its use of synthetic read nodes to represent post-update positions.
Many variables can have a post-update at the same CFG node.
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from bfd363b to 1bbd5d5 Compare October 2, 2026 14:23
asgerf added 3 commits October 2, 2026 16:47
Many tests passed for the wrong reasons, due to the SSA bug.

We need more library/operator modelling to actually find these flows.
Switched to TaintTracking and adds some very ad-hoc steps to recover
most of the results.

Some more tests pass and others fail; these are now consistent with what
we actually model.
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch 2 times, most recently from 1bbd5d5 to 2c637ab Compare October 2, 2026 14:55
@asgerf asgerf changed the title Ssa/multiple reads v2 SSA: Tolerate multiple variables being read at the same CFG node Oct 2, 2026
The C++ instantiation of DataFlowIntegration generated a bad join order
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from 4048c51 to cbd6d35 Compare October 5, 2026 07:39
@asgerf
asgerf requested a balanced review from Copilot October 5, 2026 12:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Variable correlation remains incomplete in post-update, must-flow, and barrier joins, and the new Swift URL rule overmatches user-defined calls.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

This PR makes SSA data-flow nodes variable-specific when multiple variables share one CFG node and adds consistency coverage.

Changes:

  • Adds variable identity to SSA expression nodes and ambiguity checks.
  • Adds Swift regression tests and updates path-injection taint flow.
  • Enables the new consistency check across supported languages.
File Description
shared/​ssa/​codeql/​ssa/​Ssa.qll Qualifies expression nodes by variable and adds consistency checks.
shared/​dataflow/​codeql/​dataflow/​VariableCapture.qll Exposes SSA ambiguity checks for variable capture.
unified/​ql/​consistency-queries/​LocalSsaConsistency.ql Enables the new unified consistency check.
unified/​ql/​lib/​codeql/​unified/​internal/​dataflow/​DataFlowPluginSwift.qll Adds Swift-specific taint steps.
unified/​ql/​src/​queries/​security/​CWE-022/​PathInjection.ql Switches path injection to taint tracking.
unified/​ql/​test/​library-tests/​dataflow/​test.swift Adds a shared-CFG-node regression case.
unified/​ql/​test/​library-tests/​dataflow/​test.expected Updates generated data-flow expectations.
unified/​ql/​test/​query-tests/​security/​CWE-022/​PathInjection/​testPathInjection.swift Updates path-injection annotations.
unified/​ql/​test/​query-tests/​security/​CWE-022/​PathInjection/​PathInjectionTest.expected Updates generated path-injection results.
rust/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Rust.
ruby/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Ruby.
java/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for Java.
csharp/​ql/​consistency-queries/​SsaConsistency.ql Enables SSA ambiguity checking for C#.
python/​ql/​test/​library-tests/​dataflow/​variable-capture/​dataflow-capture-consistency.expected Updates generated consistency expectations.
javascript/​ql/​test/​library-tests/​FlowSummary/​CaptureConsistency.expected Updates generated consistency expectations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1683 to +1687
TExprNode(DfInput::Expr e, SourceVariable v, Boolean isPost) {
exists(Definition def |
def.getSourceVariable() = v and
e = DfInput::getARead(def)
)
Comment on lines +38 to +42
exists(CallExpr call |
call.getCallee().(Identifier).getValue() = ["URL", "NSURL"] and
node1.isResultValue(call.getNamedArgument("string")) and
step.taint() and
node2.isResultValue(call)
@asgerf
asgerf marked this pull request as ready for review October 5, 2026 12:52
@asgerf
asgerf requested review from a team as code owners October 5, 2026 12:52
@asgerf
asgerf requested a review from aschackmull October 5, 2026 12:52
@asgerf asgerf added the no-change-note-required This PR does not need a change note label Oct 5, 2026
@asgerf
asgerf force-pushed the ssa/multiple-reads-v2 branch from fab449a to 32e5525 Compare October 5, 2026 13:02
/** Gets the underlying expression. */
DfInput::Expr getExpr() { result = e }

/** Holds if represents the access to `var` performed at `expr`. */

@hvitved hvitved Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

grammar

@aschackmull aschackmull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! Thanks for fixing this.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C# DataFlow Library Java JS no-change-note-required This PR does not need a change note Python Ruby Rust Pull requests that update Rust code Unified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants