Add Gradio models

This commit is contained in:
Sylwia Budzynska
2024-04-05 14:14:21 +02:00
parent c378d6a661
commit bed0d5678d
5 changed files with 190 additions and 0 deletions

View File

@@ -29,6 +29,7 @@ private import semmle.python.frameworks.FastApi
private import semmle.python.frameworks.Flask
private import semmle.python.frameworks.FlaskAdmin
private import semmle.python.frameworks.FlaskSqlAlchemy
private import semmle.python.frameworks.Gradio
private import semmle.python.frameworks.Httpx
private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke

View File

@@ -0,0 +1,137 @@
/**
* Provides classes modeling security-relevant aspects of the `gradio` PyPI package.
* See https://pypi.org/project/gradio/.
*/
import python
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.TaintTracking
import semmle.python.ApiGraphs
module Gradio {
/**
* Event handlers in Gradio, which are sources of untrusted data.
*/
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",
"like", "key_up",])
.getACall()
}
}
class GradioInterface extends API::CallNode {
GradioInterface() { this = API::moduleImport("gradio").getMember(["Interface", "ChatInterface"]).getACall() }
}
/**
* Track `inputs` parameters in Gradio event handlers, that are lists, back to source, f.ex. `gr.Textbox(...)`. Handle keyword and positional parameters.
*/
class GradioInputList extends RemoteFlowSource::Range {
GradioInputList() {
exists(API::CallNode call |
(call instanceof GradioInput
or
call instanceof GradioInterface)
and
// limit only to lists of parameters given to `inputs`.
((call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
or
call.getParameter(1).asSink().asCfgNode() instanceof ListNode)
and
(this = call.getKeywordParameter("inputs").getASuccessor().getAValueReachingSink()
or
this = call.getParameter(1).getASuccessor().getAValueReachingSink()))
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* Track `inputs` parameters in Gradio event handlers, that are not lists. Handle keyword and positional parameters.
*/
class GradioInputParameter extends RemoteFlowSource::Range {
GradioInputParameter() {
exists(API::CallNode call |
(call instanceof GradioInput
or
call instanceof GradioInterface)
and
(this = call.getKeywordParameter("fn").getParameter(_).asSource()
or
this = call.getParameter(0).getParameter(_).asSource())
and
// exclude lists of parameters given to `inputs`
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
and
not call.getParameter(1).asSink().asCfgNode() instanceof ListNode
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* Track `inputs` parameters in Gradio decorators to event handlers.
*/
class GradioInputDecorator extends RemoteFlowSource::Range {
GradioInputDecorator() {
exists(API::CallNode call |
(call instanceof GradioInput or call instanceof GradioInterface)
and
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(API::CallNode node |
(node instanceof GradioInput
or
node instanceof GradioInterface)
and
// handle cases where there are multiple arguments passed as a list to `inputs`
((
(node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
or
node.getParameter(1).asSink().asCfgNode() instanceof ListNode)
and
exists(int i |
(nodeTo = node.getParameter(0).getParameter(i).asSource()
or
nodeTo = node.getKeywordParameter("fn").getParameter(i).asSource())
and
(nodeFrom.asCfgNode() = node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i)
or
nodeFrom.asCfgNode() = node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i))))
)
)
}
}
}

View File

@@ -0,0 +1,29 @@
import gradio as gr
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Hello")
# decorator
@greet_btn.click(inputs=name, outputs=output)
def greet(name):
return "Hello " + name + "!"
# `click` event handler with keyword arguments
def greet1(name):
return "Hello " + name + "!"
greet1_btn = gr.Button("Hello")
greet1_btn.click(fn=greet1, inputs=name, outputs=output, api_name="greet")
# `click` event handler with positional arguments
def greet2(name):
return "Hello " + name + "!"
greet2_btn = gr.Button("Hello")
greet2_btn.click(fn=greet2, inputs=name, outputs=output, api_name="greet")
demo.launch()

View File

@@ -0,0 +1,22 @@
import gradio as gr
import os
with gr.Blocks() as demo:
path = gr.Textbox(label="Path")
file = gr.Textbox(label="File")
output = gr.Textbox(label="Output Box")
# path injection sink
def fileread(path, file):
filepath = os.path.join(path, file)
with open(filepath, "r") as f:
return f.read()
# `click` event handler with `inputs` containing a list
greet1_btn = gr.Button("Path for the file to display")
greet1_btn.click(fn=fileread, inputs=[path,file], outputs=output, api_name="fileread")
demo.launch()