Skip to content

Commit 8128d23

Browse files
authored
Merge pull request #2505 from erik-krogh/EventEmitter
Approved by esbena, max-schaefer
2 parents 1887938 + 1619a98 commit 8128d23

File tree

12 files changed

+399
-162
lines changed

12 files changed

+399
-162
lines changed

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import semmle.javascript.frameworks.Credentials
7373
import semmle.javascript.frameworks.CryptoLibraries
7474
import semmle.javascript.frameworks.DigitalOcean
7575
import semmle.javascript.frameworks.Electron
76+
import semmle.javascript.frameworks.EventEmitter
7677
import semmle.javascript.frameworks.Files
7778
import semmle.javascript.frameworks.Firebase
7879
import semmle.javascript.frameworks.jQuery

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

Lines changed: 63 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -73,187 +73,109 @@ module Electron {
7373
/**
7474
* A reference to the `webContents` property of a browser object.
7575
*/
76-
class WebContents extends DataFlow::SourceNode {
76+
class WebContents extends DataFlow::SourceNode, NodeJSLib::NodeJSEventEmitter {
7777
WebContents() { this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents") }
7878
}
7979

8080
/**
8181
* Provides classes and predicates for modelling Electron inter-process communication (IPC).
82+
* The Electron IPC are EventEmitters, but they also expose a number of methods on top of the standard EventEmitter.
8283
*/
8384
private module IPC {
84-
class Process extends string {
85-
Process() { this = "main" or this = "renderer" }
85+
DataFlow::SourceNode main() { result = DataFlow::moduleMember("electron", "ipcMain") }
8686

87-
DataFlow::SourceNode getAnImport() {
88-
this = Process::main() and result = DataFlow::moduleMember("electron", "ipcMain")
89-
or
90-
this = Process::renderer() and result = DataFlow::moduleMember("electron", "ipcRenderer")
91-
}
92-
}
93-
94-
module Process {
95-
Process main() { result = "main" }
96-
97-
Process renderer() { result = "renderer" }
98-
}
99-
100-
/**
101-
* An IPC callback.
102-
*/
103-
class Callback extends DataFlow::FunctionNode {
104-
DataFlow::Node channel;
105-
Process process;
106-
107-
Callback() {
108-
exists(DataFlow::MethodCallNode mc |
109-
mc = process.getAnImport().getAMemberCall("on") and
110-
this = mc.getCallback(1) and
111-
channel = mc.getArgument(0)
112-
)
113-
}
114-
115-
/** Gets the process on which this callback is executed. */
116-
Process getProcess() { result = process }
117-
118-
/** Gets the name of the channel the callback is listening on. */
119-
string getChannelName() { result = channel.getStringValue() }
120-
121-
/** Gets the data flow node containing the message received by the callback. */
122-
DataFlow::Node getMessage() { result = getParameter(1) }
123-
}
87+
DataFlow::SourceNode renderer() { result = DataFlow::moduleMember("electron", "ipcRenderer") }
12488

12589
/**
126-
* An IPC message.
90+
* A model for the Main and Renderer process in an Electron app.
12791
*/
128-
abstract class Message extends DataFlow::Node {
129-
/** Gets the process that sends this message. */
130-
abstract Process getProcess();
131-
132-
/** Gets the name of the channel this message is sent on. */
133-
abstract string getChannelName();
92+
abstract class Process extends EventEmitter::Range {
93+
/**
94+
* Gets a node that refers to a Process object.
95+
*/
96+
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }
13497
}
13598

13699
/**
137-
* An IPC message sent directly from a process.
100+
* An instance of the Main process of an Electron app.
101+
* Communication in an Electron app generally happens from the renderer process to the main process.
138102
*/
139-
class DirectMessage extends Message {
140-
DataFlow::MethodCallNode mc;
141-
Process process;
142-
DataFlow::Node channel;
143-
boolean isSync;
144-
145-
DirectMessage() {
146-
exists(string send |
147-
send = "send" and isSync = false
148-
or
149-
send = "sendSync" and isSync = true
150-
|
151-
mc = process.getAnImport().getAMemberCall(send) and
152-
this = mc.getArgument(1) and
153-
channel = mc.getArgument(0)
154-
)
155-
}
156-
157-
override Process getProcess() { result = process }
158-
159-
override string getChannelName() { result = channel.getStringValue() }
103+
class MainProcess extends Process {
104+
MainProcess() { this = main() }
160105
}
161106

162107
/**
163-
* A synchronous IPC message sent directly from a process.
108+
* An instance of the renderer process of an Electron app.
164109
*/
165-
class SyncDirectMessage extends DirectMessage {
166-
SyncDirectMessage() { isSync = true }
167-
168-
/** Gets the data flow node holding the reply to the message. */
169-
DataFlow::Node getReply() { result = mc }
110+
class RendererProcess extends Process {
111+
RendererProcess() { this = renderer() }
170112
}
171113

172114
/**
173-
* An asynchronous IPC reply sent from within an IPC callback.
115+
* The `sender` property of the event in an IPC event handler.
116+
* This sender is used to send a response back from the main process to the renderer.
174117
*/
175-
class AsyncReplyMessage extends Message {
176-
Callback callback;
177-
DataFlow::Node channel;
178-
179-
AsyncReplyMessage() {
180-
exists(DataFlow::MethodCallNode mc |
181-
mc = callback.getParameter(0).getAPropertyRead("sender").getAMemberCall("send") and
182-
this = mc.getArgument(1) and
183-
channel = mc.getArgument(0)
118+
class ProcessSender extends Process {
119+
ProcessSender() {
120+
exists(IPCSendRegistration reg | reg.getEmitter() instanceof MainProcess |
121+
this = reg.getABoundCallbackParameter(1, 0).getAPropertyRead("sender")
184122
)
185123
}
186-
187-
override Process getProcess() { result = callback.getProcess() }
188-
189-
override string getChannelName() { result = channel.getStringValue() }
190124
}
191125

192126
/**
193-
* A synchronous IPC reply sent from within an IPC callback.
127+
* A registration of an Electron IPC event handler.
128+
* Does mostly the same as an EventEmitter event handler,
129+
* except that values can be returned through the `event.returnValue` property.
194130
*/
195-
class SyncReplyMessage extends Message {
196-
Callback callback;
131+
class IPCSendRegistration extends EventRegistration::DefaultEventRegistration,
132+
DataFlow::MethodCallNode {
133+
override Process emitter;
197134

198-
SyncReplyMessage() {
199-
this = callback.getParameter(0).getAPropertyWrite("returnValue").getRhs()
200-
}
135+
IPCSendRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) }
201136

202-
override Process getProcess() { result = callback.getProcess() }
137+
override DataFlow::Node getAReturnedValue() {
138+
result = this.getABoundCallbackParameter(1, 0).getAPropertyWrite("returnValue").getRhs()
139+
}
203140

204-
override string getChannelName() { result = callback.getChannelName() }
141+
override IPCDispatch getAReturnDispatch() {
142+
result.getCalleeName() = "sendSync"
143+
}
205144
}
206145

207146
/**
208-
* An asynchronous Electron IPC message sent from the main process via a `webContents` object.
147+
* A dispatch of an IPC event.
148+
* An IPC event is sent from the renderer to the main process.
149+
* And a value can be returned through the `returnValue` property of the event (first parameter in the callback).
209150
*/
210-
class WebContentsMessage extends Message {
211-
DataFlow::Node channel;
212-
213-
WebContentsMessage() {
214-
exists(WebContents wc, DataFlow::MethodCallNode mc |
215-
wc.flowsTo(mc.getReceiver()) and
216-
this = mc.getArgument(1) and
217-
channel = mc.getArgument(0) and
218-
mc.getCalleeName() = "send"
151+
class IPCDispatch extends EventDispatch::DefaultEventDispatch, DataFlow::InvokeNode {
152+
override Process emitter;
153+
154+
IPCDispatch() {
155+
exists(string methodName | methodName = "sendSync" or methodName = "send" |
156+
this = emitter.ref().getAMemberCall(methodName)
219157
)
220158
}
221159

222-
override Process getProcess() { result = Process::main() }
223-
224-
override string getChannelName() { result = channel.getStringValue() }
225-
}
226-
227-
/**
228-
* Holds if `pred` flows to `succ` via Electron IPC.
229-
*/
230-
private predicate ipcFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
231-
// match a message sent from one process with a callback parameter in the other process
232-
exists(Callback callback, Message msg |
233-
callback.getChannelName() = msg.getChannelName() and
234-
callback.getProcess() != msg.getProcess() and
235-
pred = msg and
236-
succ = callback.getMessage()
237-
)
238-
or
239-
// match a synchronous reply sent from one process with a `sendSync` call in the other process
240-
exists(SyncDirectMessage sendSync, SyncReplyMessage msg |
241-
sendSync.getChannelName() = msg.getChannelName() and
242-
sendSync.getProcess() != msg.getProcess() and
243-
pred = msg and
244-
succ = sendSync.getReply()
245-
)
246-
}
247-
248-
/**
249-
* An additional flow step via an Electron IPC message.
250-
*/
251-
private class IPCAdditionalFlowStep extends DataFlow::AdditionalFlowStep {
252-
IPCAdditionalFlowStep() { ipcFlowStep(this, _) }
160+
/**
161+
* Gets the `i`th dispatched argument to the event handler.
162+
* The 0th parameter in the callback is a event generated by the IPC system,
163+
* therefore these arguments start at 1.
164+
*/
165+
override DataFlow::Node getSentItem(int i) {
166+
i >= 1 and
167+
result = getArgument(i)
168+
}
253169

254-
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
255-
pred = this and
256-
ipcFlowStep(pred, succ)
170+
/**
171+
* Gets a registration that this dispatch can send an event to.
172+
*/
173+
override IPCSendRegistration getAReceiver() {
174+
this.getEmitter() instanceof RendererProcess and
175+
result.getEmitter() instanceof MainProcess
176+
or
177+
this.getEmitter() instanceof ProcessSender and
178+
result.getEmitter() instanceof RendererProcess
257179
}
258180
}
259181
}

0 commit comments

Comments
 (0)