fix: restrict sendToFrame to same-process frames by default (#26875) (#26926)

This commit is contained in:
Jeremy Rose
2020-12-11 13:01:31 -08:00
committed by GitHub
parent ca7528a2d0
commit 429400040e
12 changed files with 89 additions and 36 deletions

View File

@@ -1,5 +1,6 @@
# IpcMainEvent Object extends `Event`
* `processId` Integer - The internal ID of the renderer process that sent this message
* `frameId` Integer - The ID of the renderer frame that sent this message
* `returnValue` any - Set this to the value to be returned in a synchronous message
* `sender` WebContents - Returns the `webContents` that sent the message

View File

@@ -1,4 +1,5 @@
# IpcMainInvokeEvent Object extends `Event`
* `processId` Integer - The internal ID of the renderer process that sent this message
* `frameId` Integer - The ID of the renderer frame that sent this message
* `sender` WebContents - Returns the `webContents` that sent the message

View File

@@ -1622,7 +1622,7 @@ app.whenReady().then(() => {
#### `contents.sendToFrame(frameId, channel, ...args)`
* `frameId` Integer
* `frameId` Integer | [number, number]
* `channel` String
* `...args` any[]

View File

@@ -161,29 +161,29 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) {
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
WebContents.prototype.sendToFrame = function (frame, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument');
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
throw new Error('Missing required frame argument (must be number or array)');
}
const internal = false;
const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
return this._sendToFrame(internal, sendToAll, frame, channel, args);
};
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument');
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
throw new Error('Missing required frame argument (must be number or array)');
}
const internal = true;
const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
return this._sendToFrame(internal, sendToAll, frame, channel, args);
};
// Following methods are mapped to webFrame.
@@ -439,8 +439,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
};
const addReplyToEvent = (event: any) => {
const { processId, frameId } = event;
event.reply = (...args: any[]) => {
event.sender.sendToFrame(event.frameId, ...args);
event.sender.sendToFrame([processId, frameId], ...args);
};
};

View File

@@ -19,7 +19,7 @@ const FUNCTION_PROPERTIES = [
];
type RendererFunctionId = [string, number] // [contextId, funcId]
type FinalizerInfo = { id: RendererFunctionId, webContents: electron.WebContents, frameId: number };
type FinalizerInfo = { id: RendererFunctionId, webContents: electron.WebContents, frameId: [number, number] };
type WeakRef<T> = { deref(): T | undefined }
type CallIntoRenderer = (...args: any[]) => void
@@ -43,7 +43,7 @@ function getCachedRendererFunction (id: RendererFunctionId): CallIntoRenderer |
if (deref !== undefined) return deref;
}
}
function setCachedRendererFunction (id: RendererFunctionId, wc: electron.WebContents, frameId: number, value: CallIntoRenderer) {
function setCachedRendererFunction (id: RendererFunctionId, wc: electron.WebContents, frameId: [number, number], value: CallIntoRenderer) {
// eslint-disable-next-line no-undef
const wr = new (globalThis as any).WeakRef(value) as WeakRef<CallIntoRenderer>;
const mapKey = id[0] + '~' + id[1];
@@ -218,7 +218,7 @@ const fakeConstructor = (constructor: Function, name: string) =>
});
// Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
const unwrapArgs = function (sender: electron.WebContents, frameId: [number, number], contextId: string, args: any[]) {
const metaToValue = function (meta: MetaTypeFromRenderer): any {
switch (meta.type) {
case 'nativeimage':
@@ -421,7 +421,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, co
});
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args);
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
const constructor = objectsRegistry.get(id);
if (constructor == null) {
@@ -432,7 +432,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId,
});
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args);
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
const func = objectsRegistry.get(id);
if (func == null) {
@@ -449,7 +449,7 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId
});
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args);
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
const object = objectsRegistry.get(id);
if (object == null) {
@@ -460,7 +460,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, cont
});
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args);
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
const object = objectsRegistry.get(id);
if (object == null) {
@@ -477,7 +477,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId,
});
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args);
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
const obj = objectsRegistry.get(id);
if (obj == null) {

View File

@@ -2423,7 +2423,7 @@ bool WebContents::SendIPCMessageWithSender(bool internal,
bool WebContents::SendIPCMessageToFrame(bool internal,
bool send_to_all,
int32_t frame_id,
v8::Local<v8::Value> frame,
const std::string& channel,
v8::Local<v8::Value> args) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
@@ -2433,17 +2433,30 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
gin::StringToV8(isolate, "Failed to serialize arguments")));
return false;
}
auto frames = web_contents()->GetAllFrames();
auto iter = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) {
return f->GetRoutingID() == frame_id;
});
if (iter == frames.end())
return false;
if (!(*iter)->IsRenderFrameLive())
int32_t frame_id;
int32_t process_id;
if (gin::ConvertFromV8(isolate, frame, &frame_id)) {
process_id = web_contents()->GetMainFrame()->GetProcess()->GetID();
} else {
std::vector<int32_t> id_pair;
if (gin::ConvertFromV8(isolate, frame, &id_pair) && id_pair.size() == 2) {
process_id = id_pair[0];
frame_id = id_pair[1];
} else {
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate,
"frameId must be a number or a pair of [processId, frameId]")));
return false;
}
}
auto* rfh = content::RenderFrameHost::FromID(process_id, frame_id);
if (!rfh || !rfh->IsRenderFrameLive() ||
content::WebContents::FromRenderFrameHost(rfh) != web_contents())
return false;
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
electron_renderer->Message(internal, send_to_all, channel, std::move(message),
0 /* sender_id */);
return true;

