feat: sandbox renderer processes for cross-origin frames (#18650)
This commit is contained in:
parent
23286fe557
commit
f3f2990b9e
8 changed files with 139 additions and 10 deletions
|
@ -59,6 +59,11 @@ bool IsBrowserProcess(base::CommandLine* cmd) {
|
||||||
return process_type.empty();
|
return process_type.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSandboxEnabled(base::CommandLine* command_line) {
|
||||||
|
return command_line->HasSwitch(switches::kEnableSandbox) ||
|
||||||
|
!command_line->HasSwitch(service_manager::switches::kNoSandbox);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true if this subprocess type needs the ResourceBundle initialized
|
// Returns true if this subprocess type needs the ResourceBundle initialized
|
||||||
// and resources loaded.
|
// and resources loaded.
|
||||||
bool SubprocessNeedsResourceBundle(const std::string& process_type) {
|
bool SubprocessNeedsResourceBundle(const std::string& process_type) {
|
||||||
|
@ -281,10 +286,9 @@ content::ContentGpuClient* AtomMainDelegate::CreateContentGpuClient() {
|
||||||
|
|
||||||
content::ContentRendererClient*
|
content::ContentRendererClient*
|
||||||
AtomMainDelegate::CreateContentRendererClient() {
|
AtomMainDelegate::CreateContentRendererClient() {
|
||||||
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||||
switches::kEnableSandbox) ||
|
|
||||||
!base::CommandLine::ForCurrentProcess()->HasSwitch(
|
if (IsSandboxEnabled(command_line)) {
|
||||||
service_manager::switches::kNoSandbox)) {
|
|
||||||
renderer_client_.reset(new AtomSandboxedRendererClient);
|
renderer_client_.reset(new AtomSandboxedRendererClient);
|
||||||
} else {
|
} else {
|
||||||
renderer_client_.reset(new AtomRendererClient);
|
renderer_client_.reset(new AtomRendererClient);
|
||||||
|
|
|
@ -1213,6 +1213,18 @@ base::ProcessId WebContents::GetOSProcessID() const {
|
||||||
return base::GetProcId(process_handle);
|
return base::GetProcId(process_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base::ProcessId WebContents::GetOSProcessIdForFrame(
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& document_url) const {
|
||||||
|
for (auto* frame : web_contents()->GetAllFrames()) {
|
||||||
|
if (frame->GetFrameName() == name &&
|
||||||
|
frame->GetLastCommittedURL().spec() == document_url) {
|
||||||
|
return base::GetProcId(frame->GetProcess()->GetProcess().Handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base::kNullProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
WebContents::Type WebContents::GetType() const {
|
WebContents::Type WebContents::GetType() const {
|
||||||
return type_;
|
return type_;
|
||||||
}
|
}
|
||||||
|
@ -2194,6 +2206,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
|
||||||
&WebContents::SetBackgroundThrottling)
|
&WebContents::SetBackgroundThrottling)
|
||||||
.SetMethod("getProcessId", &WebContents::GetProcessID)
|
.SetMethod("getProcessId", &WebContents::GetProcessID)
|
||||||
.SetMethod("getOSProcessId", &WebContents::GetOSProcessID)
|
.SetMethod("getOSProcessId", &WebContents::GetOSProcessID)
|
||||||
|
.SetMethod("_getOSProcessIdForFrame",
|
||||||
|
&WebContents::GetOSProcessIdForFrame)
|
||||||
.SetMethod("equal", &WebContents::Equal)
|
.SetMethod("equal", &WebContents::Equal)
|
||||||
.SetMethod("_loadURL", &WebContents::LoadURL)
|
.SetMethod("_loadURL", &WebContents::LoadURL)
|
||||||
.SetMethod("downloadURL", &WebContents::DownloadURL)
|
.SetMethod("downloadURL", &WebContents::DownloadURL)
|
||||||
|
|
|
@ -138,6 +138,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||||
void SetBackgroundThrottling(bool allowed);
|
void SetBackgroundThrottling(bool allowed);
|
||||||
int GetProcessID() const;
|
int GetProcessID() const;
|
||||||
base::ProcessId GetOSProcessID() const;
|
base::ProcessId GetOSProcessID() const;
|
||||||
|
base::ProcessId GetOSProcessIdForFrame(const std::string& name,
|
||||||
|
const std::string& document_url) const;
|
||||||
Type GetType() const;
|
Type GetType() const;
|
||||||
bool Equal(const WebContents* web_contents) const;
|
bool Equal(const WebContents* web_contents) const;
|
||||||
void LoadURL(const GURL& url, const mate::Dictionary& options);
|
void LoadURL(const GURL& url, const mate::Dictionary& options);
|
||||||
|
|
|
@ -327,6 +327,10 @@ void AtomBrowserClient::ConsiderSiteInstanceForAffinity(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AtomBrowserClient::IsRendererSubFrame(int process_id) const {
|
||||||
|
return base::ContainsKey(renderer_is_subframe_, process_id);
|
||||||
|
}
|
||||||
|
|
||||||
void AtomBrowserClient::RenderProcessWillLaunch(
|
void AtomBrowserClient::RenderProcessWillLaunch(
|
||||||
content::RenderProcessHost* host,
|
content::RenderProcessHost* host,
|
||||||
service_manager::mojom::ServiceRequest* service_request) {
|
service_manager::mojom::ServiceRequest* service_request) {
|
||||||
|
@ -463,6 +467,11 @@ void AtomBrowserClient::RegisterPendingSiteInstance(
|
||||||
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||||
auto* pending_process = pending_site_instance->GetProcess();
|
auto* pending_process = pending_site_instance->GetProcess();
|
||||||
pending_processes_[pending_process->GetID()] = web_contents;
|
pending_processes_[pending_process->GetID()] = web_contents;
|
||||||
|
|
||||||
|
if (rfh->GetParent())
|
||||||
|
renderer_is_subframe_.insert(pending_process->GetID());
|
||||||
|
else
|
||||||
|
renderer_is_subframe_.erase(pending_process->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||||
|
@ -513,7 +522,8 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||||
}
|
}
|
||||||
auto* web_preferences = WebContentsPreferences::From(web_contents);
|
auto* web_preferences = WebContentsPreferences::From(web_contents);
|
||||||
if (web_preferences)
|
if (web_preferences)
|
||||||
web_preferences->AppendCommandLineSwitches(command_line);
|
web_preferences->AppendCommandLineSwitches(
|
||||||
|
command_line, IsRendererSubFrame(process_id));
|
||||||
SessionPreferences::AppendExtraCommandLineSwitches(
|
SessionPreferences::AppendExtraCommandLineSwitches(
|
||||||
web_contents->GetBrowserContext(), command_line);
|
web_contents->GetBrowserContext(), command_line);
|
||||||
if (CanUseCustomSiteInstance()) {
|
if (CanUseCustomSiteInstance()) {
|
||||||
|
@ -752,6 +762,7 @@ void AtomBrowserClient::RenderProcessHostDestroyed(
|
||||||
content::RenderProcessHost* host) {
|
content::RenderProcessHost* host) {
|
||||||
int process_id = host->GetID();
|
int process_id = host->GetID();
|
||||||
pending_processes_.erase(process_id);
|
pending_processes_.erase(process_id);
|
||||||
|
renderer_is_subframe_.erase(process_id);
|
||||||
RemoveProcessPreferences(process_id);
|
RemoveProcessPreferences(process_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -235,11 +235,15 @@ class AtomBrowserClient : public content::ContentBrowserClient,
|
||||||
void ConsiderSiteInstanceForAffinity(content::RenderFrameHost* rfh,
|
void ConsiderSiteInstanceForAffinity(content::RenderFrameHost* rfh,
|
||||||
content::SiteInstance* site_instance);
|
content::SiteInstance* site_instance);
|
||||||
|
|
||||||
|
bool IsRendererSubFrame(int process_id) const;
|
||||||
|
|
||||||
// pending_render_process => web contents.
|
// pending_render_process => web contents.
|
||||||
std::map<int, content::WebContents*> pending_processes_;
|
std::map<int, content::WebContents*> pending_processes_;
|
||||||
|
|
||||||
std::map<int, base::ProcessId> render_process_host_pids_;
|
std::map<int, base::ProcessId> render_process_host_pids_;
|
||||||
|
|
||||||
|
std::set<int> renderer_is_subframe_;
|
||||||
|
|
||||||
// list of site per affinity. weak_ptr to prevent instance locking
|
// list of site per affinity. weak_ptr to prevent instance locking
|
||||||
std::map<std::string, content::SiteInstance*> site_per_affinities_;
|
std::map<std::string, content::SiteInstance*> site_per_affinities_;
|
||||||
|
|
||||||
|
|
|
@ -271,7 +271,8 @@ WebContentsPreferences* WebContentsPreferences::From(
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContentsPreferences::AppendCommandLineSwitches(
|
void WebContentsPreferences::AppendCommandLineSwitches(
|
||||||
base::CommandLine* command_line) {
|
base::CommandLine* command_line,
|
||||||
|
bool is_subframe) {
|
||||||
// Check if plugins are enabled.
|
// Check if plugins are enabled.
|
||||||
if (IsEnabled(options::kPlugins))
|
if (IsEnabled(options::kPlugins))
|
||||||
command_line->AppendSwitch(switches::kEnablePlugins);
|
command_line->AppendSwitch(switches::kEnablePlugins);
|
||||||
|
@ -293,10 +294,16 @@ void WebContentsPreferences::AppendCommandLineSwitches(
|
||||||
if (IsEnabled(options::kWebviewTag))
|
if (IsEnabled(options::kWebviewTag))
|
||||||
command_line->AppendSwitch(switches::kWebviewTag);
|
command_line->AppendSwitch(switches::kWebviewTag);
|
||||||
|
|
||||||
|
// Sandbox can be enabled for renderer processes hosting cross-origin frames
|
||||||
|
// unless nodeIntegrationInSubFrames is enabled
|
||||||
|
bool can_sandbox_frame =
|
||||||
|
is_subframe && !IsEnabled(options::kNodeIntegrationInSubFrames);
|
||||||
|
|
||||||
// If the `sandbox` option was passed to the BrowserWindow's webPreferences,
|
// If the `sandbox` option was passed to the BrowserWindow's webPreferences,
|
||||||
// pass `--enable-sandbox` to the renderer so it won't have any node.js
|
// pass `--enable-sandbox` to the renderer so it won't have any node.js
|
||||||
// integration.
|
// integration. Otherwise disable Chromium sandbox, unless app.enableSandbox()
|
||||||
if (IsEnabled(options::kSandbox)) {
|
// was called.
|
||||||
|
if (IsEnabled(options::kSandbox) || can_sandbox_frame) {
|
||||||
command_line->AppendSwitch(switches::kEnableSandbox);
|
command_line->AppendSwitch(switches::kEnableSandbox);
|
||||||
} else if (!command_line->HasSwitch(switches::kEnableSandbox)) {
|
} else if (!command_line->HasSwitch(switches::kEnableSandbox)) {
|
||||||
command_line->AppendSwitch(service_manager::switches::kNoSandbox);
|
command_line->AppendSwitch(service_manager::switches::kNoSandbox);
|
||||||
|
|
|
@ -47,7 +47,8 @@ class WebContentsPreferences
|
||||||
void Merge(const base::DictionaryValue& new_web_preferences);
|
void Merge(const base::DictionaryValue& new_web_preferences);
|
||||||
|
|
||||||
// Append command paramters according to preferences.
|
// Append command paramters according to preferences.
|
||||||
void AppendCommandLineSwitches(base::CommandLine* command_line);
|
void AppendCommandLineSwitches(base::CommandLine* command_line,
|
||||||
|
bool is_subframe);
|
||||||
|
|
||||||
// Modify the WebPreferences according to preferences.
|
// Modify the WebPreferences according to preferences.
|
||||||
void OverrideWebkitPrefs(content::WebPreferences* prefs);
|
void OverrideWebkitPrefs(content::WebPreferences* prefs);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
const { expect } = require('chai')
|
const { expect } = require('chai')
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const http = require('http')
|
||||||
|
|
||||||
const { emittedNTimes, emittedOnce } = require('./events-helpers')
|
const { emittedNTimes, emittedOnce } = require('./events-helpers')
|
||||||
const { closeWindow } = require('./window-helpers')
|
const { closeWindow } = require('./window-helpers')
|
||||||
|
|
||||||
const { BrowserWindow } = remote
|
const { app, BrowserWindow, ipcMain } = remote
|
||||||
|
|
||||||
describe('renderer nodeIntegrationInSubFrames', () => {
|
describe('renderer nodeIntegrationInSubFrames', () => {
|
||||||
const generateTests = (description, webPreferences) => {
|
const generateTests = (description, webPreferences) => {
|
||||||
|
@ -149,3 +150,88 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||||
generateTests(config.title, config.webPreferences)
|
generateTests(config.title, config.webPreferences)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('cross-site frame sandboxing', () => {
|
||||||
|
let server = null
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
this.skip()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
server = http.createServer((req, res) => {
|
||||||
|
res.end(`<iframe name="frame" src="${server.cross_site_url}" />`)
|
||||||
|
})
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
server.url = `http://127.0.0.1:${server.address().port}/`
|
||||||
|
server.cross_site_url = `http://localhost:${server.address().port}/`
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
server.close()
|
||||||
|
server = null
|
||||||
|
})
|
||||||
|
|
||||||
|
const fixtures = path.resolve(__dirname, 'fixtures')
|
||||||
|
const preload = path.join(fixtures, 'module', 'preload-pid.js')
|
||||||
|
|
||||||
|
let w
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return closeWindow(w).then(() => {
|
||||||
|
w = null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const generateSpecs = (description, webPreferences) => {
|
||||||
|
describe(description, () => {
|
||||||
|
it('iframe process is sandboxed if possible', async () => {
|
||||||
|
w = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences
|
||||||
|
})
|
||||||
|
|
||||||
|
await w.loadURL(server.url)
|
||||||
|
|
||||||
|
const pidMain = w.webContents.getOSProcessId()
|
||||||
|
const pidFrame = w.webContents._getOSProcessIdForFrame('frame', server.cross_site_url)
|
||||||
|
|
||||||
|
const metrics = app.getAppMetrics()
|
||||||
|
const isProcessSandboxed = function (pid) {
|
||||||
|
const entry = metrics.filter(metric => metric.pid === pid)[0]
|
||||||
|
return entry && entry.sandboxed
|
||||||
|
}
|
||||||
|
|
||||||
|
const sandboxMain = !!(webPreferences.sandbox || process.mas)
|
||||||
|
const sandboxFrame = sandboxMain || !webPreferences.nodeIntegrationInSubFrames
|
||||||
|
|
||||||
|
expect(isProcessSandboxed(pidMain)).to.equal(sandboxMain)
|
||||||
|
expect(isProcessSandboxed(pidFrame)).to.equal(sandboxFrame)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSpecs('nodeIntegrationInSubFrames = false, sandbox = false', {
|
||||||
|
nodeIntegrationInSubFrames: false,
|
||||||
|
sandbox: false
|
||||||
|
})
|
||||||
|
|
||||||
|
generateSpecs('nodeIntegrationInSubFrames = false, sandbox = true', {
|
||||||
|
nodeIntegrationInSubFrames: false,
|
||||||
|
sandbox: true
|
||||||
|
})
|
||||||
|
|
||||||
|
generateSpecs('nodeIntegrationInSubFrames = true, sandbox = false', {
|
||||||
|
nodeIntegrationInSubFrames: true,
|
||||||
|
sandbox: false
|
||||||
|
})
|
||||||
|
|
||||||
|
generateSpecs('nodeIntegrationInSubFrames = true, sandbox = true', {
|
||||||
|
nodeIntegrationInSubFrames: true,
|
||||||
|
sandbox: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in a new issue