Skip to content

Commit

Permalink
feat: sandbox renderer processes for cross-origin frames
Browse files Browse the repository at this point in the history
  • Loading branch information
miniak committed Jun 15, 2019
1 parent 3309005 commit cd5e900
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 18 deletions.
12 changes: 8 additions & 4 deletions atom/app/atom_main_delegate.cc
Expand Up @@ -59,6 +59,11 @@ bool IsBrowserProcess(base::CommandLine* cmd) {
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
// and resources loaded.
bool SubprocessNeedsResourceBundle(const std::string& process_type) {
Expand Down Expand Up @@ -281,10 +286,9 @@ content::ContentGpuClient* AtomMainDelegate::CreateContentGpuClient() {

content::ContentRendererClient*
AtomMainDelegate::CreateContentRendererClient() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableSandbox) ||
!base::CommandLine::ForCurrentProcess()->HasSwitch(
service_manager::switches::kNoSandbox)) {
auto* command_line = base::CommandLine::ForCurrentProcess();

if (IsSandboxEnabled(command_line)) {
renderer_client_.reset(new AtomSandboxedRendererClient);
} else {
renderer_client_.reset(new AtomRendererClient);
Expand Down
40 changes: 32 additions & 8 deletions atom/browser/api/atom_api_web_contents.cc
Expand Up @@ -1213,6 +1213,14 @@ base::ProcessId WebContents::GetOSProcessID() const {
return base::GetProcId(process_handle);
}

base::ProcessId WebContents::GetOSProcessIdForFrame(
const std::string& name) const {
if (auto* frame = GetFrameByName(name)) {
return base::GetProcId(frame->GetProcess()->GetProcess().Handle());
}
return base::kNullProcessId;
}

WebContents::Type WebContents::GetType() const {
return type_;
}
Expand Down Expand Up @@ -1735,17 +1743,12 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
int32_t frame_id,
const std::string& channel,
const base::ListValue& args) {
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())
auto* frame = GetFrameByRoutingID(frame_id);
if (!frame || !frame->IsRenderFrameLive())
return false;

mojom::ElectronRendererAssociatedPtr electron_ptr;
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
frame->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(internal, send_to_all, channel, args.Clone(),
0 /* sender_id */);
Expand Down Expand Up @@ -2194,6 +2197,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
&WebContents::SetBackgroundThrottling)
.SetMethod("getProcessId", &WebContents::GetProcessID)
.SetMethod("getOSProcessId", &WebContents::GetOSProcessID)
.SetMethod("_getOSProcessIdForFrame",
&WebContents::GetOSProcessIdForFrame)
.SetMethod("equal", &WebContents::Equal)
.SetMethod("_loadURL", &WebContents::LoadURL)
.SetMethod("downloadURL", &WebContents::DownloadURL)
Expand Down Expand Up @@ -2298,6 +2303,25 @@ AtomBrowserContext* WebContents::GetBrowserContext() const {
return static_cast<AtomBrowserContext*>(web_contents()->GetBrowserContext());
}

content::RenderFrameHost* WebContents::GetFrameByRoutingID(int frame_id) const {
auto frames = web_contents()->GetAllFrames();
auto it = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) {
return f->GetRoutingID() == frame_id;
});

return (it != frames.end()) ? *it : nullptr;
}

content::RenderFrameHost* WebContents::GetFrameByName(
const std::string& name) const {
auto frames = web_contents()->GetAllFrames();
auto it = std::find_if(frames.begin(), frames.end(), [name](auto* f) {
return !f->GetFrameName().empty() && f->GetFrameName() == name;
});

return (it != frames.end()) ? *it : nullptr;
}

