implement SocketIO on top of the EventEmitter model

This commit is contained in:
Erik Krogh Kristensen
2019-12-12 16:46:06 +01:00
parent 8128d23b6e
commit 4e880e2f96
14 changed files with 371 additions and 334 deletions

View File

@@ -117,9 +117,9 @@ module EventRegistration {
abstract DataFlow::Node getReceivedItem(int i);
abstract DataFlow::Node getAReturnedValue();
DataFlow::Node getAReturnedValue() { none() }
abstract EventDispatch::Range getAReturnDispatch();
EventDispatch::Range getAReturnDispatch() { none() }
}
/**
@@ -136,10 +136,6 @@ module EventRegistration {
override DataFlow::Node getReceivedItem(int i) {
result = this.getABoundCallbackParameter(1, i)
}
override DataFlow::Node getAReturnedValue() { none() }
override EventDispatch::Range getAReturnDispatch() { none() }
}
}
@@ -227,3 +223,4 @@ private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep {
succ = dispatch
}
}

View File

@@ -3,7 +3,6 @@
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* Provides classes for working with server-side socket.io code
@@ -24,46 +23,64 @@ module SocketIO {
result = DataFlow::moduleImport("socket.io").getAMemberCall("listen")
}
/**
* Gets a data flow node that may refer to the socket.io server created at `srv`.
*/
private DataFlow::SourceNode server(ServerObject srv, DataFlow::TypeTracker t) {
result = newServer() and
srv = MkServer(result) and
t.start()
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = server(srv, t2) |
result = pred.track(t2, t)
abstract private class SocketIOObject extends DataFlow::SourceNode, EventEmitter::Range {
abstract DataFlow::SourceNode ref();
}
/** A socket.io server. */
class ServerObject extends SocketIOObject {
ServerObject() {
this = newServer()
}
/** Gets the default namespace of this server. */
NamespaceObject getDefaultNamespace() { result = MkNamespace(this, "/") }
/** Gets the namespace with the given path of this server. */
NamespaceObject getNamespace(string path) { result = MkNamespace(this, path) }
/**
* Gets a data flow node that may refer to the socket.io server created at `srv`.
*/
private DataFlow::SourceNode server(DataFlow::TypeTracker t) {
result = this and t.start()
or
// invocation of a chainable method
exists(DataFlow::MethodCallNode mcn, string m |
m = "adapter" or
m = "attach" or
m = "bind" or
m = "listen" or
m = "onconnection" or
m = "origins" or
m = "path" or
m = "serveClient" or
m = "set"
|
mcn = pred.getAMethodCall(m) and
// exclude getter versions
exists(mcn.getAnArgument()) and
result = mcn and
t = t2.continue()
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = server(t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(DataFlow::MethodCallNode mcn, string m |
m = "adapter" or
m = "attach" or
m = "bind" or
m = "listen" or
m = "onconnection" or
m = "origins" or
m = "path" or
m = "serveClient" or
m = "set" or
m = EventEmitter::chainableMethod()
|
mcn = pred.getAMethodCall(m) and
// exclude getter versions
exists(mcn.getAnArgument()) and
result = mcn and
t = t2.continue()
)
)
)
}
override DataFlow::SourceNode ref() { result = server(DataFlow::TypeTracker::end()) }
}
/** A data flow node that may produce (that is, create or return) a socket.io server. */
class ServerNode extends DataFlow::SourceNode {
ServerObject srv;
ServerObject obj;
ServerNode() { this = server(srv, DataFlow::TypeTracker::end()) }
ServerNode() { this = obj.ref() }
/** Gets the server to which this node refers. */
ServerObject getServer() { result = srv }
ServerObject getServer() { result = obj }
}
/**
@@ -84,157 +101,184 @@ module SocketIO {
}
/**
* Gets a data flow node that may refer to the socket.io namespace created at `ns`.
* A reference to a namespace object.
*/
private DataFlow::SourceNode namespace(NamespaceObject ns, DataFlow::TypeTracker t) {
t.start() and
exists(ServerNode srv |
// namespace lookup on `srv`
result = srv.getAPropertyRead("sockets") and
ns = srv.getServer().getDefaultNamespace()
or
exists(DataFlow::MethodCallNode mcn, string path |
mcn = srv.getAMethodCall("of") and
mcn.getArgument(0).mayHaveStringValue(path) and
result = mcn and
ns = MkNamespace(srv.getServer(), path)
class NamespaceBase extends SocketIOObject {
NamespaceObject ns;
NamespaceBase() {
exists(ServerObject srv |
// namespace lookup on `srv`
this = srv.ref().getAPropertyRead("sockets") and
ns = srv.getDefaultNamespace()
or
exists(DataFlow::MethodCallNode mcn, string path |
mcn = srv.ref().getAMethodCall("of") and
mcn.getArgument(0).mayHaveStringValue(path) and
this = mcn and
ns = MkNamespace(srv, path)
)
or
// invocation of a method that `srv` forwards to its default namespace
this = srv.ref().getAMethodCall(namespaceChainableMethod()) and
ns = srv.getDefaultNamespace()
)
}
/** Gets the namespace to which this object refers. */
NamespaceObject getNamespace() { result = ns }
/**
* Gets a data flow node that may refer to the socket.io namespace created at `ns`.
*/
private DataFlow::SourceNode namespace(DataFlow::TypeTracker t) {
t.start() and result = this
or
// invocation of a method that `srv` forwards to its default namespace
result = srv.getAMethodCall(namespaceChainableMethod()) and
ns = srv.getServer().getDefaultNamespace()
)
or
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = namespace(ns, t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
result = pred.getAMethodCall(namespaceChainableMethod()) and
t = t2.continue()
or
// invocation of chainable getter method
exists(string m |
m = "json" or
m = "local" or
m = "volatile"
|
result = pred.getAPropertyRead(m) and
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = namespace(t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
result = pred.getAMethodCall(namespaceChainableMethod()) and
t = t2.continue()
or
// invocation of chainable getter method
exists(string m |
m = "json" or
m = "local" or
m = "volatile"
|
result = pred.getAPropertyRead(m) and
t = t2.continue()
)
)
)
}
override DataFlow::SourceNode ref() { result = namespace(DataFlow::TypeTracker::end()) }
}
/** A data flow node that may produce a namespace object. */
class NamespaceNode extends DataFlow::SourceNode {
NamespaceObject ns;
NamespaceBase namespace;
NamespaceNode() { this = namespace(ns, DataFlow::TypeTracker::end()) }
NamespaceNode() { this = namespace.ref() }
/** Gets the namespace to which this node refers. */
NamespaceObject getNamespace() { result = ns }
NamespaceObject getNamespace() { result = namespace.getNamespace() }
}
/**
* Gets a data flow node that may refer to a socket.io socket belonging to namespace `ns`.
*/
private DataFlow::SourceNode socket(NamespaceObject ns, DataFlow::TypeTracker t) {
// callback accepting a socket
t.start() and
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
(
ns = base.(ServerNode).getServer().getDefaultNamespace() or
ns = base.(NamespaceNode).getNamespace()
) and
(connect = "connect" or connect = "connection")
|
on = base.getAMethodCall(EventEmitter::on()) and
on.getArgument(0).mayHaveStringValue(connect) and
result = on.getCallback(1).getParameter(0)
)
or
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = socket(ns, t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(string m |
m = "binary" or
m = "compress" or
m = "disconnect" or
m = "emit" or
m = "in" or
m = "join" or
m = "leave" or
m = "send" or
m = "to" or
m = "use" or
m = "write" or
m = EventEmitter::chainableMethod()
/** An socket object from SocketIO */
class SocketObject extends SocketIOObject {
NamespaceObject ns;
SocketObject() {
exists(DataFlow::SourceNode base, string connect, DataFlow::MethodCallNode on |
(
ns = any(ServerObject o | o.ref() = base).getDefaultNamespace() or
ns = any(NamespaceBase o | o.ref() = base).getNamespace()
) and
(connect = "connect" or connect = "connection")
|
result = pred.getAMethodCall(m) and
t = t2.continue()
on = base.getAMethodCall(EventEmitter::on()) and
on.getArgument(0).mayHaveStringValue(connect) and
this = on.getCallback(1).getParameter(0)
)
}
/** Gets the namespace to which this socket belongs. */
NamespaceObject getNamespace() { result = ns }
private DataFlow::SourceNode socket(DataFlow::TypeTracker t) {
result = this and t.start()
or
// invocation of a chainable getter method
exists(string m |
m = "broadcast" or
m = "json" or
m = "local" or
m = "volatile"
|
result = pred.getAPropertyRead(m) and
t = t2.continue()
exists(DataFlow::SourceNode pred, DataFlow::TypeTracker t2 | pred = socket(t2) |
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(string m |
m = "binary" or
m = "compress" or
m = "disconnect" or
m = "emit" or
m = "in" or
m = "join" or
m = "leave" or
m = "send" or
m = "to" or
m = "use" or
m = "write" or
m = EventEmitter::chainableMethod()
|
result = pred.getAMethodCall(m) and
t = t2.continue()
)
or
// invocation of a chainable getter method
exists(string m |
m = "broadcast" or
m = "json" or
m = "local" or
m = "volatile"
|
result = pred.getAPropertyRead(m) and
t = t2.continue()
)
)
)
}
override DataFlow::SourceNode ref() { result = socket(DataFlow::TypeTracker::end()) }
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
NamespaceObject ns;
SocketObject socket;
SocketNode() { this = socket(ns, DataFlow::TypeTracker::end()) }
SocketNode() { this = socket.ref() }
/** Gets the namespace to which this socket belongs. */
NamespaceObject getNamespace() { result = ns }
NamespaceObject getNamespace() { result = socket.getNamespace() }
}
/**
* A data flow node representing an API call that receives data from a client.
*/
class ReceiveNode extends DataFlow::MethodCallNode {
SocketNode socket;
class ReceiveNode extends EventRegistration::Range, DataFlow::MethodCallNode {
override SocketObject emitter;
ReceiveNode() { this = socket.getAMethodCall(EventEmitter::on()) }
ReceiveNode() { this = emitter.ref().getAMethodCall(EventEmitter::on()) }
/** Gets the socket through which data is received. */
SocketNode getSocket() { result = socket }
/** Gets the event name associated with the data, if it can be determined. */
string getEventName() { getArgument(0).mayHaveStringValue(result) }
SocketObject getSocket() { result = emitter }
/** Gets the callback that handles data received from a client. */
private DataFlow::FunctionNode getListener() { result = getCallback(1) }
DataFlow::FunctionNode getListener() { result = getCallback(1) }
/** Gets the `i`th parameter through which data is received from a client. */
DataFlow::SourceNode getReceivedItem(int i) {
override DataFlow::SourceNode getReceivedItem(int i) {
exists(DataFlow::FunctionNode cb | cb = getListener() and result = cb.getParameter(i) |
// exclude last parameter if it looks like a callback
result != cb.getLastParameter() or not exists(result.getAnInvocation())
)
}
/** Gets a data flow node representing data received from a client. */
DataFlow::SourceNode getAReceivedItem() { result = getReceivedItem(_) }
override string getChannel() { this.getArgument(0).mayHaveStringValue(result) }
}
/** Gets the acknowledgment callback, if any. */
DataFlow::SourceNode getAck() {
result = getListener().getLastParameter() and
exists(result.getAnInvocation())
/** An acknowledgment callback when receiving a message. */
class ReceiveCallback extends EventDispatch::Range, DataFlow::SourceNode {
ReceiveNode rcv;
ReceiveCallback() {
this = rcv.getListener().getLastParameter() and
exists(this.getAnInvocation()) and
emitter = rcv.getEmitter()
}
/** Gets a client-side node that may be sending the data received here. */
SocketIOClient::SendNode getASender() {
result.getSocket().getATargetNamespace() = getSocket().getNamespace() and
not result.getEventName() != getEventName()
override string getChannel() { result = rcv.getChannel() }
override DataFlow::Node getSentItem(int i) { result = this.getACall().getArgument(i) }
override SocketIOClient::SendCallback getAReceiver() {
result.getSendNode().getAReceiver() = rcv
}
}
@@ -242,7 +286,7 @@ module SocketIO {
* A data flow node representing data received from a client, viewed as remote user input.
*/
private class ReceivedItemAsRemoteFlow extends RemoteFlowSource {
ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getAReceivedItem() }
ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getReceivedItem(_) }
override string getSourceType() { result = "socket.io client data" }
@@ -252,15 +296,12 @@ module SocketIO {
/**
* A data flow node representing an API call that sends data to a client.
*/
class SendNode extends DataFlow::MethodCallNode {
DataFlow::SourceNode base;
class SendNode extends DataFlow::MethodCallNode, EventDispatch::Range {
override SocketIOObject emitter;
int firstDataIndex;
SendNode() {
exists(string m |
(base instanceof ServerNode or base instanceof NamespaceNode or base instanceof SocketNode) and
this = base.getAMethodCall(m)
|
exists(string m | this = emitter.ref().getAMethodCall(m) |
// a call to `emit`
m = "emit" and
firstDataIndex = 1
@@ -276,92 +317,65 @@ module SocketIO {
*
* This predicate is not defined for broadcasting sends.
*/
SocketNode getSocket() { result = base }
SocketObject getSocket() { result = emitter }
/**
* Gets the namespace to which data is sent.
*/
NamespaceObject getNamespace() {
result = base.(ServerNode).getServer().getDefaultNamespace() or
result = base.(NamespaceNode).getNamespace() or
result = base.(SocketNode).getNamespace()
result = emitter.(ServerObject).getDefaultNamespace() or
result = emitter.(NamespaceBase).getNamespace() or
result = emitter.(SocketObject).getNamespace()
}
/** Gets the event name associated with the data, if it can be determined. */
string getEventName() {
override string getChannel() {
if firstDataIndex = 1 then getArgument(0).mayHaveStringValue(result) else result = "message"
}
/** Gets the `i`th argument through which data is sent to the client. */
DataFlow::Node getSentItem(int i) {
override DataFlow::Node getSentItem(int i) {
result = getArgument(i + firstDataIndex) and
i >= 0 and
(
// exclude last argument if it looks like a callback
result != getLastArgument() or not exists(getAck())
result != getLastArgument() or not exists(SendCallback c | c.getSendNode() = this)
)
}
/** Gets a data flow node representing data sent to the client. */
DataFlow::Node getASentItem() { result = getSentItem(_) }
/** Gets the acknowledgment callback, if any. */
DataFlow::FunctionNode getAck() {
// acknowledgments are only available when sending through a socket
exists(getSocket()) and
result = getLastArgument().getALocalSource()
}
/** Gets a client-side node that may be receiving the data sent here. */
SocketIOClient::ReceiveNode getAReceiver() {
result.getSocket().getATargetNamespace() = getNamespace() and
not result.getEventName() != getEventName()
override SocketIOClient::ReceiveNode getAReceiver() {
result.getSocket().getATargetNamespace() = getNamespace()
}
}
/** A socket.io server, identified by its creation site. */
private newtype TServer = MkServer(DataFlow::SourceNode nd) { nd = newServer() }
/** A socket.io namespace, identified by its server and its path. */
private newtype TNamespace =
MkNamespace(ServerObject srv, string path) {
path = "/"
or
exists(ServerNode nd | nd.getServer() = srv |
nd.getAMethodCall("of").getArgument(0).mayHaveStringValue(path)
)
srv.ref().getAMethodCall("of").getArgument(0).mayHaveStringValue(path)
}
/** A socket.io server. */
class ServerObject extends TServer {
DataFlow::SourceNode origin;
/** An acknowledgment callback from sending message. */
class SendCallback extends EventRegistration::Range, DataFlow::FunctionNode {
SendNode send;
ServerObject() { this = MkServer(origin) }
SendCallback() {
// acknowledgments are only available when sending through a socket
exists(send.getSocket()) and
this = send.getLastArgument().getALocalSource() and
emitter = send.getEmitter()
}
/** Gets the data flow node where this server is created. */
DataFlow::SourceNode getOrigin() { result = origin }
override string getChannel() { result = send.getChannel() }
/** Gets the default namespace of this server. */
NamespaceObject getDefaultNamespace() { result = MkNamespace(this, "/") }
/** Gets the namespace with the given path of this server. */
NamespaceObject getNamespace(string path) { result = MkNamespace(this, path) }
override DataFlow::Node getReceivedItem(int i) { result = this.getParameter(i) }
/**
* Holds if this server is created at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
* Get the send node where this callback was registered.
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
origin.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this server. */
string toString() { result = "socket.io server" }
SendNode getSendNode() { result = send }
}
/** A socket.io namespace. */
@@ -387,42 +401,34 @@ module SocketIO {
* (npm package `socket.io-client`).
*/
module SocketIOClient {
/**
* Gets a data flow node that may refer to the socket.io socket created at `invk`.
*/
private DataFlow::SourceNode socket(DataFlow::InvokeNode invk, DataFlow::TypeTracker t) {
t.start() and
exists(DataFlow::SourceNode io |
io = DataFlow::globalVarRef("io") or
io = DataFlow::globalVarRef("io").getAPropertyRead("connect") or
io = DataFlow::moduleImport("io") or
io = DataFlow::moduleMember("io", "connect") or
io = DataFlow::moduleImport("socket.io-client") or
io = DataFlow::moduleMember("socket.io-client", "connect")
|
invk = io.getAnInvocation() and
result = invk
)
or
exists(DataFlow::TypeTracker t2 | result = socket(invk, t2).track(t2, t))
}
/** A socket object. */
class SocketObject extends DataFlow::InvokeNode, EventEmitter::Range {
SocketObject() {
exists(DataFlow::SourceNode io |
io = DataFlow::globalVarRef("io") or
io = DataFlow::globalVarRef("io").getAPropertyRead("connect") or
io = DataFlow::moduleImport("io") or
io = DataFlow::moduleMember("io", "connect") or
io = DataFlow::moduleImport("socket.io-client") or
io = DataFlow::moduleMember("socket.io-client", "connect")
|
this = io.getAnInvocation()
)
}
/**
* Gets the NPM package that contains `nd`.
*/
private NPMPackage getPackage(DataFlow::SourceNode nd) { result.getAFile() = nd.getFile() }
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
t.start() and result = this
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
DataFlow::InvokeNode invk;
SocketNode() { this = socket(invk, DataFlow::TypeTracker::end()) }
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
/** Gets the path of the namespace this socket belongs to, if it can be determined. */
string getNamespacePath() {
// the path name of the specified URL
exists(string url, string pathRegex |
invk.getArgument(0).mayHaveStringValue(url) and
this.getArgument(0).mayHaveStringValue(url) and
pathRegex = "(?<!/)/(?!/)[^?#]*"
|
result = url.regexpFind(pathRegex, 0, _)
@@ -433,7 +439,7 @@ module SocketIOClient {
)
or
// if no URL is specified, the path defaults to "/"
not exists(invk.getArgument(0)) and
not exists(this.getArgument(0)) and
result = "/"
}
@@ -446,7 +452,7 @@ module SocketIOClient {
* it can be determined.
*/
SocketIO::ServerObject getATargetServer() {
getPackage(result.getOrigin()) = getPackage(this) and
getPackage(result) = getPackage(this) and
(
not exists(getNamespacePath()) or
exists(result.getNamespace(getNamespacePath()))
@@ -463,22 +469,51 @@ module SocketIOClient {
}
/** Gets a server-side socket this client-side socket may be communicating with. */
SocketIO::SocketNode getATargetSocket() { result.getNamespace() = getATargetNamespace() }
SocketIO::SocketObject getATargetSocket() { result.getNamespace() = getATargetNamespace() }
}
/** A data flow node that may produce a socket object. */
class SocketNode extends DataFlow::SourceNode {
SocketObject socket;
SocketNode() { this = socket.ref() }
/** Gets the path of the namespace this socket belongs to, if it can be determined. */
string getNamespacePath() { result = socket.getNamespacePath() }
/**
* Gets a server this socket may be communicating with.
*
* To avoid matching sockets with unrelated servers, we restrict the search to
* servers defined in the same npm package. Furthermore, the server is required
* to have a namespace with the same path as the namespace of this socket, if
* it can be determined.
*/
SocketIO::ServerObject getATargetServer() { result = socket.getATargetServer() }
/** Gets a namespace this socket may be communicating with. */
SocketIO::NamespaceObject getATargetNamespace() { result = socket.getATargetNamespace() }
/** Gets a server-side socket this client-side socket may be communicating with. */
SocketIO::SocketNode getATargetSocket() { result.getNamespace() = socket.getATargetNamespace() }
}
/** Gets the NPM package that contains `nd`. */
private NPMPackage getPackage(DataFlow::SourceNode nd) { result.getAFile() = nd.getFile() }
/**
* A data flow node representing an API call that receives data from the server.
*/
class ReceiveNode extends DataFlow::MethodCallNode {
SocketNode socket;
class ReceiveNode extends DataFlow::MethodCallNode, EventRegistration::Range {
override SocketObject emitter;
ReceiveNode() { this = socket.getAMethodCall(EventEmitter::on()) }
ReceiveNode() { this = emitter.ref().getAMethodCall(EventEmitter::on()) }
/** Gets the socket through which data is received. */
SocketNode getSocket() { result = socket }
SocketObject getSocket() { result = emitter }
/** Gets the event name associated with the data, if it can be determined. */
string getEventName() { getArgument(0).mayHaveStringValue(result) }
override string getChannel() { getArgument(0).mayHaveStringValue(result) }
private DataFlow::SourceNode getListener(DataFlow::TypeBackTracker t) {
t.start() and
@@ -488,43 +523,49 @@ module SocketIOClient {
}
/** Gets the callback that handles data received from the server. */
private DataFlow::FunctionNode getListener() {
result = getListener(DataFlow::TypeBackTracker::end())
}
DataFlow::FunctionNode getListener() { result = getListener(DataFlow::TypeBackTracker::end()) }
/** Gets the `i`th parameter through which data is received from the server. */
DataFlow::SourceNode getReceivedItem(int i) {
override DataFlow::SourceNode getReceivedItem(int i) {
exists(DataFlow::FunctionNode cb | cb = getListener() and result = cb.getParameter(i) |
// exclude the last parameter if it looks like a callback
result != cb.getLastParameter() or not exists(result.getAnInvocation())
)
}
}
/** Gets a data flow node representing data received from the server. */
DataFlow::SourceNode getAReceivedItem() { result = getReceivedItem(_) }
/** An acknowledgment callback from a receive node. */
class RecieveCallback extends EventDispatch::Range, DataFlow::SourceNode {
override SocketObject emitter;
ReceiveNode rcv;
/** Gets the acknowledgment callback, if any. */
DataFlow::SourceNode getAck() {
result = getListener().getLastParameter() and
exists(result.getAnInvocation())
RecieveCallback() {
this = rcv.getListener().getLastParameter() and
exists(this.getAnInvocation()) and
emitter = rcv.getEmitter()
}
/** Gets a server-side node that may be sending the data received here. */
SocketIO::SendNode getASender() {
result.getNamespace() = getSocket().getATargetNamespace() and
not result.getEventName() != getEventName()
}
override string getChannel() { result = rcv.getChannel() }
override DataFlow::Node getSentItem(int i) { result = this.getACall().getArgument(i) }
override SocketIO::SendCallback getAReceiver() { result.getSendNode().getAReceiver() = rcv }
/**
* Get the receive node where this callback was registered.
*/
ReceiveNode getReceiveNode() { result = rcv }
}
/**
* A data flow node representing an API call that sends data to the server.
*/
class SendNode extends DataFlow::MethodCallNode {
SocketNode base;
class SendNode extends DataFlow::MethodCallNode, EventDispatch::Range {
override SocketObject emitter;
int firstDataIndex;
SendNode() {
exists(string m | this = base.getAMethodCall(m) |
exists(string m | this = emitter.ref().getAMethodCall(m) |
// a call to `emit`
m = "emit" and
firstDataIndex = 1
@@ -538,73 +579,50 @@ module SocketIOClient {
/**
* Gets the socket through which data is sent to the server.
*/
SocketNode getSocket() { result = base }
SocketObject getSocket() { result = emitter }
/**
* Gets the path of the namespace to which data is sent, if it can be determined.
*/
string getNamespacePath() { result = base.getNamespacePath() }
string getNamespacePath() { result = emitter.getNamespacePath() }
/** Gets the event name associated with the data, if it can be determined. */
string getEventName() {
override string getChannel() {
if firstDataIndex = 1 then getArgument(0).mayHaveStringValue(result) else result = "message"
}
/** Gets the `i`th argument through which data is sent to the server. */
DataFlow::Node getSentItem(int i) {
override DataFlow::Node getSentItem(int i) {
result = getArgument(i + firstDataIndex) and
i >= 0 and
(
// exclude last argument if it looks like a callback
result != getLastArgument() or not exists(getAck())
result != getLastArgument() or not exists(SendCallback c | c.getSendNode() = this)
)
}
/** Gets a data flow node representing data sent to the server. */
DataFlow::Node getASentItem() { result = getSentItem(_) }
/** Gets the acknowledgment callback, if any. */
DataFlow::FunctionNode getAck() { result = getLastArgument().getALocalSource() }
/** Gets a server-side node that may be receiving the data sent here. */
SocketIO::ReceiveNode getAReceiver() {
result.getSocket().getNamespace() = getSocket().getATargetNamespace() and
not result.getEventName() != getEventName()
override SocketIO::ReceiveNode getAReceiver() {
result.getSocket().getNamespace() = getSocket().getATargetNamespace()
}
}
}
/** A data flow step through socket.io sockets. */
private class SocketIoStep extends DataFlow::AdditionalFlowStep {
DataFlow::Node pred;
DataFlow::Node succ;
/** An acknowledgment callback from sending message. */
class SendCallback extends EventRegistration::Range, DataFlow::FunctionNode {
SendNode send;
SocketIoStep() {
(
exists(SocketIO::SendNode send, SocketIOClient::ReceiveNode recv, int i |
recv = send.getAReceiver()
|
pred = send.getSentItem(i) and
succ = recv.getReceivedItem(i)
or
pred = recv.getAck().getACall().getArgument(i) and
succ = send.getAck().getParameter(i)
)
or
exists(SocketIOClient::SendNode send, SocketIO::ReceiveNode recv, int i |
recv = send.getAReceiver()
|
pred = send.getSentItem(i) and
succ = recv.getReceivedItem(i)
or
pred = recv.getAck().getACall().getArgument(i) and
succ = send.getAck().getParameter(i)
)
) and
this = pred
}
SendCallback() {
this = send.getLastArgument().getALocalSource() and
emitter = send.getEmitter()
}
override predicate step(DataFlow::Node predNode, DataFlow::Node succNode) {
predNode = pred and succNode = succ
override string getChannel() { result = send.getChannel() }
override DataFlow::Node getReceivedItem(int i) { result = this.getParameter(i) }
/**
* Get the SendNode where this callback was registered.
*/
SendNode getSendNode() { result = send }
}
}

View File

@@ -3,5 +3,5 @@ import javascript
query predicate test_ClientReceiveNode(
SocketIOClient::ReceiveNode rn, SocketIOClient::SocketNode res
) {
res = rn.getSocket()
res = rn.getSocket().ref()
}

View File

@@ -3,5 +3,5 @@ import javascript
query predicate test_ClientReceiveNode_getASender(
SocketIOClient::ReceiveNode rn, SocketIO::SendNode res
) {
res = rn.getASender()
res.getAReceiver() = rn
}

View File

@@ -1,7 +1,7 @@
import javascript
query predicate test_ClientReceiveNode_getAck(
SocketIOClient::ReceiveNode rn, DataFlow::SourceNode res
SocketIOClient::ReceiveNode rn, SocketIOClient::RecieveCallback res
) {
res = rn.getAck()
res.getReceiveNode() = rn
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_ClientReceiveNode_getEventName(SocketIOClient::ReceiveNode rn, string res) {
res = rn.getEventName()
res = rn.getChannel()
}

View File

@@ -3,5 +3,5 @@ import javascript
query predicate test_ClientSendNode(
SocketIOClient::SendNode sn, SocketIOClient::SocketNode res0, string res1
) {
res0 = sn.getSocket() and res1 = sn.getNamespacePath()
res0 = sn.getSocket().ref() and res1 = sn.getNamespacePath()
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_ClientSendNode_getAck(SocketIOClient::SendNode sn, DataFlow::FunctionNode res) {
res = sn.getAck()
query predicate test_ClientSendNode_getAck(SocketIOClient::SendNode sn, SocketIOClient::SendCallback res) {
res.getSendNode() = sn
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_ClientSendNode_getEventName(SocketIOClient::SendNode sn, string res) {
res = sn.getEventName()
res = sn.getChannel()
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_ReceiveNode_getASender(SocketIO::ReceiveNode rn, SocketIOClient::SendNode res) {
res = rn.getASender()
res.getAReceiver() = rn
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_ReceiveNode_getEventName(SocketIO::ReceiveNode rn, string res) {
res = rn.getEventName()
res = rn.getChannel()
}

View File

@@ -1,5 +1,5 @@
import javascript
query predicate test_SendNode_getAck(SocketIO::SendNode sn, DataFlow::FunctionNode res) {
res = sn.getAck()
query predicate test_SendNode_getAck(SocketIO::SendNode sn, SocketIO::SendCallback res) {
res.getSendNode() = sn
}

View File

@@ -3,5 +3,5 @@ import javascript
query predicate test_ServerObject(
SocketIO::ServerObject srv, DataFlow::SourceNode res0, SocketIO::NamespaceObject res1
) {
res0 = srv.getOrigin() and res1 = srv.getDefaultNamespace()
res0 = srv and res1 = srv.getDefaultNamespace()
}

View File

@@ -31,10 +31,12 @@ test_NamespaceNode
| tst.js:68:1:68:32 | ns.on(' ... => {}) | socket.io namespace with path '/' |
| tst.js:69:1:73:2 | ns.on(' ... {});\\n}) | socket.io namespace with path '/' |
test_ClientReceiveNode_getASender
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:30:1:30:28 | ns.emit ... event') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:31:1:31:20 | ns.send('a message') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:39:1:39:31 | io.emit ... ssage') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:40:1:40:20 | io.send('a message') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:41:1:41:21 | io.writ ... ssage') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:51:3:51:22 | socket.emit('event') |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:54:3:54:43 | socket. ... => {}) |
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | tst.js:55:3:55:27 | socket. ... ssage') |
| client2.js:8:1:8:33 | sock.on ... => {}) | tst.js:30:1:30:28 | ns.emit ... event') |
@@ -46,7 +48,13 @@ test_ClientReceiveNode_getASender
| client2.js:8:1:8:33 | sock.on ... => {}) | tst.js:54:3:54:43 | socket. ... => {}) |
| client2.js:8:1:8:33 | sock.on ... => {}) | tst.js:55:3:55:27 | socket. ... ssage') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:30:1:30:28 | ns.emit ... event') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:31:1:31:20 | ns.send('a message') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:39:1:39:31 | io.emit ... ssage') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:40:1:40:20 | io.send('a message') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:41:1:41:21 | io.writ ... ssage') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:51:3:51:22 | socket.emit('event') |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:54:3:54:43 | socket. ... => {}) |
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | tst.js:55:3:55:27 | socket. ... ssage') |
| client2.js:18:1:18:41 | sock2.o ... dler')) | tst.js:32:1:32:22 | ns2.wri ... ssage') |
test_ReceiveNode
| tst.js:70:3:70:35 | socket. ... => {}) | tst.js:69:22:69:27 | socket |
@@ -127,7 +135,9 @@ test_ClientSocketNode
| client4.js:3:1:3:4 | io() | / |
| client4.js:4:1:4:23 | io.conn ... sages") | /messages |
test_ReceiveNode_getASender
| tst.js:70:3:70:35 | socket. ... => {}) | client2.js:14:1:14:32 | sock.em ... there") |
| tst.js:70:3:70:35 | socket. ... => {}) | client2.js:16:1:16:36 | sock.wr ... => {}) |
| tst.js:71:3:71:46 | socket. ... => {}) | client2.js:14:1:14:32 | sock.em ... there") |
| tst.js:71:3:71:46 | socket. ... => {}) | client2.js:16:1:16:36 | sock.wr ... => {}) |
| tst.js:72:3:72:43 | socket. ... => {}) | client2.js:14:1:14:32 | sock.em ... there") |
| tst.js:72:3:72:43 | socket. ... => {}) | client2.js:16:1:16:36 | sock.wr ... => {}) |
@@ -140,20 +150,24 @@ test_SendNode_getSocket
| tst.js:54:3:54:43 | socket. ... => {}) | tst.js:50:19:50:24 | socket |
| tst.js:55:3:55:27 | socket. ... ssage') | tst.js:50:19:50:24 | socket |
test_ServerNode
| tst.js:1:12:1:33 | require ... .io')() | tst.js:1:12:1:33 | socket.io server |
| tst.js:4:13:4:24 | new Server() | tst.js:4:13:4:24 | socket.io server |
| tst.js:6:13:6:27 | Server.listen() | tst.js:6:13:6:27 | socket.io server |
| tst.js:9:1:9:21 | io.serv ... (false) | tst.js:1:12:1:33 | socket.io server |
| tst.js:10:1:10:21 | io.set( ... s', []) | tst.js:1:12:1:33 | socket.io server |
| tst.js:11:1:11:21 | io.path ... npath') | tst.js:1:12:1:33 | socket.io server |
| tst.js:12:1:12:15 | io.adapter(foo) | tst.js:1:12:1:33 | socket.io server |
| tst.js:13:1:13:14 | io.origins([]) | tst.js:1:12:1:33 | socket.io server |
| tst.js:14:1:14:15 | io.listen(http) | tst.js:1:12:1:33 | socket.io server |
| tst.js:15:1:15:15 | io.attach(http) | tst.js:1:12:1:33 | socket.io server |
| tst.js:16:1:16:15 | io.bind(engine) | tst.js:1:12:1:33 | socket.io server |
| tst.js:17:1:17:23 | io.onco ... socket) | tst.js:1:12:1:33 | socket.io server |
| tst.js:79:1:79:10 | obj.server | tst.js:1:12:1:33 | socket.io server |
| tst.js:1:12:1:33 | require ... .io')() | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:4:13:4:24 | new Server() | tst.js:4:13:4:24 | new Server() |
| tst.js:6:13:6:27 | Server.listen() | tst.js:6:13:6:27 | Server.listen() |
| tst.js:9:1:9:21 | io.serv ... (false) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:10:1:10:21 | io.set( ... s', []) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:11:1:11:21 | io.path ... npath') | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:12:1:12:15 | io.adapter(foo) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:13:1:13:14 | io.origins([]) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:14:1:14:15 | io.listen(http) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:15:1:15:15 | io.attach(http) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:16:1:16:15 | io.bind(engine) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:17:1:17:23 | io.onco ... socket) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:50:1:66:2 | io.on(' ... cal;\\n}) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:67:1:67:35 | io.on(' ... => {}) | tst.js:1:12:1:33 | require ... .io')() |
| tst.js:79:1:79:10 | obj.server | tst.js:1:12:1:33 | require ... .io')() |
test_ClientSendNode_getAReceiver
| client2.js:14:1:14:32 | sock.em ... there") | tst.js:70:3:70:35 | socket. ... => {}) |
| client2.js:14:1:14:32 | sock.em ... there") | tst.js:71:3:71:46 | socket. ... => {}) |
| client2.js:14:1:14:32 | sock.em ... there") | tst.js:72:3:72:43 | socket. ... => {}) |
| client2.js:16:1:16:36 | sock.wr ... => {}) | tst.js:70:3:70:35 | socket. ... => {}) |
| client2.js:16:1:16:36 | sock.wr ... => {}) | tst.js:71:3:71:46 | socket. ... => {}) |
@@ -167,10 +181,10 @@ test_ClientReceiveNode_getReceivedItem
| client2.js:10:1:12:2 | sock.on ... d");\\n}) | 0 | client2.js:10:19:10:19 | x |
| client2.js:18:1:18:41 | sock2.o ... dler')) | 0 | handler.js:1:19:1:19 | x |
test_NamespaceObject
| socket.io namespace with path '/' | tst.js:1:12:1:33 | socket.io server | / |
| socket.io namespace with path '/' | tst.js:4:13:4:24 | socket.io server | / |
| socket.io namespace with path '/' | tst.js:6:13:6:27 | socket.io server | / |
| socket.io namespace with path '/foo/bar' | tst.js:1:12:1:33 | socket.io server | /foo/bar |
| socket.io namespace with path '/' | tst.js:1:12:1:33 | require ... .io')() | / |
| socket.io namespace with path '/' | tst.js:4:13:4:24 | new Server() | / |
| socket.io namespace with path '/' | tst.js:6:13:6:27 | Server.listen() | / |
| socket.io namespace with path '/foo/bar' | tst.js:1:12:1:33 | require ... .io')() | /foo/bar |
test_ClientReceiveNode
| client2.js:4:1:6:2 | sock.on ... y);\\n}) | client2.js:1:12:1:56 | require ... lhost") |
| client2.js:8:1:8:33 | sock.on ... => {}) | client2.js:1:12:1:56 | require ... lhost") |
@@ -192,24 +206,32 @@ test_SendNode
| tst.js:54:3:54:43 | socket. ... => {}) | socket.io namespace with path '/' |
| tst.js:55:3:55:27 | socket. ... ssage') | socket.io namespace with path '/' |
test_SendNode_getAReceiver
| tst.js:30:1:30:28 | ns.emit ... event') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:30:1:30:28 | ns.emit ... event') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:30:1:30:28 | ns.emit ... event') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:31:1:31:20 | ns.send('a message') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:31:1:31:20 | ns.send('a message') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:31:1:31:20 | ns.send('a message') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:32:1:32:22 | ns2.wri ... ssage') | client2.js:18:1:18:41 | sock2.o ... dler')) |
| tst.js:39:1:39:31 | io.emit ... ssage') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:39:1:39:31 | io.emit ... ssage') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:39:1:39:31 | io.emit ... ssage') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:40:1:40:20 | io.send('a message') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:40:1:40:20 | io.send('a message') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:40:1:40:20 | io.send('a message') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:41:1:41:21 | io.writ ... ssage') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:41:1:41:21 | io.writ ... ssage') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:41:1:41:21 | io.writ ... ssage') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:51:3:51:22 | socket.emit('event') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:51:3:51:22 | socket.emit('event') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:51:3:51:22 | socket.emit('event') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:54:3:54:43 | socket. ... => {}) | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:54:3:54:43 | socket. ... => {}) | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:54:3:54:43 | socket. ... => {}) | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
| tst.js:55:3:55:27 | socket. ... ssage') | client2.js:4:1:6:2 | sock.on ... y);\\n}) |
| tst.js:55:3:55:27 | socket. ... ssage') | client2.js:8:1:8:33 | sock.on ... => {}) |
| tst.js:55:3:55:27 | socket. ... ssage') | client2.js:10:1:12:2 | sock.on ... d");\\n}) |
test_ServerObject
| tst.js:1:12:1:33 | socket.io server | tst.js:1:12:1:33 | require ... .io')() | socket.io namespace with path '/' |
| tst.js:4:13:4:24 | socket.io server | tst.js:4:13:4:24 | new Server() | socket.io namespace with path '/' |
| tst.js:6:13:6:27 | socket.io server | tst.js:6:13:6:27 | Server.listen() | socket.io namespace with path '/' |
| tst.js:1:12:1:33 | require ... .io')() | tst.js:1:12:1:33 | require ... .io')() | socket.io namespace with path '/' |
| tst.js:4:13:4:24 | new Server() | tst.js:4:13:4:24 | new Server() | socket.io namespace with path '/' |
| tst.js:6:13:6:27 | Server.listen() | tst.js:6:13:6:27 | Server.listen() | socket.io namespace with path '/' |