mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Merge pull request #2636 from erik-krogh/NewSocketIO
Approved by esbena
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
|
||||
/**
|
||||
* Provides classes for working with server-side socket.io code
|
||||
@@ -25,45 +24,76 @@ module SocketIO {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that may refer to the socket.io server created at `srv`.
|
||||
* A common superclass for all socket-like objects on the serverside of SocketIO.
|
||||
* All of the subclasses can be used to send data to SocketIO clients (see the `SendNode` class).
|
||||
*/
|
||||
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 {
|
||||
/**
|
||||
* Gets a node that refers to this SocketIOObject object.
|
||||
*/
|
||||
abstract DataFlow::SourceNode ref();
|
||||
|
||||
/** Gets the namespace belonging to this object. */
|
||||
abstract NamespaceObject getNamespace();
|
||||
}
|
||||
|
||||
/** 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 default namespace of this server. */
|
||||
override NamespaceObject getNamespace() { result = getDefaultNamespace() }
|
||||
|
||||
/** 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 +114,182 @@ 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()
|
||||
)
|
||||
}
|
||||
|
||||
override 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)
|
||||
)
|
||||
}
|
||||
|
||||
override 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 +297,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 +307,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
|
||||
@@ -273,95 +325,67 @@ module SocketIO {
|
||||
|
||||
/**
|
||||
* Gets the socket through which data is sent to the client.
|
||||
*
|
||||
* 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.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 registered when sending a message to a client.
|
||||
* Responses from clients are received using this callback.
|
||||
*/
|
||||
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).
|
||||
* Gets 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 +411,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 +449,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 +462,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 +479,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 +533,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 }
|
||||
|
||||
/**
|
||||
* Gets 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 +589,53 @@ 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 registered when sending a message to a server.
|
||||
* Responses from servers are received using this callback.
|
||||
*/
|
||||
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) }
|
||||
|
||||
/**
|
||||
* Gets the SendNode where this callback was registered.
|
||||
*/
|
||||
SendNode getSendNode() { result = send }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user