Skip to content

Commit e7c95ba

Browse files
author
Max Schaefer
committed
JavaScript: Add flow steps modelling Electron IPC.
1 parent a4e4957 commit e7c95ba

File tree

6 files changed

+231
-7
lines changed

6 files changed

+231
-7
lines changed

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

Lines changed: 191 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ module Electron {
5050
*/
5151
private class BrowserObjectByType extends BrowserObject {
5252
BrowserObjectByType() {
53-
exists (string tp | tp = "BrowserWindow" or tp = "BrowserView" |
53+
exists(string tp | tp = "BrowserWindow" or tp = "BrowserView" |
5454
asExpr().getType().hasUnderlyingType("electron", tp)
5555
)
5656
}
@@ -60,17 +60,202 @@ module Electron {
6060
* A data flow node whose value may originate from a browser object instantiation.
6161
*/
6262
private class BrowserObjectByFlow extends BrowserObject {
63-
BrowserObjectByFlow() {
64-
any(NewBrowserObject nbo).flowsTo(this)
65-
}
63+
BrowserObjectByFlow() { any(NewBrowserObject nbo).flowsTo(this) }
6664
}
6765

6866
/**
6967
* A reference to the `webContents` property of a browser object.
7068
*/
7169
class WebContents extends DataFlow::SourceNode {
72-
WebContents() {
73-
this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents")
70+
WebContents() { this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents") }
71+
}
72+
73+
/**
74+
* Provides classes and predicates for modelling Electron IPC.
75+
*/
76+
private module IPC {
77+
class Process extends string {
78+
Process() { this = "main" or this = "renderer" }
79+
80+
DataFlow::SourceNode getAnImport() {
81+
this = Process::main() and result = DataFlow::moduleMember("electron", "ipcMain")
82+
or
83+
this = Process::renderer() and result = DataFlow::moduleMember("electron", "ipcRenderer")
84+
}
85+
}
86+
87+
module Process {
88+
Process main() { result = "main" }
89+
90+
Process renderer() { result = "renderer" }
91+
}
92+
93+
/**
94+
* An IPC callback.
95+
*/
96+
class Callback extends DataFlow::FunctionNode {
97+
DataFlow::Node channel;
98+
99+
Process process;
100+
101+
Callback() {
102+
exists(DataFlow::MethodCallNode mc |
103+
mc = process.getAnImport().getAMemberCall("on") and
104+
this = mc.getCallback(1) and
105+
channel = mc.getArgument(0)
106+
)
107+
}
108+
109+
/** Gets the process on which this callback is executed. */
110+
Process getProcess() { result = process }
111+
112+
/** Gets the name of the channel the callback is listening on. */
113+
string getChannelName() { result = channel.asExpr().getStringValue() }
114+
115+
/** Gets the data flow node containing the message received by the callback. */
116+
DataFlow::Node getMessage() {
117+
result = getParameter(1)
118+
}
119+
}
120+
121+
/**
122+
* An IPC message.
123+
*/
124+
abstract class Message extends DataFlow::Node {
125+
/** Gets the process that sends this message. */
126+
abstract Process getProcess();
127+
128+
/** Gets the name of the channel this message is sent on. */
129+
abstract string getChannelName();
130+
}
131+
132+
/**
133+
* An IPC message sent directly from a process.
134+
*/
135+
class DirectMessage extends Message {
136+
DataFlow::MethodCallNode mc;
137+
138+
Process process;
139+
140+
DataFlow::Node channel;
141+
142+
boolean isSync;
143+
144+
DirectMessage() {
145+
exists(string send |
146+
send = "send" and isSync = false
147+
or
148+
send = "sendSync" and isSync = true
149+
|
150+
mc = process.getAnImport().getAMemberCall(send) and
151+
this = mc.getArgument(1) and
152+
channel = mc.getArgument(0)
153+
)
154+
}
155+
156+
override Process getProcess() { result = process }
157+
158+
override string getChannelName() { result = channel.asExpr().getStringValue() }
159+
}
160+
161+
/**
162+
* A synchronous IPC message sent directly from a process.
163+
*/
164+
class SyncDirectMessage extends DirectMessage {
165+
SyncDirectMessage() { isSync = true }
166+
167+
/** Gets the data flow node holding the reply to the message. */
168+
DataFlow::Node getReply() {
169+
result = mc
170+
}
171+
}
172+
173+
/**
174+
* An asynchronous IPC reply sent from within an IPC callback.
175+
*/
176+
class AsyncReplyMessage extends Message {
177+
Callback callback;
178+
179+
DataFlow::Node channel;
180+
181+
AsyncReplyMessage() {
182+
exists(DataFlow::MethodCallNode mc |
183+
mc = callback.getParameter(0).getAPropertyRead("sender").getAMemberCall("send") and
184+
this = mc.getArgument(1) and
185+
channel = mc.getArgument(0)
186+
)
187+
}
188+
189+
override Process getProcess() { result = callback.getProcess() }
190+
191+
override string getChannelName() { result = channel.asExpr().getStringValue() }
192+
}
193+
194+
/**
195+
* A synchronous IPC reply sent from within an IPC callback.
196+
*/
197+
class SyncReplyMessage extends Message {
198+
Callback callback;
199+
200+
SyncReplyMessage() {
201+
this = callback.getParameter(0).getAPropertyWrite("returnValue").getRhs()
202+
}
203+
204+
override Process getProcess() { result = callback.getProcess() }
205+
206+
override string getChannelName() { result = callback.getChannelName() }
207+
}
208+
209+
/**
210+
* An asynchronous Electron IPC message sent from the main process via a `webContents` object.
211+
*/
212+
class WebContentsMessage extends Message {
213+
DataFlow::Node channel;
214+
215+
WebContentsMessage() {
216+
exists(WebContents wc, DataFlow::MethodCallNode mc |
217+
wc.flowsTo(mc.getReceiver()) and
218+
this = mc.getArgument(1) and
219+
channel = mc.getArgument(0) and
220+
mc.getCalleeName() = "send"
221+
)
222+
}
223+
224+
override Process getProcess() { result = Process::main() }
225+
226+
override string getChannelName() { result = channel.asExpr().getStringValue() }
227+
}
228+
229+
/**
230+
* Holds if `pred` flows to `succ` via Electron IPC.
231+
*/
232+
private predicate ipcFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
233+
// match a message sent from one process with a callback parameter in the other process
234+
exists(Callback callback, Message msg |
235+
callback.getChannelName() = msg.getChannelName() and
236+
callback.getProcess() != msg.getProcess() and
237+
pred = msg and
238+
succ = callback.getMessage()
239+
)
240+
or
241+
// match a synchronous reply sent from one process with a `sendSync` call in the other process
242+
exists(SyncDirectMessage sendSync, SyncReplyMessage msg |
243+
sendSync.getChannelName() = msg.getChannelName() and
244+
sendSync.getProcess() != msg.getProcess() and
245+
pred = msg and
246+
succ = sendSync.getReply()
247+
)
248+
}
249+
250+
/**
251+
* An additional flow step via an Electron IPC message.
252+
*/
253+
private class IPCAdditionalFlowStep extends DataFlow::Configuration {
254+
IPCAdditionalFlowStep() { this instanceof DataFlow::Configuration }
255+
256+
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
257+
ipcFlowStep(pred, succ)
258+
}
74259
}
75260
}
76261

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| electron.js:43:30:43:35 | 'pong' | electron.js:52:33:52:35 | arg |
2+
| electron.js:48:23:48:28 | 'pong' | electron.js:58:1:58:36 | ipcRend ... 'ping') |
3+
| electron.js:56:27:56:32 | 'ping' | electron.js:42:29:42:31 | arg |
4+
| electron.js:58:30:58:35 | 'ping' | electron.js:47:28:47:30 | arg |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import javascript
2+
3+
class TestConfig extends DataFlow::Configuration {
4+
TestConfig() { this = "TestConfig" }
5+
}
6+
7+
from TestConfig cfg, DataFlow::Node pred, DataFlow::Node succ
8+
where cfg.isAdditionalFlowStep(pred, succ)
9+
select pred, succ
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| electron.js:39:1:39:19 | foo(bw).webContents |
2+
| electron.js:40:1:40:19 | foo(bv).webContents |
3+
| electron.ts:4:3:4:16 | bw.webContents |
4+
| electron.ts:5:3:5:16 | bv.webContents |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import javascript
2+
3+
from Electron::WebContents wc
4+
select wc

javascript/ql/test/library-tests/frameworks/Electron/electron.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const {BrowserView, BrowserWindow, ClientRequest, net} = require('electron')
1+
const { ipcMain, ipcRenderer, BrowserView, BrowserWindow, ClientRequest, net } = require('electron')
22

33
var bw = new BrowserWindow({webPreferences: {}})
44
var bv = new BrowserView({webPreferences: {}})
@@ -38,3 +38,21 @@ function foo(x) {
3838

3939
foo(bw).webContents;
4040
foo(bv).webContents;
41+
42+
ipcMain.on('async', (event, arg) => {
43+
event.sender.send('reply', 'pong');
44+
arg
45+
});
46+
47+
ipcMain.on('sync', (event, arg) => {
48+
event.returnValue = 'pong';
49+
arg
50+
});
51+
52+
ipcRenderer.on('reply', (event, arg) => {
53+
arg
54+
});
55+
56+
ipcRenderer.send('async', 'ping');
57+
58+
ipcRenderer.sendSync('sync', 'ping');

0 commit comments

Comments
 (0)