Browse Source

Add "sandboxed" option to "webPreferences".

When "sandboxed" is passed as a web preference for `BrowserWindow`, the newly
created renderer won't run any node.js code/integration, only communicating with
the system via the IPC API of the content module. This is a requirement for
running the renderer under chrome OS-level sandbox.

Beyond that, certain behaviors of AtomBrowserClient are modified when dealing
with sandboxed renderers:

- `OverrideSiteInstanceNavigation` no longer create a new `SiteInstance` for
  every navigation. Instead, it reuses the source `SiteInstance` when not
  navigating to a different site.
- `CanCreateWindow` will return true and allow javascript access.
Thiago de Arruda 8 years ago
parent
commit
c783ec72bc

+ 13 - 6
atom/app/atom_main_delegate.cc

@@ -11,6 +11,7 @@
 #include "atom/browser/atom_browser_client.h"
 #include "atom/browser/relauncher.h"
 #include "atom/common/google_api_key.h"
+#include "atom/common/options_switches.h"
 #include "atom/renderer/atom_renderer_client.h"
 #include "atom/utility/atom_content_utility_client.h"
 #include "base/command_line.h"
@@ -29,7 +30,7 @@ namespace {
 const char* kRelauncherProcess = "relauncher";
 
 bool IsBrowserProcess(base::CommandLine* cmd) {
-  std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType);
+  std::string process_type = cmd->GetSwitchValueASCII(::switches::kProcessType);
   return process_type.empty();
 }
 
@@ -72,7 +73,7 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
 
   // Only enable logging when --enable-logging is specified.
   std::unique_ptr<base::Environment> env(base::Environment::Create());
-  if (!command_line->HasSwitch(switches::kEnableLogging) &&
+  if (!command_line->HasSwitch(::switches::kEnableLogging) &&
       !env->HasVar("ELECTRON_ENABLE_LOGGING")) {
     settings.logging_dest = logging::LOG_NONE;
     logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES);
@@ -115,17 +116,17 @@ void AtomMainDelegate::PreSandboxStartup() {
 
   auto command_line = base::CommandLine::ForCurrentProcess();
   std::string process_type = command_line->GetSwitchValueASCII(
-      switches::kProcessType);
+      ::switches::kProcessType);
 
   // Only append arguments for browser process.
   if (!IsBrowserProcess(command_line))
     return;
 
   // Disable renderer sandbox for most of node's functions.
-  command_line->AppendSwitch(switches::kNoSandbox);
+  command_line->AppendSwitch(::switches::kNoSandbox);
 
   // Allow file:// URIs to read other file:// URIs by default.
-  command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
+  command_line->AppendSwitch(::switches::kAllowFileAccessFromFiles);
 
 #if defined(OS_MACOSX)
   // Enable AVFoundation.
@@ -140,7 +141,13 @@ content::ContentBrowserClient* AtomMainDelegate::CreateContentBrowserClient() {
 
 content::ContentRendererClient*
     AtomMainDelegate::CreateContentRendererClient() {
-  renderer_client_.reset(new AtomRendererClient);
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+        switches::kEnableSandbox)) {
+    renderer_client_.reset(new content::ContentRendererClient);
+  } else {
+    renderer_client_.reset(new AtomRendererClient);
+  }
+
   return renderer_client_.get();
 }
 

+ 52 - 2
atom/browser/atom_browser_client.cc

@@ -99,6 +99,44 @@ content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID(
   return WebContentsPreferences::GetWebContentsFromProcessID(process_id);
 }
 
+bool AtomBrowserClient::ShouldCreateNewSiteInstance(
+    content::BrowserContext* browser_context,
+    content::SiteInstance* current_instance,
+    const GURL& url) {
+
+  if (url.SchemeIs(url::kJavaScriptScheme))
+    // "javacript:" scheme should always use same SiteInstance
+    return false;
+
+  if (!IsRendererSandboxed(current_instance->GetProcess()->GetID()))
+    // non-sandboxed renderers should always create a new SiteInstance
+    return true;
+
+  // Create new a SiteInstance if navigating to a different site.
+  auto src_url = current_instance->GetSiteURL();
+  return
+      !content::SiteInstance::IsSameWebSite(browser_context, src_url, url) &&
+      // `IsSameWebSite` doesn't seem to work for some URIs such as `file:`,
+      // handle these scenarios by comparing only the site as defined by
+      // `GetSiteForURL`.
+      content::SiteInstance::GetSiteForURL(browser_context, url) != src_url;
+}
+
+void AtomBrowserClient::AddSandboxedRendererId(int process_id) {
+  base::AutoLock auto_lock(sandboxed_renderers_lock_);
+  sandboxed_renderers_.insert(process_id);
+}
+
+void AtomBrowserClient::RemoveSandboxedRendererId(int process_id) {
+  base::AutoLock auto_lock(sandboxed_renderers_lock_);
+  sandboxed_renderers_.erase(process_id);
+}
+
+bool AtomBrowserClient::IsRendererSandboxed(int process_id) {
+  base::AutoLock auto_lock(sandboxed_renderers_lock_);
+  return sandboxed_renderers_.count(process_id);
+}
+
 void AtomBrowserClient::RenderProcessWillLaunch(
     content::RenderProcessHost* host) {
   int process_id = host->GetID();
@@ -106,6 +144,13 @@ void AtomBrowserClient::RenderProcessWillLaunch(
   host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext()));
   host->AddFilter(
       new WidevineCdmMessageFilter(process_id, host->GetBrowserContext()));