// static
mate::Handle<WebContents> WebContents::Create(v8::Isolate* isolate,
const mate::Dictionary& options) {
Expand Down
4 changes: 4 additions & 0 deletions atom/browser/api/atom_api_web_contents.h
Expand Up @@ -138,6 +138,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
void SetBackgroundThrottling(bool allowed);
int GetProcessID() const;
base::ProcessId GetOSProcessID() const;
base::ProcessId GetOSProcessIdForFrame(const std::string& name) const;
Type GetType() const;
bool Equal(const WebContents* web_contents) const;
void LoadURL(const GURL& url, const mate::Dictionary& options);
Expand Down Expand Up @@ -477,6 +478,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
private:
AtomBrowserContext* GetBrowserContext() const;

content::RenderFrameHost* GetFrameByRoutingID(int frame_id) const;
content::RenderFrameHost* GetFrameByName(const std::string& name) const;

// Binds the given request for the ElectronBrowser API. When the
// RenderFrameHost is destroyed, all related bindings will be removed.
void BindElectronBrowser(mojom::ElectronBrowserRequest request,
Expand Down
13 changes: 12 additions & 1 deletion atom/browser/atom_browser_client.cc
Expand Up @@ -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(
content::RenderProcessHost* host,
service_manager::mojom::ServiceRequest* service_request) {
Expand Down Expand Up @@ -463,6 +467,11 @@ void AtomBrowserClient::RegisterPendingSiteInstance(
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
auto* pending_process = pending_site_instance->GetProcess();
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(
Expand Down Expand Up @@ -513,7 +522,8 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
}
auto* web_preferences = WebContentsPreferences::From(web_contents);
if (web_preferences)
web_preferences->AppendCommandLineSwitches(command_line);
web_preferences->AppendCommandLineSwitches(
command_line, IsRendererSubFrame(process_id));
SessionPreferences::AppendExtraCommandLineSwitches(
web_contents->GetBrowserContext(), command_line);
if (CanUseCustomSiteInstance()) {
Expand Down Expand Up @@ -752,6 +762,7 @@ void AtomBrowserClient::RenderProcessHostDestroyed(
content::RenderProcessHost* host) {
int process_id = host->GetID();
pending_processes_.erase(process_id);
renderer_is_subframe_.erase(process_id);
RemoveProcessPreferences(process_id);
}

Expand Down
4 changes: 4 additions & 0 deletions atom/browser/atom_browser_client.h
Expand Up @@ -235,11 +235,15 @@ class AtomBrowserClient : public content::ContentBrowserClient,
void ConsiderSiteInstanceForAffinity(content::RenderFrameHost* rfh,
content::SiteInstance* site_instance);

bool IsRendererSubFrame(int process_id) const;

// pending_render_process => web contents.
std::map<int, content::WebContents*> pending_processes_;

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
std::map<std::string, content::SiteInstance*> site_per_affinities_;

Expand Down
13 changes: 10 additions & 3 deletions atom/browser/web_contents_preferences.cc
Expand Up @@ -271,7 +271,8 @@ WebContentsPreferences* WebContentsPreferences::From(
}

void WebContentsPreferences::AppendCommandLineSwitches(
base::CommandLine* command_line) {
base::CommandLine* command_line,
bool is_subframe) {
// Check if plugins are enabled.
if (IsEnabled(options::kPlugins))
command_line->AppendSwitch(switches::kEnablePlugins);
Expand All @@ -293,10 +294,16 @@ void WebContentsPreferences::AppendCommandLineSwitches(
if (IsEnabled(options::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,
// pass `--enable-sandbox` to the renderer so it won't have any node.js
// integration.
if (IsEnabled(options::kSandbox)) {
// integration. Otherwise disable Chromium sandbox, unless app.enableSandbox()
// was called.
if (IsEnabled(options::kSandbox) || can_sandbox_frame) {
command_line->AppendSwitch(switches::kEnableSandbox);
} else if (!command_line->HasSwitch(switches::kEnableSandbox)) {
command_line->AppendSwitch(service_manager::switches::kNoSandbox);
Expand Down
3 changes: 2 additions & 1 deletion atom/browser/web_contents_preferences.h
Expand Up @@ -47,7 +47,8 @@ class WebContentsPreferences
void Merge(const base::DictionaryValue& new_web_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.
void OverrideWebkitPrefs(content::WebPreferences* prefs);
Expand Down
88 changes: 87 additions & 1 deletion spec/api-subframe-spec.js
@@ -1,11 +1,12 @@
const { expect } = require('chai')
const { remote } = require('electron')
const path = require('path')
const http = require('http')

const { emittedNTimes, emittedOnce } = require('./events-helpers')
const { closeWindow } = require('./window-helpers')

const { BrowserWindow } = remote
const { app, BrowserWindow, ipcMain } = remote

describe('renderer nodeIntegrationInSubFrames', () => {
const generateTests = (description, webPreferences) => {
Expand Down Expand Up @@ -149,3 +150,88 @@ describe('renderer nodeIntegrationInSubFrames', () => {
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')

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
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
})
})

0 comments on commit cd5e900

Please sign in to comment.