Files
codeql/python/ql/lib/semmle/python/frameworks/Gradio.qll
yoff 5b9803e03c Python: switch dataflow library to new (shared) CFG + SSA
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 #21894 (kept
around as documentation), rebased on top of the four preparatory PRs:

  P1: Remove AstNode.getAFlowNode() and rewrite callers (#21919).
  P2: Qualify Flow.qll's AST references with Py:: prefix (#21920).
  P3: Add new shared-CFG-backed control flow graph (#21921).
  P4: Add new shared-SSA-backed SSA adapter (#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>
2026-06-05 08:04:01 +00:00

125 lines
4.7 KiB
Plaintext

/**
* Provides classes modeling security-relevant aspects of the `gradio` PyPI package.
* See https://pypi.org/project/gradio/.
*/
import python
private import semmle.python.controlflow.internal.Cfg as Cfg
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.TaintTracking
import semmle.python.ApiGraphs
/**
* Provides models for the `gradio` PyPI package.
* See https://pypi.org/project/gradio/.
*/
module Gradio {
/**
* The event handlers, Interface and gradio.ChatInterface classes, which take untrusted data.
*/
private class GradioInput extends API::CallNode {
GradioInput() {
this =
API::moduleImport("gradio")
.getMember([
"Button", "Textbox", "UploadButton", "Slider", "JSON", "HTML", "Markdown", "File",
"AnnotatedImage", "Audio", "BarPlot", "Chatbot", "Checkbox", "CheckboxGroup",
"ClearButton", "Code", "ColorPicker", "Dataframe", "Dataset", "DownloadButton",
"Dropdown", "DuplicateButton", "FileExplorer", "Gallery", "HighlightedText",
"Image", "ImageEditor", "Label", "LinePlot", "LoginButton", "LogoutButton",
"Model3D", "Number", "ParamViewer", "Plot", "Radio", "ScatterPlot", "SimpleImage",
"State", "Video"
])
.getReturn()
.getMember([
"change", "input", "click", "submit", "edit", "clear", "play", "pause", "stop",
"end", "start_recording", "pause_recording", "stop_recording", "focus", "blur",
"upload", "release", "select", "stream", "like", "load", "key_up",
])
.getACall()
or
this = API::moduleImport("gradio").getMember(["Interface", "ChatInterface"]).getACall()
}
}
/**
* The `inputs` parameters in Gradio event handlers, that are lists and are sources of untrusted data.
* This model allows tracking each element list back to source, f.ex. `gr.Textbox(...)`.
*/
private class GradioInputList extends RemoteFlowSource::Range {
GradioInputList() {
exists(GradioInput call |
// limit only to lists of parameters given to `inputs`.
(
(
call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode
or
call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
) and
(
this = call.getKeywordParameter("inputs").getASubscript().getAValueReachingSink()
or
this = call.getParameter(1).getASubscript().getAValueReachingSink()
)
)
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* The `inputs` parameters in Gradio event handlers, that are not lists and are sources of untrusted data.
*/
private class GradioInputParameter extends RemoteFlowSource::Range {
GradioInputParameter() {
exists(GradioInput call |
this = call.getParameter(0, "fn").getParameter(_).asSource() and
// exclude lists of parameters given to `inputs`
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode and
not call.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* The `inputs` parameters in Gradio decorators to event handlers, that are sources of untrusted data.
*/
private class GradioInputDecorator extends RemoteFlowSource::Range {
GradioInputDecorator() {
exists(GradioInput call |
this = call.getReturn().getACall().getParameter(0).getParameter(_).asSource()
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* Extra taint propagation for tracking `inputs` parameters in Gradio event handlers, that are lists.
*/
private class ListTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(GradioInput node |
// handle cases where there are multiple arguments passed as a list to `inputs`
(
(
node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof Cfg::ListNode
or
node.getParameter(1).asSink().asCfgNode() instanceof Cfg::ListNode
) and
exists(int i | nodeTo = node.getParameter(0, "fn").getParameter(i).asSource() |
nodeFrom.asCfgNode() =
node.getKeywordParameter("inputs").asSink().asCfgNode().(Cfg::ListNode).getElement(i)
or
nodeFrom.asCfgNode() =
node.getParameter(1).asSink().asCfgNode().(Cfg::ListNode).getElement(i)
)
)
)
}
}
}