JavaScript: Add flow steps modelling Electron IPC.

This commit is contained in:
Max Schaefer
2019-02-07 15:08:52 +00:00
parent a4e4957f31
commit e7c95bae49
6 changed files with 231 additions and 7 deletions

View File

@@ -50,7 +50,7 @@ module Electron {
*/
private class BrowserObjectByType extends BrowserObject {
BrowserObjectByType() {
exists (string tp | tp = "BrowserWindow" or tp = "BrowserView" |
exists(string tp | tp = "BrowserWindow" or tp = "BrowserView" |
asExpr().getType().hasUnderlyingType("electron", tp)
)
}
@@ -60,17 +60,202 @@ module Electron {
* A data flow node whose value may originate from a browser object instantiation.
*/
private class BrowserObjectByFlow extends BrowserObject {
BrowserObjectByFlow() {
any(NewBrowserObject nbo).flowsTo(this)
}
BrowserObjectByFlow() { any(NewBrowserObject nbo).flowsTo(this) }
}
/**
* A reference to the `webContents` property of a browser object.
*/
class WebContents extends DataFlow::SourceNode {
WebContents() {
this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents")
WebContents() { this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents") }
}
/**
* Provides classes and predicates for modelling Electron IPC.
*/
private module IPC {
class Process extends string {
Process() { this = "main" or this = "renderer" }
DataFlow::SourceNode getAnImport() {
this = Process::main() and result = DataFlow::moduleMember("electron", "ipcMain")
or
this = Process::renderer() and result = DataFlow::moduleMember("electron", "ipcRenderer")
}
}
module Process {
Process main() { result = "main" }
Process renderer() { result = "renderer" }
}
/**
* An IPC callback.
*/
class Callback extends DataFlow::FunctionNode {
DataFlow::Node channel;
Process process;
Callback() {
exists(DataFlow::MethodCallNode mc |
mc = process.getAnImport().getAMemberCall("on") and
this = mc.getCallback(1) and
channel = mc.getArgument(0)
)
}
/** Gets the process on which this callback is executed. */
Process getProcess() { result = process }
/** Gets the name of the channel the callback is listening on. */
string getChannelName() { result = channel.asExpr().getStringValue() }
/** Gets the data flow node containing the message received by the callback. */
DataFlow::Node getMessage() {
result = getParameter(1)
}
}
/**
* An IPC message.
*/
abstract class Message extends DataFlow::Node {
/** Gets the process that sends this message. */
abstract Process getProcess();
/** Gets the name of the channel this message is sent on. */
abstract string getChannelName();
}
/**
* An IPC message sent directly from a process.
*/
class DirectMessage extends Message {
DataFlow::MethodCallNode mc;
Process process;
DataFlow::Node channel;
boolean isSync;
DirectMessage() {
exists(string send |
send = "send" and isSync = false
or
send = "sendSync" and isSync = true
|
mc = process.getAnImport().getAMemberCall(send) and
this = mc.getArgument(1) and
channel = mc.getArgument(0)
)
}
override Process getProcess() { result = process }
override string getChannelName() { result = channel.asExpr().getStringValue() }
}
/**
* A synchronous IPC message sent directly from a process.
*/
class SyncDirectMessage extends DirectMessage {
SyncDirectMessage() { isSync = true }
/** Gets the data flow node holding the reply to the message. */
DataFlow::Node getReply() {
result = mc
}
}
/**
* An asynchronous IPC reply sent from within an IPC callback.
*/
class AsyncReplyMessage extends Message {
Callback callback;
DataFlow::Node channel;
AsyncReplyMessage() {
exists(DataFlow::MethodCallNode mc |
mc = callback.getParameter(0).getAPropertyRead("sender").getAMemberCall("send") and
this = mc.getArgument(1) and
channel = mc.getArgument(0)
)
}
override Process getProcess() { result = callback.getProcess() }
override string getChannelName() { result = channel.asExpr().getStringValue() }
}
/**
* A synchronous IPC reply sent from within an IPC callback.
*/
class SyncReplyMessage extends Message {
Callback callback;
SyncReplyMessage() {
this = callback.getParameter(0).getAPropertyWrite("returnValue").getRhs()
}
override Process getProcess() { result = callback.getProcess() }
override string getChannelName() { result = callback.getChannelName() }
}
/**
* An asynchronous Electron IPC message sent from the main process via a `webContents` object.
*/
class WebContentsMessage extends Message {
DataFlow::Node channel;
WebContentsMessage() {
exists(WebContents wc, DataFlow::MethodCallNode mc |
wc.flowsTo(mc.getReceiver()) and
this = mc.getArgument(1) and
channel = mc.getArgument(0) and
mc.getCalleeName() = "send"
)
}
override Process getProcess() { result = Process::main() }
override string getChannelName() { result = channel.asExpr().getStringValue() }
}
/**
* Holds if `pred` flows to `succ` via Electron IPC.
*/
private predicate ipcFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
// match a message sent from one process with a callback parameter in the other process
exists(Callback callback, Message msg |
callback.getChannelName() = msg.getChannelName() and
callback.getProcess() != msg.getProcess() and
pred = msg and
succ = callback.getMessage()
)
or
// match a synchronous reply sent from one process with a `sendSync` call in the other process
exists(SyncDirectMessage sendSync, SyncReplyMessage msg |
sendSync.getChannelName() = msg.getChannelName() and
sendSync.getProcess() != msg.getProcess() and
pred = msg and
succ = sendSync.getReply()
)
}
/**
* An additional flow step via an Electron IPC message.
*/
private class IPCAdditionalFlowStep extends DataFlow::Configuration {
IPCAdditionalFlowStep() { this instanceof DataFlow::Configuration }
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
ipcFlowStep(pred, succ)
}
}
}

View File

@@ -0,0 +1,4 @@
| electron.js:43:30:43:35 | 'pong' | electron.js:52:33:52:35 | arg |
| electron.js:48:23:48:28 | 'pong' | electron.js:58:1:58:36 | ipcRend ... 'ping') |
| electron.js:56:27:56:32 | 'ping' | electron.js:42:29:42:31 | arg |
| electron.js:58:30:58:35 | 'ping' | electron.js:47:28:47:30 | arg |

View File

@@ -0,0 +1,9 @@
import javascript
class TestConfig extends DataFlow::Configuration {
TestConfig() { this = "TestConfig" }
}
from TestConfig cfg, DataFlow::Node pred, DataFlow::Node succ
where cfg.isAdditionalFlowStep(pred, succ)
select pred, succ

View File

@@ -0,0 +1,4 @@
| electron.js:39:1:39:19 | foo(bw).webContents |
| electron.js:40:1:40:19 | foo(bv).webContents |
| electron.ts:4:3:4:16 | bw.webContents |
| electron.ts:5:3:5:16 | bv.webContents |

View File

@@ -0,0 +1,4 @@
import javascript
from Electron::WebContents wc
select wc

View File

@@ -1,4 +1,4 @@
const {BrowserView, BrowserWindow, ClientRequest, net} = require('electron')
const { ipcMain, ipcRenderer, BrowserView, BrowserWindow, ClientRequest, net } = require('electron')
var bw = new BrowserWindow({webPreferences: {}})
var bv = new BrowserView({webPreferences: {}})
@@ -38,3 +38,21 @@ function foo(x) {
foo(bw).webContents;
foo(bv).webContents;
ipcMain.on('async', (event, arg) => {
event.sender.send('reply', 'pong');
arg
});
ipcMain.on('sync', (event, arg) => {
event.returnValue = 'pong';
arg
});
ipcRenderer.on('reply', (event, arg) => {
arg
});
ipcRenderer.send('async', 'ping');
ipcRenderer.sendSync('sync', 'ping');