View File

@@ -294,7 +294,7 @@ class WebContents : public gin::Wrappable<WebContents>,
bool SendIPCMessageToFrame(bool internal,
bool send_to_all,
int32_t frame_id,
v8::Local<v8::Value> frame,
const std::string& channel,
v8::Local<v8::Value> args);

View File

@@ -5,6 +5,7 @@
#include "shell/common/gin_helper/event_emitter.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "shell/browser/api/event.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
@@ -67,8 +68,10 @@ v8::Local<v8::Object> CreateNativeEvent(
Dictionary dict(isolate, event);
dict.Set("sender", sender);
// Should always set frameId even when callback is null.
if (frame)
if (frame) {
dict.Set("frameId", frame->GetRoutingID());
dict.Set("processId", frame->GetProcess()->GetID());
}
return event;
}

View File

@@ -3,6 +3,7 @@ import * as path from 'path';
import * as cp from 'child_process';
import { closeAllWindows } from './window-helpers';
import { emittedOnce } from './events-helpers';
import { defer } from './spec-helpers';
import { ipcMain, BrowserWindow } from 'electron/main';
describe('ipc main module', () => {
@@ -59,5 +60,30 @@ describe('ipc main module', () => {
output = JSON.parse(output);
expect(output).to.deep.equal(['error']);
});
it('can be replied to', async () => {
ipcMain.on('test-echo', (e, arg) => {
e.reply('test-echo', arg);
});
defer(() => {
ipcMain.removeAllListeners('test-echo');
});
const w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
});
w.loadURL('about:blank');
const v = await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const { ipcRenderer } = require('electron')
ipcRenderer.send('test-echo', 'hello')
ipcRenderer.on('test-echo', (e, v) => {
resolve(v)
})
})`);
expect(v).to.equal('hello');
});
});
});

View File

@@ -3,7 +3,8 @@
"top=5,left=10,resizable=no",
{
"sender": "[WebContents]",
"frameId": 1
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame name",
@@ -36,7 +37,8 @@
"zoomFactor=2,resizable=0,x=0,y=10",
{
"sender": "[WebContents]",
"frameId": 1
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame name",
@@ -68,7 +70,8 @@
"backgroundColor=gray,webPreferences=0,x=100,y=100",
{
"sender": "[WebContents]",
"frameId": 1
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame name",
@@ -100,7 +103,8 @@
"x=50,y=20,title=sup",
{
"sender": "[WebContents]",
"frameId": 1
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame name",
@@ -130,7 +134,8 @@
"show=false,top=1,left=1",
{
"sender": "[WebContents]",
"frameId": 1
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame name",

View File

@@ -94,6 +94,9 @@ function stringifySnapshots (snapshots: any, pretty = false) {
if (key === 'openerId' && typeof value === 'number') {
return 'placeholder-opener-id';
}
if (key === 'processId' && typeof value === 'number') {
return 'placeholder-process-id';
}
if (key === 'returnValue') {
return 'placeholder-guest-contents-id';
}

View File

@@ -57,7 +57,7 @@ declare namespace Electron {
_getPreloadPaths(): string[];
equal(other: WebContents): boolean;
_initiallyShown: boolean;
_sendToFrameInternal(frameId: number, channel: string, ...args: any[]): boolean;
_sendToFrameInternal(frameId: number | [number, number], channel: string, ...args: any[]): boolean;
}
interface WebPreferences {
@@ -112,7 +112,7 @@ declare namespace Electron {
interface WebContentsInternal extends Electron.WebContents {
_send(internal: boolean, sendToAll: boolean, channel: string, args: any): boolean;
_sendToFrame(internal: boolean, sendToAll: boolean, frameId: number, channel: string, args: any): boolean;
_sendToFrame(internal: boolean, sendToAll: boolean, frameId: number | [number, number], channel: string, args: any): boolean;
_postMessage(channel: string, message: any, transfer?: any[]): void;
_sendInternal(channel: string, ...args: any[]): void;
_sendInternalToAll(channel: string, ...args: any[]): void;