Skip to content

Python: test closure instance cross-talk - #22395

Draft
yoff wants to merge 4 commits into
github:yoff/python-shared-cfg-dataflow-flipfrom
yoff:yoff-python-closure-identity
Draft

yoff wants to merge 4 commits into
github:yoff/python-shared-cfg-dataflow-flipfrom
yoff:yoff-python-closure-identity

Conversation

@yoff

@yoff yoff commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add an inline type-tracking regression for two closures created from one wrapper AST with different captured callables
  • keep positive controls for the sensitive callable and ordinary captured data
  • mark the safe closure result as SPURIOUS so this tests-only draft remains green

Motivation

This is an experimental dependent follow-up to #21925. The improved shared-CFG/SSA routing exposes cross-talk in generic decorator factories such as Django keep_lazy and Airflow provide_session / action_cli: separate wrapper instances can capture different callables, but callable type tracking currently merges their returns.

Current finding

TypeTrackingImpl::capturedJumpStep transfers the merged outer value into the wrapper scope through a shared type-tracking JumpStep, which intentionally discards call context. Removing that jump eliminates the false positive but also drops legitimate captured callable and data flow. A sound implementation therefore appears to require call-site-specific closure/function-object identity in shared type tracking or dataflow; this draft intentionally does not include a workaround or framework-specific suppression.

Testing

codeql test run python/ql/test/library-tests/dataflow/variable-capture python/ql/test/library-tests/dataflow/typetracking

All four targeted tests pass with the inline SPURIOUS expectations.

yoff and others added 4 commits August 20, 2026 10:32
Flips the Python dataflow trunk from the legacy CFG (semmle/python/Flow.qll)
and legacy ESSA SSA (semmle/python/essa/*) to the new shared CFG facade
(semmle.python.controlflow.internal.Cfg) and the new SSA adapter
(semmle.python.dataflow.new.internal.SsaImpl), both introduced
additively in the preceding PRs in this stack.

This is the trunk-flip equivalent of the original draft PR github#21894 (kept
around as documentation), rebased on top of the four preparatory PRs:

  P1: Remove AstNode.getAFlowNode() and rewrite callers (github#21919).
  P2: Qualify Flow.qll's AST references with Py:: prefix (github#21920).
  P3: Add new shared-CFG-backed control flow graph (github#21921).
  P4: Add new shared-SSA-backed SSA adapter (github#21923).

The Python dataflow library (semmle/python/dataflow/new/) now imports
the new CFG facade and SSA adapter. All CFG-typed predicates
(ControlFlowNode, CallNode, BasicBlock, NameNode, AttrNode, ...) are
qualified with the Cfg:: prefix; SSA references switch from
EssaVariable/EssaDefinition to SsaImpl::Definition/SourceVariable.

GuardNode is redesigned to use the new CFG's outcome-node model
(isAfterTrue / isAfterFalse) instead of the legacy ConditionBlock +
flipped indirection. Only BarrierGuard<...> is preserved as public
API.

Framework files (Bottle, FastApi, Django, Tornado, Pyramid, Stdlib,
...) are updated to take CFG nodes from the new facade.

A handful of dataflow consistency tweaks for the new CFG:
- Augmented-assignment targets are treated as both load and store.
- 'from X import *' produces uncertain SSA writes for unknown names.
- CFG nodes are canonicalised so dataflow does not see equivalent
  pre/post-order pairs as distinct nodes.

Two AST tweaks for the new CFG:
- AstNodeImpl: omit PEP 695 type-parameter names from
  FunctionDefExpr / ClassDefExpr children.
- ImportResolution: drop the legacy essa import.

Test churn (~175 files): reblessed library- and query-test .expected
files reflect slightly different CFG granularity, different toString
output, and a handful of true alert deltas in security queries.

Verification: all 367 lib + src + consistency-queries compile clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The `Cfg::ControlFlowNode` facade re-exports the shared CFG library's
`dominates`/`strictlyDominates` predicates, which are declared
`bindingset[this, that]` + `pragma[inline_late]` and are meant to be used
as bound-pair membership checks. The facade wrappers dropped these
annotations (using plain `pragma[inline]`), so even though the only
callers — the `with` / `async with` taint steps in DataFlowPrivate.qll
and TaintTrackingPrivate.qll — bind both endpoints, the optimizer was
free to materialise `Cfg::ControlFlowNode.strictlyDominates/1` as a full
O(nodes^2) relation over the (larger) shared-CFG node set.

On some projects this dominated analysis time entirely (DCA showed e.g.
ICTU/quality-time and biosimulations regressing ~75-160x). Restoring
`bindingset[this, other]` + `pragma[inline_late]` on the wrappers turns
the predicate back into a bound-pair check and is result-preserving (only
binding annotations change, the predicate body is unchanged).

Reproduced on ICTU/quality-time: full python-security-extended suite went
from stalling >20min on `strictlyDominates` to completing in ~6min; all
ControlFlow and dataflow/coverage library tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the public expression adapter and apply the canonical QL annotation ordering required by the formatter.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 529363f5-bc7d-4f0b-9f47-e03ba9aa0cdf
Add an inline type-tracking regression for two closures created from the same wrapper AST with different captured callables. Keep positive controls for the sensitive callable and ordinary captured data while marking the safe closure result as the current spurious flow.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@yoff
yoff force-pushed the yoff/python-shared-cfg-dataflow-flip branch from f2a6889 to f7774ef Compare September 22, 2026 11:38

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant