Merge pull request #20914 from joefarebrother/python-socketio

Python: Add models for socketio
This commit is contained in:
Joe Farebrother
2025-12-04 23:14:58 +00:00
committed by GitHub
9 changed files with 229 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Remote flow sources for the `python-socketio` package have been modeled.

View File

@@ -78,6 +78,7 @@ private import semmle.python.frameworks.Sanic
private import semmle.python.frameworks.ServerLess private import semmle.python.frameworks.ServerLess
private import semmle.python.frameworks.Setuptools private import semmle.python.frameworks.Setuptools
private import semmle.python.frameworks.Simplejson private import semmle.python.frameworks.Simplejson
private import semmle.python.frameworks.Socketio
private import semmle.python.frameworks.SqlAlchemy private import semmle.python.frameworks.SqlAlchemy
private import semmle.python.frameworks.Starlette private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.Stdlib private import semmle.python.frameworks.Stdlib

View File

@@ -0,0 +1,119 @@
/**
* Provides definitions and modeling for the `python-socketio` PyPI package.
* See https://python-socketio.readthedocs.io/en/stable/.
*/
private import python
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
/**
* Provides models for the `python-socketio` PyPI package.
* See https://python-socketio.readthedocs.io/en/stable/.
*/
module SocketIO {
/** Provides models for socketio `Server` and `AsyncServer` classes. */
module Server {
/** Gets an instance of a socketio `Server` or `AsyncServer`. */
API::Node server() {
result = API::moduleImport("socketio").getMember(["Server", "AsyncServer"]).getAnInstance()
}
/** Gets a decorator that indicates a socketio event handler. */
private API::Node serverEventAnnotation() {
result = server().getMember("event")
or
result = server().getMember("on").getReturn()
}
private class EventHandler extends Http::Server::RequestHandler::Range {
EventHandler() {
serverEventAnnotation().getAValueReachableFromSource().asExpr() = this.getADecorator()
or
exists(DataFlow::CallCfgNode c, DataFlow::Node arg |
c = server().getMember("on").getACall()
|
(
arg = c.getArg(1)
or
arg = c.getArgByName("handler")
) and
poorMansFunctionTracker(this) = arg
)
}
override Parameter getARoutedParameter() {
result = this.getAnArg() and
not result = this.getArg(0) // First parameter is `sid`, which is not a remote flow source as it cannot be controlled by the client.
}
override string getFramework() { result = "socketio" }
}
private class CallbackArgument extends DataFlow::Node {
CallbackArgument() {
exists(DataFlow::CallCfgNode c |
c = [server(), Namespace::instance()].getMember(["emit", "send"]).getACall()
|
this = c.getArgByName("callback")
)
}
}
private class CallbackHandler extends Http::Server::RequestHandler::Range {
CallbackHandler() { any(CallbackArgument ca) = poorMansFunctionTracker(this) }
override Parameter getARoutedParameter() { result = this.getAnArg() }
override string getFramework() { result = "socketio" }
}
private class SocketIOCall extends RemoteFlowSource::Range {
SocketIOCall() { this = [server(), Namespace::instance()].getMember("call").getACall() }
override string getSourceType() { result = "socketio call" }
}
}
/** Provides modeling for socketio server Namespace/AsyncNamespace classes. */
module Namespace {
/** Gets a reference to the `socketio.Namespace` or `socketio.AsyncNamespace` classes or any subclass. */
API::Node subclassRef() {
result =
API::moduleImport("socketio").getMember(["Namespace", "AsyncNamespace"]).getASubclass*()
}
/** Gets a reference to an instance of a subclass of `socketio.Namespace` or `socketio.AsyncNamespace`. */
API::Node instance() {
result = subclassRef().getAnInstance()
or
result = subclassRef().getAMember().getSelfParameter()
}
/** A socketio Namespace class. */
class NamespaceClass extends Class {
NamespaceClass() { this.getABase() = subclassRef().asSource().asExpr() }
/** Gets a handler for socketio events. */
Function getAnEventHandler() {
result = this.getAMethod() and
result.getName().matches("on_%")
}
}
private class NamespaceEventHandler extends Http::Server::RequestHandler::Range {
NamespaceEventHandler() { this = any(NamespaceClass nc).getAnEventHandler() }
override Parameter getARoutedParameter() {
result = this.getAnArg() and
not result = this.getArg(0) and
not result = this.getArg(1) // First 2 parameters are `self` and `sid`.
}
override string getFramework() { result = "socketio" }
}
}
}

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
testFailures

View File

@@ -0,0 +1,2 @@
import experimental.meta.InlineTaintTest
import MakeInlineTaintTest<TestTaintTrackingConfig>

View File

@@ -0,0 +1,69 @@
import sys
import socketio
def ensure_tainted(*args):
print("tainted", args)
def ensure_not_tainted(*args):
print("not tainted", args)
sio = socketio.Server()
@sio.event
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
ensure_not_tainted(sid)
ensure_tainted(environ, # $ tainted
auth) # $ tainted
@sio.event
def event1(sid, data): # $ requestHandler routedParameter=data
ensure_not_tainted(sid)
ensure_tainted(data) # $ tainted
res = sio.call("e1", sid=sid)
ensure_tainted(res) # $ tainted
sio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
class MyNamespace(socketio.Namespace):
def on_event2(self, sid, data): # $ requestHandler routedParameter=data
ensure_not_tainted(self, sid)
ensure_tainted(data) # $ tainted
res = self.call("e1", sid=sid)
ensure_tainted(res) # $ tainted
self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
sio.register_namespace(MyNamespace("/ns"))
asio = socketio.AsyncServer(async_mode='asgi')
@asio.event
async def event3(sid, data): # $ requestHandler routedParameter=data
ensure_not_tainted(sid)
ensure_tainted(data) # $ tainted
res = await asio.call("e1", sid=sid)
ensure_tainted(res) # $ tainted
await asio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
class MyAsyncNamespace(socketio.AsyncNamespace):
async def on_event4(self, sid, data): # $ requestHandler routedParameter=data
ensure_not_tainted(self, sid)
ensure_tainted(data) # $ tainted
res = await self.call("e1", sid=sid)
ensure_tainted(res) # $ tainted
await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
await self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
asio.register_namespace(MyAsyncNamespace("/ns"))
if __name__ == "__main__":
if "--async" in sys.argv: # $ threatModelSource[commandargs]=sys.argv
import uvicorn
app = socketio.ASGIApp(asio)
uvicorn.run(app, host='127.0.0.1', port=8000)
else:
import eventlet
app = socketio.WSGIApp(sio)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

View File

@@ -0,0 +1,29 @@
import socketio
sio = socketio.Server()
@sio.on("connect")
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
print("connect", sid, environ, auth)
@sio.on("event1")
def handle(sid, data): # $ requestHandler routedParameter=data
print("e1", sid, data)
@sio.event
def event2(sid, data): # $ requestHandler routedParameter=data
print("e2", sid, data)
def event3(sid, data): # $ requestHandler routedParameter=data
print("e3", sid, data)
sio.on("event3", handler=event3)
sio.on("event4", lambda sid,data: print("e4", sid, data)) # $ requestHandler routedParameter=data
if __name__ == "__main__":
app = socketio.WSGIApp(sio)
import eventlet
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)