mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
Merge pull request #936 from xiemaisi/js/revive-electron-support
Approved by esben-semmle
This commit is contained in:
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Electron::BrowserObject obj
|
||||
select obj
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Electron::WebContents wc
|
||||
select wc
|
||||
@@ -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 | {} |
|
||||
|
||||
8
javascript/ql/test/library-tests/frameworks/Electron/electron.d.ts
vendored
Normal file
8
javascript/ql/test/library-tests/frameworks/Electron/electron.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare namespace Electron {
|
||||
export class BrowserWindow { }
|
||||
export class BrowserView { }
|
||||
}
|
||||
|
||||
declare module 'electron' {
|
||||
export = Electron;
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
///<reference path="./electron.d.ts"/>
|
||||
|
||||
function f(bw: Electron.BrowserWindow, bv: Electron.BrowserView) {
|
||||
bw.webContents;
|
||||
bv.webContents;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"include": ["."]
|
||||
}
|
||||
Reference in New Issue
Block a user