Skip to content

Conversation

tarruda
Copy link
Contributor

@tarruda tarruda commented Aug 21, 2016

This PR adds a sandboxed option that can be passed to webPreferences of BrowserWindow constructor. Passing this option will completely change the renderer implementation of the new window: It won't have any node.js integration, and all system interaction happens through IPC.

It also adds a --enable-sandbox command-line option that enables chromium OS-level sandbox for all renderers, and if this option is passed, then all BrowserWindow instances will have the sandboxed option passed automatically(since they would not work otherwise).

This PR also restores chromium native window.open API for sandboxed windows as there should be no issue in having multiple webContents for one sandboxed renderer(since there's no node.js context)

While the PR is relatively big, it is mostly made of required infrastructure changes to support the new code. The implementation is relatively simple: For renderers with the sandboxed option enabled, the AtomSandboxedRendererClient class is used. This class is unrelated to AtomRendererClient as it takes a very different approach to inject preload scripts and expose the ipcRenderer module(I've tried to reuse as much as possible, though). To support a basic commonjs environment in sandboxed renderers, browserify is used and integrated into the build system.

I've tried to make commits small and self contained as possible, so reading each commit individually is the recommended way to review.

Close #1865

@tarruda tarruda changed the title Add sandbox option and support native window.open [WIP] Add sandbox option and support native window.open Aug 22, 2016
@tarruda tarruda force-pushed the support-chromium-sandbox branch 24 times, most recently from af8043a to 7187417 Compare August 30, 2016 07:11
@tarruda
Copy link
Contributor Author

tarruda commented Aug 30, 2016

Hi again

I'm done making changes and adding tests, so this is ready for review.

This PR currently has some issues:

  • The C++ implementation of sandboxed renderer shares little code with the non-sandboxed renderer. As a consequence, sandboxed renderers have some missing functionality(setting background color doesn't work, for example). This can be fixed by extracting a base common class for AtomRendererClient and AtomSandboxedRendererClient.
  • The javascript code that supports sandboxed renderers(lib/sandboxed_renderer/init.js) only shares part of ipcRenderer with the non-sandboxed version(lib/renderer/init.js), but nothing stops more code to be shared by the two. For example, I believe it is possible to expose the most electron/node.js API to sandboxed renderers by augmenting the browser-renderer RPC module.
  • Right now, the code which determines which navigations will create a new SiteInstance is reimplementing some logic from content module. This is necessary because renderers spawned from sandboxed navigations should also be sandboxed, and I've found no way of associating newly spawned renderers(AppendExtraCommandLineSwitches) with sandboxed parents other than reusing the pending process logic in OverrideSiteIntanceForNavigation. The ideal implementation would probably require patching libchromiumcontent to notify AtomBrowserClient whenever a new SiteInstance is created, and populating pending_processes_ with the renderer id for the new instance.

I want to fix all of these issues, but I prefer to do it in separate PRs as this one is very big already.

Another possible issue(depending on the POV) is that browserify is now a build dependency and is used to provide commonjs for preload scripts in sandboxed renderers. I've opted to use browserify because is a very mature browser commonjs implementation and also was the fastest path to get a working commonjs environment in sandboxed renders.

An alternative to using browserify would be to implement a commonjs environment that uses IPC to read files from disk using the main process, though I'd prefer to stick with browserify as it is cleaner than repeatedly loading a bunch of files via IPC.

Finally think its worth mentioning that this PR adds a lot of code but the existing use cases of electron should not be affected by the changes made here. My goal is to allow electron to be used in more scenarios, such as implementing a web browser that shares some of chromium security features.

@tarruda tarruda changed the title [WIP] Add sandbox option and support native window.open Add sandbox option and support native window.open Aug 30, 2016

content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
if (WebContentsPreferences::IsSandboxed(web_contents)) {
AddSandboxedRendererId(host->GetID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be moved to BrowserClient::SiteInstanceGotProcess? So we can get rid of the locker.

Copy link
Contributor Author

@tarruda tarruda Sep 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only started playing with chromium content for this PR, so you tell me 😄

The locker is there because the sandboxed_renderers_ set is read from CanCreateWindow(IO thread)/ShouldCreateNewSiteInstance(UI thread) and written by RenderProcessWillLaunch/RenderProcessHostDestroyed which are both UI thread. BTW what is the simplest logic for determining which thread a method is called on, when no header comments are available? (other than printing the thread id to the console)

If you really want to get rid of the locker I suppose we could have a duplicate sandboxed_renderers_ set that is used by IO thread only(and updated with PostTask)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, we can just use lock for now and see if we can do better optimization in future.

Add RenderFrameObserver/RenderViewObserver subclasses that implement the
necessary support for preload scripts in sandboxed renderers.
The sandbox option allows multiple webContents in one renderer process, so using
the only the renderer id to identify WebContents instances is no longer an
option.

WebContents::GetID now returns a 64-bit integer, which is composed of both the
process id(high 32), and the RenderViewHost routing id(low 32). Also add a
`GetProcessID` that retrieves the renderer process id, a requirement in some of
our javascript code.
@tarruda tarruda force-pushed the support-chromium-sandbox branch from 39a0600 to 47fd417 Compare September 27, 2016 09:03
@zcbenz
Copy link
Contributor

zcbenz commented Sep 27, 2016

👍

@YurySolovyov
Copy link
Contributor

@tarruda this seems interesting, are you going to add some docs to explain the option and what it gived to developers. There are some information in this thread, but it is not easy to find.

@ahmedmohamedali
Copy link
Contributor

Hi @tarruda,

Understood ipcRenderer is exposed in the sandboxed renderer. Is there a plan to extend the list of exposed modules? for example websocket ?

Thanks.

@tarruda
Copy link
Contributor Author

tarruda commented Oct 20, 2016

@tarruda this seems interesting, are you going to add some docs to explain the option and what it gived to developers. There are some information in this thread, but it is not easy to find.

I will add documentation later

Understood ipcRenderer is exposed in the sandboxed renderer. Is there a plan to extend the list of exposed modules? for example websocket ?

It is possible that we'll eventually expose every module available to non-sandboxed renderers, but you can already achieve anything with ipcRenderer alone. From a security POV, it is better that only ipcRenderer is exposed.

@ahmedmohamedali
Copy link
Contributor

@tarruda I agree for the security constraints and this the reason I'm interested in your contribution. However I also need to establish a communication with a standalone node.js process (non electron process). Websocket seems an easy option for a electron renderer to communicate with the node.js process. Is it possible to require the websocket module in the preload in js or this can only be achievable by modifying electron code ? Thanks

@ahmedmohamedali
Copy link
Contributor

I think the list of modules to expose should be passed with a new parameter such as sandboxAllowedModules. The default electron behavior will not be impacted and the change will give an opportunity for those who needs to support some more modules. Not sure if there is way to raise a change request here or if I need to create a PR. Thanks.

@tarruda
Copy link
Contributor Author

tarruda commented Oct 22, 2016

However I also need to establish a communication with a standalone node.js process (non electron process). Websocket seems an easy option for a electron renderer to communicate with the node.js process. Is it possible to require the websocket module in the preload in js or this can only be achievable by modifying electron code ? Thanks

I'm not an expert in the chrome sandbox architecture, but if I understood it correctly, the renderer is only allowed to communicate with the browser process, so even if we allow arbitrary node/electron modules to be required from the renderer, the entire API would be proxied using IPC(it would simply be hidden from you). Even network access is done via the browser process, so the renderer is never opening any network connections directly.

If you simply want to use WebSocket web api, then it should already be available to sandboxed renderers, there's no need to modify anything.

@tarruda
Copy link
Contributor Author

tarruda commented Oct 22, 2016

Not sure if there is way to raise a change request here or if I need to create a PR. Thanks.

No need to create an issue a, simply use #6712 to track progress. I plan to implement it as soon as I get a free weekend, work/family have been demanding a lot lately :)

@ahmedmohamedali
Copy link
Contributor

ahmedmohamedali commented Oct 22, 2016

That's fine, many thanks.
Exposing ipcRenderer by default in sandboxed mode will still be unsafe as security hole in the main process can be exploited to harm the user machine.
So,I think ipcRenderer should be disabled by default and it will be the responsibility of the developer to select it in the allowed modules if he wills. Another option is to expose a new safeIpcRender module that will only process user defined messages.

@GregoryGatellier
Copy link

@tarruda I would like your opinion about having a default preload used when we are in sandbox mode (and in that case no need to have preload attribute in webpreferences).
We are very interested by using the sandbox but having a preload script for each application is not so easy to maintain (depending if the application need specific code in the preload)

@tarruda
Copy link
Contributor Author

tarruda commented Nov 2, 2016

@tarruda I would like your opinion about having a default preload used when we are in sandbox mode (and in that case no need to have preload attribute in webpreferences).
We are very interested by using the sandbox but having a preload script for each application is not so easy to maintain (depending if the application need specific code in the preload)

Not sure if I understand what you mean by "default preload". Can you give an example?

If you want to have a single preload API that exports all electron/node.js APIs to the renderer, then the sandbox won't help much(assuming you are using it for security purposes).

@GregoryGatellier
Copy link

Today with your solution all the developers who want to use sandbox have to manage on their side a preload for initiliazing some code (if they want to use it) for each application and depending if they want to activate or not the sandbox.
My main concern is for having window.open works in sandbox mode, we need some specific code in the preload and it's not very helpful to adding manually the code every time we need to use sandbox (it should be automatic).

@tarruda
Copy link
Contributor Author

tarruda commented Nov 3, 2016

There's no need to have a preload, you can simply use new BrowserWindow({webPreferences: {sandbox: true}}) and it will create a sandboxed window. This should work for loading most web applications and offer the same security as chrome. window.open will also work without a preload.

@ahmedmohamedali
Copy link
Contributor

@tarruda,

I think security whole in the master process can be exploited via IpcRenderer. If I need a pre-load file to expose ipcRenderer this is fine, I will just avoid to expose it. But if it is automatically exposed what is the best way to disable it before starting the renderer process ?
Thanks.

@GregoryGatellier
Copy link

GregoryGatellier commented Nov 3, 2016

Yes but for having window.open works correctly in sandbox mode I need to have some code to be loaded. That's why I ask the question (if some was interesting to have a fix for window.open).

@tarruda
Copy link
Contributor Author

tarruda commented Nov 3, 2016

But if it is automatically exposed what is the best way to disable it before starting the renderer process ?

It is not automatically exposed. Since one of the main use cases of sandbox mode is enabling chromium sandbox for security, you have to explicitly create any extra APIs that use ipcRenderer to communicate with the main process.

@ahmedmohamedali
Copy link
Contributor

I already got that. I need that ipcRenderer not even be available in the Renderer process.
How to achieve that ?

@GregoryGatellier
Copy link

In order to give more details: 'default preload' means for me specific code in init.js for renderer sandboxed

@tarruda
Copy link
Contributor Author

tarruda commented Nov 3, 2016

I already got that. I need that ipcRenderer not even be available in the Renderer process.
How to achieve that ?

ipcRenderer is only available if your preload script exposes it as a global variable. The preload script is executed in the scope of an anonymous function before any javascript from the web page is loaded. The only way ipcRenderer is available to javascript loaded by web page is if you do something like this:

// preload.js
window.ipcRenderer = require('electron').ipcRenderer
// ipcRenderer is a global variable visible to untrusted javascript.

@ahmedmohamedali
Copy link
Contributor

Great. This is what I wanted to confirm with you.
Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants