Skip to content

Commit 83eadba

Browse files
Add namespace models
1 parent b0be818 commit 83eadba

File tree

3 files changed

+103
-48
lines changed

3 files changed

+103
-48
lines changed

python/ql/lib/semmle/python/frameworks/Socketio.qll

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,101 @@ private import semmle.python.frameworks.internal.PoorMansFunctionResolution
1616
* See https://python-socketio.readthedocs.io/en/stable/.
1717
*/
1818
module SocketIO {
19-
/** An instance of a socketio `Server` or `AsyncServer`. */
20-
API::Node server() {
21-
result = API::moduleImport("socketio").getMember(["Server", "AsyncServer"]).getAnInstance()
22-
}
23-
24-
API::Node serverEventAnnotation() {
25-
result = server().getMember("event")
26-
or
27-
result = server().getMember("on").getReturn()
28-
}
19+
/** Provides models for socketio `Server` and `AsyncServer` classes. */
20+
module Server {
21+
/** An instance of a socketio `Server` or `AsyncServer`. */
22+
API::Node server() {
23+
result = API::moduleImport("socketio").getMember(["Server", "AsyncServer"]).getAnInstance()
24+
}
2925

30-
private class EventHandler extends Http::Server::RequestHandler::Range {
31-
EventHandler() {
32-
serverEventAnnotation().getAValueReachableFromSource().asExpr() = this.getADecorator()
26+
/** A decorator that indicates a socketio event handler. */
27+
private API::Node serverEventAnnotation() {
28+
result = server().getMember("event")
3329
or
34-
exists(DataFlow::CallCfgNode c, DataFlow::Node arg | c = server().getMember("on").getACall() |
35-
(
36-
arg = c.getArg(1)
37-
or
38-
arg = c.getArgByName("handler")
39-
) and
40-
poorMansFunctionTracker(this) = arg
41-
)
30+
result = server().getMember("on").getReturn()
31+
}
32+
33+
private class EventHandler extends Http::Server::RequestHandler::Range {
34+
EventHandler() {
35+
serverEventAnnotation().getAValueReachableFromSource().asExpr() = this.getADecorator()
36+
or
37+
exists(DataFlow::CallCfgNode c, DataFlow::Node arg |
38+
c = server().getMember("on").getACall()
39+
|
40+
(
41+
arg = c.getArg(1)
42+
or
43+
arg = c.getArgByName("handler")
44+
) and
45+
poorMansFunctionTracker(this) = arg
46+
)
47+
}
48+
49+
override Parameter getARoutedParameter() {
50+
result = this.getAnArg() and
51+
not result = this.getArg(0) // First parameter is `sid`, which is not a remote flow source as it cannot be controlled by the client.
52+
}
53+
54+
override string getFramework() { result = "socketio" }
4255
}
4356

44-
override Parameter getARoutedParameter() {
45-
result = this.getAnArg() and not result = this.getArg(0)
57+
private class CallbackArgument extends DataFlow::Node {
58+
CallbackArgument() {
59+
exists(DataFlow::CallCfgNode c |
60+
c = [server(), Namespace::instance()].getMember(["emit", "send"]).getACall()
61+
|
62+
this = c.getArgByName("callback")
63+
)
64+
}
4665
}
4766

48-
override string getFramework() { result = "socketio" }
49-
}
67+
private class CallbackHandler extends Http::Server::RequestHandler::Range {
68+
CallbackHandler() { any(CallbackArgument ca) = poorMansFunctionTracker(this) }
69+
70+
override Parameter getARoutedParameter() { result = this.getAnArg() }
5071

51-
private class CallbackArgument extends DataFlow::Node {
52-
CallbackArgument() {
53-
exists(DataFlow::CallCfgNode c | c = server().getMember(["emit", "send"]).getACall() |
54-
this = c.getArgByName("callback")
55-
)
72+
override string getFramework() { result = "socketio" }
73+
}
74+
75+
private class SocketIOCall extends RemoteFlowSource::Range {
76+
SocketIOCall() { this = [server(), Namespace::instance()].getMember("call").getACall() }
77+
78+
override string getSourceType() { result = "socketio call" }
5679
}
5780
}
5881

59-
private class CallbackHandler extends Http::Server::RequestHandler::Range {
60-
CallbackHandler() { any(CallbackArgument ca) = poorMansFunctionTracker(this) }
82+
/** Provides modelling for socketio server Namespace/AsyncNamespace classes. */
83+
module Namespace {
84+
/** Gets a reference to the `socketio.Namespace` or `socketio.AsyncNamespace` classes or any subclass. */
85+
API::Node subclassRef() {
86+
result =
87+
API::moduleImport("socketio").getMember(["Namespace", "AsyncNamespace"]).getASubclass*()
88+
}
6189

62-
override Parameter getARoutedParameter() { result = this.getAnArg() }
90+
/** Gets a reference to an instance of a subclass of `socketio.Namespace` or `socketio.AsyncNamespace`. */
91+
API::Node instance() { result = subclassRef().getAnInstance() }
6392

64-
override string getFramework() { result = "socketio" }
65-
}
93+
/** A socketio Namespace class. */
94+
class NamespaceClass extends Class {
95+
NamespaceClass() { this.getABase() = subclassRef().asSource().asExpr() }
6696

67-
private class SocketIOCall extends RemoteFlowSource::Range {
68-
SocketIOCall() { this = server().getMember("call").getACall() }
97+
/** Gets a handler for socketio events. */
98+
Function getAnEventHandler() {
99+
result = this.getAMethod() and
100+
result.getName().matches("on_%")
101+
}
102+
}
103+
104+
private class NamespaceEventHandler extends Http::Server::RequestHandler::Range {
105+
NamespaceEventHandler() { this = any(NamespaceClass nc).getAnEventHandler() }
69106

70-
override string getSourceType() { result = "socketio call" }
107+
override Parameter getARoutedParameter() {
108+
result = this.getAnArg() and
109+
not result = this.getArg(0) and
110+
not result = this.getArg(1) // First 2 parameters are `self` and `sid`.
111+
}
112+
113+
override string getFramework() { result = "socketio" }
114+
}
71115
}
72116
}

python/ql/test/library-tests/frameworks/socketio/taint_test.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,40 @@ def ensure_not_tainted(*args):
1111
sio = socketio.Server()
1212

1313
@sio.event
14-
def connect(sid, environ, auth): # $ requestHandler routedParameter=sid routedParameter=environ routedParameter=auth
14+
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
1515
ensure_not_tainted(sid)
1616
ensure_tainted(environ, # $ tainted
1717
auth) # $ tainted
1818

1919
@sio.event
20-
def event1(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
20+
def event1(sid, data): # $ requestHandler routedParameter=data
2121
ensure_not_tainted(sid)
2222
ensure_tainted(data) # $ tainted
2323
res = sio.call("e1", sid=sid)
2424
ensure_tainted(res) # $ tainted
2525
sio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
2626
sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
2727

28+
class MyNamespace(socketio.Namespace):
29+
def on_event2(self, sid, data): # $ requestHandler routedParameter=data
30+
ensure_not_tainted(self, sid)
31+
ensure_tainted(data)
32+
res = self.call("e1", sid=sid)
33+
ensure_tainted(res) # $ tainted
34+
self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
35+
self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
36+
37+
sio.register_namespace(MyNamespace("/ns"))
38+
2839
asio = socketio.AsyncServer(async_mode='asgi')
2940

3041
@asio.event
31-
async def event2(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
42+
async def event3(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
3243
ensure_not_tainted(sid)
3344
ensure_tainted(data) # $ tainted
34-
res = await asio.call("e2", sid=sid)
45+
res = await asio.call("e1", sid=sid)
3546
ensure_tainted(res) # $ tainted
36-
await asio.emit("e3", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
47+
await asio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
3748
await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted
3849

3950
if __name__ == "__main__":

python/ql/test/library-tests/frameworks/socketio/test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@
33
sio = socketio.Server()
44

55
@sio.on("connect")
6-
def connect(sid, environ, auth): # $ requestHandler routedParameter=sid routedParameter=environ routedParameter=auth
6+
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
77
print("connect", sid, environ, auth)
88

99
@sio.on("event1")
10-
def handle(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
10+
def handle(sid, data): # $ requestHandler routedParameter=data
1111
print("e1", sid, data)
1212

1313
@sio.event
14-
def event2(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
14+
def event2(sid, data): # $ requestHandler routedParameter=data
1515
print("e2", sid, data)
1616

17-
def event3(sid, data): # $ requestHandler routedParameter=sid routedParameter=data
17+
def event3(sid, data): # $ requestHandler routedParameter=data
1818
print("e3", sid, data)
1919

2020
sio.on("event3", handler=event3)
2121

22-
sio.on("event4", lambda sid,data: print("e4", sid, data)) # $ requestHandler routedParameter=sid routedParameter=data
22+
sio.on("event4", lambda sid,data: print("e4", sid, data)) # $ requestHandler routedParameter=data
2323

2424

2525

0 commit comments

Comments
 (0)