mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
fix: restrict sendToFrame to same-process frames by default (#26928)
* fix: restrict sendToFrame to same-process frames by default (#26875) * missed a conflict * fix build * fix build again
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1570,7 +1570,7 @@ app.whenReady().then(() => {
|
||||
|
||||
#### `contents.sendToFrame(frameId, channel, ...args)`
|
||||
|
||||
* `frameId` Integer
|
||||
* `frameId` Integer | [number, number]
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
|
||||
@@ -166,29 +166,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.
|
||||
@@ -437,8 +437,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
};
|
||||
|
||||
const addReplyToEvent = (event) => {
|
||||
const { processId, frameId } = event;
|
||||
event.reply = (...args) => {
|
||||
event.sender.sendToFrame(event.frameId, ...args);
|
||||
event.sender.sendToFrame([processId, frameId], ...args);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -275,7 +275,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':
|
||||
@@ -329,7 +329,7 @@ const unwrapArgs = function (sender: electron.WebContents, frameId: number, cont
|
||||
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location);
|
||||
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length });
|
||||
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender);
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId[0], frameId[1], contextId, meta.id, sender);
|
||||
rendererFunctions.set(objectId, callIntoRenderer);
|
||||
return callIntoRenderer;
|
||||
}
|
||||
@@ -478,7 +478,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) {
|
||||
@@ -489,7 +489,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) {
|
||||
@@ -506,7 +506,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) {
|
||||
@@ -517,7 +517,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) {
|
||||
@@ -534,7 +534,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) {
|
||||
|
||||
@@ -2320,7 +2320,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) {
|
||||
blink::CloneableMessage message;
|
||||
@@ -2329,17 +2329,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;
|
||||
|
||||
@@ -275,7 +275,7 @@ class WebContents : public gin_helper::TrackableObject<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);
|
||||
|
||||
|
||||
@@ -17,22 +17,25 @@ namespace electron {
|
||||
// static
|
||||
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int process_id,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents) {
|
||||
new RemoteCallbackFreer(isolate, target, frame_id, context_id, object_id,
|
||||
web_contents);
|
||||
new RemoteCallbackFreer(isolate, target, process_id, frame_id, context_id,
|
||||
object_id, web_contents);
|
||||
}
|
||||
|
||||
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int process_id,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
content::WebContentsObserver(web_contents),
|
||||
process_id_(process_id),
|
||||
frame_id_(frame_id),
|
||||
context_id_(context_id),
|
||||
object_id_(object_id) {}
|
||||
@@ -40,16 +43,14 @@ RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
RemoteCallbackFreer::~RemoteCallbackFreer() = default;
|
||||
|
||||
void RemoteCallbackFreer::RunDestructor() {
|
||||
auto frames = web_contents()->GetAllFrames();
|
||||
auto iter = std::find_if(frames.begin(), frames.end(), [this](auto* f) {
|
||||
return f->GetRoutingID() == frame_id_;
|
||||
});
|
||||
auto* rfh = content::RenderFrameHost::FromID(process_id_, frame_id_);
|
||||
if (!rfh || !rfh->IsRenderFrameLive() ||
|
||||
content::WebContents::FromRenderFrameHost(rfh) != web_contents())
|
||||
return;
|
||||
|
||||
if (iter != frames.end() && (*iter)->IsRenderFrameLive()) {
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->DereferenceRemoteJSCallback(context_id_, object_id_);
|
||||
}
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->DereferenceRemoteJSCallback(context_id_, object_id_);
|
||||
|
||||
Observe(nullptr);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int process_id,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
@@ -25,6 +26,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
protected:
|
||||
RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int process_id,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
@@ -37,6 +39,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
void RenderViewDeleted(content::RenderViewHost*) override;
|
||||
|
||||
private:
|
||||
int process_id_;
|
||||
int frame_id_;
|
||||
std::string context_id_;
|
||||
int object_id_;
|
||||
|
||||
@@ -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"
|
||||
@@ -82,8 +83,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ describe('ipc main module', () => {
|
||||
});
|
||||
|
||||
describe('ipcMain.on', () => {
|
||||
afterEach(() => { ipcMain.removeAllListeners('test-echo'); });
|
||||
|
||||
it('is not used for internals', async () => {
|
||||
const appPath = path.join(fixtures, 'api', 'ipc-main-listeners');
|
||||
const electronPath = process.execPath;
|
||||
@@ -59,5 +61,27 @@ 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);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
2
typings/internal-ambient.d.ts
vendored
2
typings/internal-ambient.d.ts
vendored
@@ -40,7 +40,7 @@ declare namespace NodeJS {
|
||||
requestGarbageCollectionForTesting(): void;
|
||||
createIDWeakMap<V>(): ElectronInternal.KeyWeakMap<number, V>;
|
||||
createDoubleIDWeakMap<V>(): ElectronInternal.KeyWeakMap<[string, number], V>;
|
||||
setRemoteCallbackFreer(fn: Function, frameId: number, contextId: String, id: number, sender: any): void
|
||||
setRemoteCallbackFreer(fn: Function, processId: number, frameId: number, contextId: String, id: number, sender: any): void
|
||||
weaklyTrackValue(value: any): void;
|
||||
clearWeaklyTrackedValues(): void;
|
||||
getWeaklyTrackedValues(): any[];
|
||||
|
||||
5
typings/internal-electron.d.ts
vendored
5
typings/internal-electron.d.ts
vendored
@@ -24,6 +24,11 @@ declare namespace Electron {
|
||||
getOwnerBrowserWindow(): Electron.BrowserWindow;
|
||||
}
|
||||
|
||||
interface WebPreferences {
|
||||
guestInstanceId?: number;
|
||||
openerId?: number;
|
||||
}
|
||||
|
||||
interface SerializedError {
|
||||
message: string;
|
||||
stack?: string,
|
||||
|
||||
Reference in New Issue
Block a user