-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Python: Add models for socketio #20914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ba06990
a83c70f
b0be818
83eadba
eb7fe71
6207137
8d313ff
16018e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * Remote flow sources for the `python-socketio` package have been modeled. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import python | ||
| import experimental.meta.ConceptsTest |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| argumentToEnsureNotTaintedNotMarkedAsSpurious | ||
| untaintedArgumentToEnsureTaintedNotMarkedAsMissing | ||
| testFailures |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import experimental.meta.InlineTaintTest | ||
| import MakeInlineTaintTest<TestTaintTrackingConfig> |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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 | ||||||
|
||||||
| sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| sio.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| self.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await asio.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await self.emit("e2", "hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await self.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.