Skip to content

Commit bed0d56

Browse files
Add Gradio models
1 parent c378d6a commit bed0d56

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed

docs/codeql/reusables/supported-frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
202202
Flask-Admin, Web framework
203203
Tornado, Web framework
204204
Twisted, Web framework
205+
Gradio, Web framework
205206
starlette, Asynchronous Server Gateway Interface (ASGI)
206207
ldap3, Lightweight Directory Access Protocol (LDAP)
207208
python-ldap, Lightweight Directory Access Protocol (LDAP)

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ private import semmle.python.frameworks.FastApi
2929
private import semmle.python.frameworks.Flask
3030
private import semmle.python.frameworks.FlaskAdmin
3131
private import semmle.python.frameworks.FlaskSqlAlchemy
32+
private import semmle.python.frameworks.Gradio
3233
private import semmle.python.frameworks.Httpx
3334
private import semmle.python.frameworks.Idna
3435
private import semmle.python.frameworks.Invoke
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `gradio` PyPI package.
3+
* See https://pypi.org/project/gradio/.
4+
*/
5+
6+
import python
7+
import semmle.python.dataflow.new.RemoteFlowSources
8+
import semmle.python.dataflow.new.TaintTracking
9+
import semmle.python.ApiGraphs
10+
11+
12+
module Gradio {
13+
/**
14+
* Event handlers in Gradio, which are sources of untrusted data.
15+
*/
16+
17+
class GradioInput extends API::CallNode {
18+
GradioInput() { this = API::moduleImport("gradio")
19+
.getMember([
20+
"Button", "Textbox", "UploadButton", "Slider",
21+
"JSON", "HTML", "Markdown", "File",
22+
"AnnotatedImage", "Audio", "BarPlot", "Chatbot", "Checkbox",
23+
"CheckboxGroup", "ClearButton", "Code", "ColorPicker", "Dataframe",
24+
"Dataset", "DownloadButton", "Dropdown", "DuplicateButton", "FileExplorer",
25+
"Gallery", "HighlightedText", "Image", "ImageEditor", "Label", "LinePlot",
26+
"LoginButton", "LogoutButton", "Model3D", "Number", "ParamViewer",
27+
"Plot", "Radio", "ScatterPlot", "SimpleImage", "State", "Video"])
28+
.getReturn()
29+
.getMember([
30+
"change", "input", "click", "submit",
31+
"edit", "clear", "play", "pause", "stop", "end", "start_recording",
32+
"pause_recording", "stop_recording", "focus", "blur",
33+
"upload", "release", "select", "stream", "like", "load",
34+
"like", "key_up",])
35+
.getACall()
36+
}
37+
38+
}
39+
40+
class GradioInterface extends API::CallNode {
41+
GradioInterface() { this = API::moduleImport("gradio").getMember(["Interface", "ChatInterface"]).getACall() }
42+
}
43+
44+
/**
45+
* Track `inputs` parameters in Gradio event handlers, that are lists, back to source, f.ex. `gr.Textbox(...)`. Handle keyword and positional parameters.
46+
*/
47+
class GradioInputList extends RemoteFlowSource::Range {
48+
GradioInputList() {
49+
exists(API::CallNode call |
50+
(call instanceof GradioInput
51+
or
52+
call instanceof GradioInterface)
53+
and
54+
// limit only to lists of parameters given to `inputs`.
55+
((call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
56+
or
57+
call.getParameter(1).asSink().asCfgNode() instanceof ListNode)
58+
and
59+
(this = call.getKeywordParameter("inputs").getASuccessor().getAValueReachingSink()
60+
or
61+
this = call.getParameter(1).getASuccessor().getAValueReachingSink()))
62+
)
63+
}
64+
override string getSourceType() { result = "Gradio untrusted input" }
65+
66+
67+
}
68+
69+
/**
70+
* Track `inputs` parameters in Gradio event handlers, that are not lists. Handle keyword and positional parameters.
71+
*/
72+
class GradioInputParameter extends RemoteFlowSource::Range {
73+
GradioInputParameter() {
74+
exists(API::CallNode call |
75+
(call instanceof GradioInput
76+
or
77+
call instanceof GradioInterface)
78+
79+
and
80+
(this = call.getKeywordParameter("fn").getParameter(_).asSource()
81+
or
82+
this = call.getParameter(0).getParameter(_).asSource())
83+
84+
and
85+
// exclude lists of parameters given to `inputs`
86+
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
87+
and
88+
not call.getParameter(1).asSink().asCfgNode() instanceof ListNode
89+
)
90+
}
91+
override string getSourceType() { result = "Gradio untrusted input" }
92+
}
93+
94+
/**
95+
* Track `inputs` parameters in Gradio decorators to event handlers.
96+
*/
97+
class GradioInputDecorator extends RemoteFlowSource::Range {
98+
GradioInputDecorator() {
99+
exists(API::CallNode call |
100+
(call instanceof GradioInput or call instanceof GradioInterface)
101+
and
102+
this = call.getReturn().getACall().getParameter(0).getParameter(_).asSource()
103+
)
104+
}
105+
override string getSourceType() { result = "Gradio untrusted input" }
106+
}
107+
108+
/**
109+
* Extra taint propagation for tracking `inputs` parameters in Gradio event handlers, that are lists.
110+
*/
111+
private class ListTaintStep extends TaintTracking::AdditionalTaintStep {
112+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
113+
exists(API::CallNode node |
114+
(node instanceof GradioInput
115+
or
116+
node instanceof GradioInterface)
117+
and
118+
// handle cases where there are multiple arguments passed as a list to `inputs`
119+
((
120+
(node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
121+
or
122+
node.getParameter(1).asSink().asCfgNode() instanceof ListNode)
123+
and
124+
exists(int i |
125+
(nodeTo = node.getParameter(0).getParameter(i).asSource()
126+
or
127+
nodeTo = node.getKeywordParameter("fn").getParameter(i).asSource())
128+
and
129+
(nodeFrom.asCfgNode() = node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i)
130+
or
131+
nodeFrom.asCfgNode() = node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i))))
132+
)
133+
)
134+
}
135+
}
136+
137+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import gradio as gr
2+
3+
4+
with gr.Blocks() as demo:
5+
name = gr.Textbox(label="Name")
6+
output = gr.Textbox(label="Output Box")
7+
greet_btn = gr.Button("Hello")
8+
9+
# decorator
10+
@greet_btn.click(inputs=name, outputs=output)
11+
def greet(name):
12+
return "Hello " + name + "!"
13+
14+
# `click` event handler with keyword arguments
15+
def greet1(name):
16+
return "Hello " + name + "!"
17+
18+
greet1_btn = gr.Button("Hello")
19+
greet1_btn.click(fn=greet1, inputs=name, outputs=output, api_name="greet")
20+
21+
# `click` event handler with positional arguments
22+
def greet2(name):
23+
return "Hello " + name + "!"
24+
25+
greet2_btn = gr.Button("Hello")
26+
greet2_btn.click(fn=greet2, inputs=name, outputs=output, api_name="greet")
27+
28+
29+
demo.launch()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import gradio as gr
2+
import os
3+
4+
with gr.Blocks() as demo:
5+
path = gr.Textbox(label="Path")
6+
file = gr.Textbox(label="File")
7+
output = gr.Textbox(label="Output Box")
8+
9+
10+
# path injection sink
11+
def fileread(path, file):
12+
filepath = os.path.join(path, file)
13+
with open(filepath, "r") as f:
14+
return f.read()
15+
16+
17+
# `click` event handler with `inputs` containing a list
18+
greet1_btn = gr.Button("Path for the file to display")
19+
greet1_btn.click(fn=fileread, inputs=[path,file], outputs=output, api_name="fileread")
20+
21+
22+
demo.launch()

0 commit comments

Comments
 (0)