+
+  content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
+  if (WebContentsPreferences::IsSandboxed(web_contents)) {
+    AddSandboxedRendererId(host->GetID());
+    // ensure the sandboxed renderer id is removed later
+    host->AddObserver(this);
+  }
 }
 
 content::SpeechRecognitionManagerDelegate*
@@ -155,8 +200,7 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation(
     return;
   }
 
-  // Restart renderer process for all navigations except "javacript:" scheme.
-  if (url.SchemeIs(url::kJavaScriptScheme))
+  if (!ShouldCreateNewSiteInstance(browser_context, current_instance, url))
     return;
 
   scoped_refptr<content::SiteInstance> site_instance =
@@ -281,6 +325,11 @@ bool AtomBrowserClient::CanCreateWindow(
     bool* no_javascript_access) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
+  if (IsRendererSandboxed(render_process_id)) {
+    *no_javascript_access = false;
+    return true;
+  }
+
   if (delegate_) {
     content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
         base::Bind(&api::App::OnCreateWindow,
@@ -338,6 +387,7 @@ void AtomBrowserClient::RenderProcessHostDestroyed(
       break;
     }
   }
+  RemoveSandboxedRendererId(process_id);
 }
 
 }  // namespace atom

+ 12 - 0
atom/browser/atom_browser_client.h

@@ -6,6 +6,7 @@
 #define ATOM_BROWSER_ATOM_BROWSER_CLIENT_H_
 
 #include <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -108,8 +109,19 @@ class AtomBrowserClient : public brightray::BrowserClient,
   void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
 
  private:
+  bool ShouldCreateNewSiteInstance(content::BrowserContext* browser_context,
+                                   content::SiteInstance* current_instance,
+                                   const GURL& dest_url);
+  // Add/remove a process id to `sandboxed_renderers_`.
+  void AddSandboxedRendererId(int process_id);
+  void RemoveSandboxedRendererId(int process_id);
+  bool IsRendererSandboxed(int process_id);
+
   // pending_render_process => current_render_process.
   std::map<int, int> pending_processes_;
+  // Set that contains the process ids of all sandboxed renderers
+  std::set<int> sandboxed_renderers_;
+  base::Lock sandboxed_renderers_lock_;
 
   std::unique_ptr<AtomResourceDispatcherHostDelegate>
       resource_dispatcher_host_delegate_;

+ 21 - 0
atom/browser/web_contents_preferences.cc

@@ -97,6 +97,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
   command_line->AppendSwitchASCII(switches::kNodeIntegration,
                                   node_integration ? "true" : "false");
 
+  // 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 (IsSandboxed(web_contents))
+    command_line->AppendSwitch(switches::kEnableSandbox);
+
   // The preload script.
   base::FilePath::StringType preload;
   if (web_preferences.GetString(options::kPreloadScript, &preload)) {
@@ -194,6 +200,21 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
     command_line->AppendSwitch(cc::switches::kEnableBeginFrameScheduling);
 }
 
+bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
+  WebContentsPreferences* self;
+  if (!web_contents)
+    return false;
+
+  self = FromWebContents(web_contents);
+  if (!self)
+    return false;
+
+  base::DictionaryValue& web_preferences = self->web_preferences_;
+  bool sandboxed = false;
+  web_preferences.GetBoolean("sandbox", &sandboxed);
+  return sandboxed;
+}
+
 // static
 void WebContentsPreferences::OverrideWebkitPrefs(
     content::WebContents* web_contents, content::WebPreferences* prefs) {

+ 2 - 0
atom/browser/web_contents_preferences.h

@@ -36,6 +36,8 @@ class WebContentsPreferences
   static void AppendExtraCommandLineSwitches(
       content::WebContents* web_contents, base::CommandLine* command_line);
 
+  static bool IsSandboxed(content::WebContents* web_contents);
+
   // Modify the WebPreferences according to |web_contents|'s preferences.
   static void OverrideWebkitPrefs(
       content::WebContents* web_contents, content::WebPreferences* prefs);

+ 3 - 0
atom/common/options_switches.cc

@@ -117,6 +117,9 @@ const char kDisableBlinkFeatures[] = "disableBlinkFeatures";
 
 namespace switches {
 
+// Enable chromium sandbox.
+const char kEnableSandbox[] = "enable-sandbox";
+
 // Enable plugins.
 const char kEnablePlugins[] = "enable-plugins";
 

+ 1 - 0
atom/common/options_switches.h

@@ -67,6 +67,7 @@ extern const char kDisableBlinkFeatures[];
 
 namespace switches {
 
+extern const char kEnableSandbox[];
 extern const char kEnablePlugins[];
 extern const char kPpapiFlashPath[];
 extern const char kPpapiFlashVersion[];