Merge pull request #936 from xiemaisi/js/revive-electron-support

Approved by esben-semmle
This commit is contained in:
semmle-qlci
2019-02-25 15:23:20 +00:00
committed by GitHub
13 changed files with 337 additions and 23 deletions

View File

@@ -2,35 +2,263 @@ import javascript
module Electron {
/**
* A data flow node that is an Electron `webPreferences` property.
* A `webPreferences` object.
*/
class WebPreferences extends DataFlow::ObjectLiteralNode {
WebPreferences() {
exists(BrowserObject bo | this = bo.getOptionArgument(0, "webPreferences").getALocalSource())
WebPreferences() { this = any(NewBrowserObject nbo).getWebPreferences() }
}
/**
* A data flow node that may contain a `BrowserWindow` or `BrowserView` object.
*/
abstract class BrowserObject extends DataFlow::Node { }
/**
* An instantiation of `BrowserWindow` or `BrowserView`.
*/
abstract private class NewBrowserObject extends BrowserObject, DataFlow::TrackedNode {
DataFlow::NewNode self;
NewBrowserObject() { this = self }
/**
* Gets the data flow node from which this instantiation takes its `webPreferences` object.
*/
DataFlow::SourceNode getWebPreferences() {
result = self.getOptionArgument(0, "webPreferences").getALocalSource()
}
}
/**
* A data flow node that creates a new `BrowserWindow` or `BrowserView`.
* An instantiation of `BrowserWindow`.
*/
abstract private class BrowserObject extends DataFlow::NewNode { }
/**
* A data flow node that creates a new `BrowserWindow`.
*/
class BrowserWindow extends BrowserObject {
class BrowserWindow extends NewBrowserObject {
BrowserWindow() {
this = DataFlow::moduleMember("electron", "BrowserWindow").getAnInstantiation()
}
}
/**
* A data flow node that creates a new `BrowserView`.
* An instantiation of `BrowserView`.
*/
class BrowserView extends BrowserObject {
class BrowserView extends NewBrowserObject {
BrowserView() { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() }
}
/**
* An expression of type `BrowserWindow` or `BrowserView`.
*/
private class BrowserObjectByType extends BrowserObject {
BrowserObjectByType() {
exists(string tp | tp = "BrowserWindow" or tp = "BrowserView" |
asExpr().getType().hasUnderlyingType("electron", tp)
)
}
}
/**
* A data flow node whose value may originate from a browser object instantiation.
*/
private class BrowserObjectByFlow extends BrowserObject {
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") }
}
/**
* Provides classes and predicates for modelling Electron inter-process communication (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)
}
}
}
/**
* A Node.js-style HTTP or HTTPS request made using an Electron module.
*/
@@ -68,7 +296,7 @@ module Electron {
override DataFlow::Node getADataNode() {
exists(string name | name = "write" or name = "end" |
result = this.(DataFlow::SourceNode).getAMethodCall(name).getArgument(0)
result = this.getAMethodCall(name).getArgument(0)
)
}
}

View File

@@ -0,0 +1,16 @@
| electron.d.ts:2:16:2:28 | BrowserWindow |
| electron.d.ts:3:16:3:26 | BrowserView |
| electron.js:3:5:3:48 | bw |
| electron.js:3:10:3:48 | new Bro ... s: {}}) |
| electron.js:4:5:4:46 | bv |
| electron.js:4:10:4:46 | new Bro ... s: {}}) |
| electron.js:35:14:35:14 | x |
| electron.js:36:12:36:12 | x |
| electron.js:39:1:39:7 | foo(bw) |
| electron.js:39:5:39:6 | bw |
| electron.js:40:1:40:7 | foo(bv) |
| electron.js:40:5:40:6 | bv |
| electron.ts:3:12:3:13 | bw |
| electron.ts:3:40:3:41 | bv |
| electron.ts:4:3:4:4 | bw |
| electron.ts:5:3:5:4 | bv |

View File

@@ -0,0 +1,4 @@
import javascript
from Electron::BrowserObject obj
select obj

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,2 +1,2 @@
| electron.js:3:36:3:37 | {} |
| electron.js:4:34:4:35 | {} |
| electron.js:3:45:3:46 | {} |
| electron.js:4:43:4:44 | {} |

View File

@@ -0,0 +1,8 @@
declare namespace Electron {
export class BrowserWindow { }
export class BrowserView { }
}
declare module 'electron' {
export = Electron;
}

View File

@@ -1,7 +1,7 @@
const {BrowserView, BrowserWindow, ClientRequest, net} = require('electron')
const { ipcMain, ipcRenderer, BrowserView, BrowserWindow, ClientRequest, net } = require('electron')
new BrowserWindow({webPreferences: {}})
new BrowserView({webPreferences: {}})
var bw = new BrowserWindow({webPreferences: {}})
var bv = new BrowserView({webPreferences: {}})
function makeClientRequests() {
net.request('https://example.com').end();
@@ -31,3 +31,28 @@ function makeClientRequests() {
post.write('stuff');
post.end('more stuff');
}
function foo(x) {
return 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');

View File

@@ -0,0 +1,6 @@
///<reference path="./electron.d.ts"/>
function f(bw: Electron.BrowserWindow, bv: Electron.BrowserView) {
bw.webContents;
bv.webContents;
}

View File

@@ -0,0 +1,3 @@
{
"include": ["."]
}