Skip to content

Commit 276f216

Browse files
author
Max Schaefer
committed
JavaScript: Use type tracking to improve modelling of socket.io.
1 parent 4702790 commit 276f216

File tree

1 file changed

+131
-89
lines changed

1 file changed

+131
-89
lines changed

javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll

Lines changed: 131 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,20 @@ module SocketIO {
2424
result = DataFlow::moduleImport("socket.io").getAMemberCall("listen")
2525
}
2626

27-
/** A data flow node that may produce (that is, create or return) a socket.io server. */
28-
class ServerNode extends DataFlow::SourceNode {
29-
ServerObject srv;
30-
31-
ServerNode() {
32-
this = newServer() and
33-
srv = MkServer(this)
27+
/**
28+
* Gets a data flow node that may refer to the socket.io server created at `srv`, with
29+
* type tracking info stored in `t`.
30+
*/
31+
private DataFlow::SourceNode server(ServerObject srv, DataFlow::TypeTracker t) {
32+
result = newServer() and
33+
srv = MkServer(result) and
34+
t.start()
35+
or
36+
exists(DataFlow::TypeTracker s, DataFlow::SourceNode pred | pred = server(srv, s) |
37+
result = pred.track(s, t)
3438
or
3539
// invocation of a chainable method
36-
exists(ServerNode base, DataFlow::MethodCallNode mcn, string m |
40+
exists(DataFlow::MethodCallNode mcn, string m |
3741
m = "adapter" or
3842
m = "attach" or
3943
m = "bind" or
@@ -44,96 +48,118 @@ module SocketIO {
4448
m = "serveClient" or
4549
m = "set"
4650
|
47-
mcn = base.getAMethodCall(m) and
51+
mcn = pred.getAMethodCall(m) and
4852
// exclude getter versions
4953
exists(mcn.getAnArgument()) and
50-
this = mcn and
51-
srv = base.getServer()
54+
result = mcn and
55+
t = s
5256
)
53-
}
57+
)
58+
}
59+
60+
/** A data flow node that may produce (that is, create or return) a socket.io server. */
61+
class ServerNode extends DataFlow::SourceNode {
62+
ServerObject srv;
63+
64+
ServerNode() { this = server(srv, _) }
5465

5566
/** Gets the server to which this node refers. */
5667
ServerObject getServer() { result = srv }
5768
}
5869

59-
/** A data flow node that may produce a namespace object. */
60-
class NamespaceNode extends DataFlow::SourceNode {
61-
NamespaceObject ns;
70+
/**
71+
* Gets the name of a chainable method on socket.io namespace objects, which servers forward
72+
* to their default namespace.
73+
*/
74+
private string namespaceChainableMethod() {
75+
result = "binary" or
76+
result = "clients" or
77+
result = "compress" or
78+
result = "emit" or
79+
result = "in" or
80+
result = "send" or
81+
result = "to" or
82+
result = "use" or
83+
result = "write" or
84+
result = EventEmitter::chainableMethod()
85+
}
6286

63-
NamespaceNode() {
64-
// namespace lookup
65-
exists(ServerNode srv |
66-
this = srv.getAPropertyRead("sockets") and
67-
ns = srv.getServer().getDefaultNamespace()
68-
or
69-
exists(DataFlow::MethodCallNode mcn, string path |
70-
mcn = srv.getAMethodCall("of") and
71-
mcn.getArgument(0).mayHaveStringValue(path) and
72-
this = mcn and
73-
ns = MkNamespace(srv.getServer(), path)
74-
)
87+
/**
88+
* Gets a data flow node that may refer to the socket.io namespace created at `ns`, with
89+
* type tracking info stored in `t`.
90+
*/
91+
private DataFlow::SourceNode namespace(NamespaceObject ns, DataFlow::TypeTracker t) {
92+
t.start() and
93+
exists(ServerNode srv |
94+
// namespace lookup on `srv`
95+
result = srv.getAPropertyRead("sockets") and
96+
ns = srv.getServer().getDefaultNamespace()
97+
or
98+
exists(DataFlow::MethodCallNode mcn, string path |
99+
mcn = srv.getAMethodCall("of") and
100+
mcn.getArgument(0).mayHaveStringValue(path) and
101+
result = mcn and
102+
ns = MkNamespace(srv.getServer(), path)
75103
)
76104
or
105+
// invocation of a method that `srv` forwards to its default namespace
106+
result = srv.getAMethodCall(namespaceChainableMethod()) and
107+
ns = srv.getServer().getDefaultNamespace()
108+
)
109+
or
110+
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker s | pred = namespace(ns, s) |
111+
result = pred.track(s, t)
112+
or
77113
// invocation of a chainable method
78-
exists(string m |
79-
m = "binary" or
80-
m = "clients" or
81-
m = "compress" or
82-
m = "emit" or
83-
m = "in" or
84-
m = "send" or
85-
m = "to" or
86-
m = "use" or
87-
m = "write" or
88-
m = EventEmitter::chainableMethod()
89-
|
90-
exists(NamespaceNode base |
91-
this = base.getAMethodCall(m) and
92-
ns = base.getNamespace()
93-
)
94-
or
95-
// server objects forward these methods to their default namespace
96-
exists(ServerNode srv |
97-
this = srv.getAMethodCall(m) and
98-
ns = srv.getServer().getDefaultNamespace()
99-
)
100-
)
114+
result = pred.getAMethodCall(namespaceChainableMethod()) and
115+
t = s
101116
or
102117
// invocation of chainable getter method
103-
exists(NamespaceNode base, string m |
118+
exists(string m |
104119
m = "json" or
105120
m = "local" or
106121
m = "volatile"
107122
|
108-
this = base.getAPropertyRead(m) and
109-
ns = base.getNamespace()
123+
result = pred.getAPropertyRead(m) and
124+
t = s
110125
)
111-
}
126+
)
127+
}
128+
129+
/** A data flow node that may produce a namespace object. */
130+
class NamespaceNode extends DataFlow::SourceNode {
131+
NamespaceObject ns;
132+
133+
NamespaceNode() { this = namespace(ns, _) }
112134

113135
/** Gets the namespace to which this node refers. */
114136
NamespaceObject getNamespace() { result = ns }
115137
}
116138

117-
/** A data flow node that may produce a socket object. */
118-
class SocketNode extends DataFlow::SourceNode {
119-
NamespaceObject ns;
120-
121-
SocketNode() {
122-
// callback accepting a socket
123-
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
124-
(
125-
ns = base.(ServerNode).getServer().getDefaultNamespace() or
126-
ns = base.(NamespaceNode).getNamespace()
127-
) and
128-
(connect = "connect" or connect = "connection")
129-
|
130-
on = base.getAMethodCall(EventEmitter::on()) and
131-
on.getArgument(0).mayHaveStringValue(connect) and
132-
this = on.getCallback(1).getParameter(0)
133-
)
139+
/**
140+
* Gets a data flow node that may refer to a socket.io socket belonging to namespace `ns`, with
141+
* type tracking info stored in `t`.
142+
*/
143+
private DataFlow::SourceNode socket(NamespaceObject ns, DataFlow::TypeTracker t) {
144+
// callback accepting a socket
145+
t.start() and
146+
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
147+
(
148+
ns = base.(ServerNode).getServer().getDefaultNamespace() or
149+
ns = base.(NamespaceNode).getNamespace()
150+
) and
151+
(connect = "connect" or connect = "connection")
152+
|
153+
on = base.getAMethodCall(EventEmitter::on()) and
154+
on.getArgument(0).mayHaveStringValue(connect) and
155+
result = on.getCallback(1).getParameter(0)
156+
)
157+
or
158+
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker s | pred = socket(ns, s) |
159+
result = pred.track(s, t)
134160
or
135161
// invocation of a chainable method
136-
exists(SocketNode base, string m |
162+
exists(string m |
137163
m = "binary" or
138164
m = "compress" or
139165
m = "disconnect" or
@@ -147,21 +173,28 @@ module SocketIO {
147173
m = "write" or
148174
m = EventEmitter::chainableMethod()
149175
|
150-
this = base.getAMethodCall(m) and
151-
ns = base.getNamespace()
176+
result = pred.getAMethodCall(m) and
177+
t = s
152178
)
153179
or
154180
// invocation of a chainable getter method
155-
exists(SocketNode base, string m |
181+
exists(string m |
156182
m = "broadcast" or
157183
m = "json" or
158184
m = "local" or
159185
m = "volatile"
160186
|
161-
this = base.getAPropertyRead(m) and
162-
ns = base.getNamespace()
187+
result = pred.getAPropertyRead(m) and
188+
t = s
163189
)
164-
}
190+
)
191+
}
192+
193+
/** A data flow node that may produce a socket object. */
194+
class SocketNode extends DataFlow::SourceNode {
195+
NamespaceObject ns;
196+
197+
SocketNode() { this = socket(ns, _) }
165198

166199
/** Gets the namespace to which this socket belongs. */
167200
NamespaceObject getNamespace() { result = ns }
@@ -361,21 +394,30 @@ module SocketIO {
361394
* (npm package `socket.io-client`).
362395
*/
363396
module SocketIOClient {
397+
/**
398+
* Gets a data flow node that may refer to the socket.io socket created at `invk`, with
399+
* type tracking info stored in `t`.
400+
*/
401+
private DataFlow::SourceNode socket(DataFlow::InvokeNode invk, DataFlow::TypeTracker t) {
402+
t.start() and
403+
exists(DataFlow::SourceNode io |
404+
io = DataFlow::globalVarRef("io") or
405+
io = DataFlow::globalVarRef("io").getAPropertyRead("connect") or
406+
io = DataFlow::moduleImport("socket.io-client") or
407+
io = DataFlow::moduleMember("socket.io-client", "connect")
408+
|
409+
invk = io.getAnInvocation() and
410+
result = invk
411+
)
412+
or
413+
exists(DataFlow::TypeTracker s | result = socket(invk, s).track(s, t))
414+
}
415+
364416
/** A data flow node that may produce a socket object. */
365417
class SocketNode extends DataFlow::SourceNode {
366418
DataFlow::InvokeNode invk;
367419

368-
SocketNode() {
369-
exists(DataFlow::SourceNode io |
370-
io = DataFlow::globalVarRef("io") or
371-
io = DataFlow::globalVarRef("io").getAPropertyRead("connect") or
372-
io = DataFlow::moduleImport("socket.io-client") or
373-
io = DataFlow::moduleMember("socket.io-client", "connect")
374-
|
375-
invk = io.getAnInvocation() and
376-
this = invk
377-
)
378-
}
420+
SocketNode() { this = socket(invk, _) }
379421

380422
/** Gets the path of the namespace this socket belongs to, if it can be determined. */
381423
string getNamespacePath() {

0 commit comments

Comments
 (0)