Bug 1455601 - Sandbox AutoConfig if sandbox_enabled pref is set. r=kmag
authorMichael Kaply <mozilla@kaply.com>
Wed, 23 May 2018 12:21:53 -0500
changeset 419634 bcae764555a8f86a0ad262d1e4d4ab46b6609440
parent 419633 538e4b3728c4308aba4d3aa34b910b93cd3bc558
child 419635 91c6df51ea6163d73eb1d45a44ae64f4f38dcbd7
push id64365
push usermozilla@kaply.com
push dateWed, 23 May 2018 22:17:00 +0000
treeherderautoland@bcae764555a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1455601
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1455601 - Sandbox AutoConfig if sandbox_enabled pref is set. r=kmag MozReview-Commit-ID: AaGWs1BmV4E
extensions/pref/autoconfig/src/nsAutoConfig.cpp
extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp
extensions/pref/autoconfig/src/nsJSConfigTriggers.h
extensions/pref/autoconfig/src/nsReadConfig.cpp
extensions/pref/autoconfig/src/prefcalls.js
extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg
extensions/pref/autoconfig/test/unit/autoconfig.js
extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
extensions/pref/autoconfig/test/unit/xpcshell.ini
--- a/extensions/pref/autoconfig/src/nsAutoConfig.cpp
+++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ResultExtensions.h"
 #include "nsAutoConfig.h"
+#include "nsJSConfigTriggers.h"
+
 #include "nsIURI.h"
 #include "nsIHttpChannel.h"
 #include "nsIFileStreams.h"
 #include "nsThreadUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIObserverService.h"
 #include "nsLiteralString.h"
 #include "nsIPromptService.h"
@@ -24,22 +26,16 @@
 
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Logging.h"
 
 using mozilla::LogLevel;
 
 mozilla::LazyLogModule MCD("MCD");
 
-extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
-                                          const char *filename,
-                                          bool bGlobalContext,
-                                          bool bCallbacks,
-                                          bool skipFirstLine);
-
 // nsISupports Implementation
 
 NS_IMPL_ISUPPORTS(nsAutoConfig, nsIAutoConfig, nsITimerCallback, nsIStreamListener,
                   nsIObserver, nsIRequestObserver, nsISupportsWeakReference,
                   nsINamed)
 
 nsAutoConfig::nsAutoConfig()
 {
@@ -144,17 +140,17 @@ nsAutoConfig::OnStopRequest(nsIRequest *
             MOZ_LOG(MCD, LogLevel::Debug, ("mcd http request failed with status %x\n", httpStatus));
             return readOfflineFile();
         }
     }
 
     // Send the autoconfig.jsc to javascript engine.
 
     rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(),
-                              nullptr, false,true, false);
+                                   nullptr, false, true, false);
     if (NS_SUCCEEDED(rv)) {
 
         // Write the autoconfig.jsc to failover.jsc (cached copy)
         rv = writeFailoverFile();
 
         if (NS_FAILED(rv))
             NS_WARNING("Error writing failover.jsc file");
 
--- a/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp
+++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp
@@ -1,45 +1,53 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "nsJSConfigTriggers.h"
+
 #include "jsapi.h"
 #include "nsIXPConnect.h"
 #include "nsCOMPtr.h"
 #include "nsIServiceManager.h"
 #include "nsIComponentManager.h"
 #include "nsString.h"
+#include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nspr.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsJSPrincipals.h"
 #include "nsIScriptError.h"
 #include "js/Wrapper.h"
+#include "NullPrincipal.h"
 
 extern mozilla::LazyLogModule MCD;
 using mozilla::AutoSafeJSContext;
 using mozilla::dom::AutoJSAPI;
 
 //*****************************************************************************
 
+static JS::PersistentRooted<JSObject *> autoconfigSystemSb;
 static JS::PersistentRooted<JSObject *> autoconfigSb;
+static bool sandboxEnabled;
 
-nsresult CentralizedAdminPrefManagerInit()
+nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled)
 {
     nsresult rv;
 
     // If the sandbox is already created, no need to create it again.
     if (autoconfigSb.initialized())
         return NS_OK;
 
+    sandboxEnabled = aSandboxEnabled || !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "release");
+
     // Grab XPConnect.
     nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv)) {
         return rv;
     }
 
     // Grab the system principal.
     nsCOMPtr<nsIPrincipal> principal;
@@ -49,34 +57,69 @@ nsresult CentralizedAdminPrefManagerInit
     // Create a sandbox.
     AutoSafeJSContext cx;
     JS::Rooted<JSObject*> sandbox(cx);
     rv = xpc->CreateSandbox(cx, principal, sandbox.address());
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Unwrap, store and root the sandbox.
     NS_ENSURE_STATE(sandbox);
+    autoconfigSystemSb.init(cx, js::UncheckedUnwrap(sandbox));
+
+
+    // Create an unprivileged sandbox.
+    principal = NullPrincipal::CreateWithoutOriginAttributes();
+    rv = xpc->CreateSandbox(cx, principal, sandbox.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+
     autoconfigSb.init(cx, js::UncheckedUnwrap(sandbox));
 
+
+    // Define gSandbox on system sandbox.
+    JSAutoRealm ac(cx, autoconfigSystemSb);
+
+    JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*sandbox));
+
+    if (!JS_WrapValue(cx, &value) ||
+        !JS_DefineProperty(cx, autoconfigSystemSb, "gSandbox", value, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+
     return NS_OK;
 }
 
 nsresult CentralizedAdminPrefManagerFinish()
 {
     if (autoconfigSb.initialized()) {
         AutoSafeJSContext cx;
         autoconfigSb.reset();
+        autoconfigSystemSb.reset();
         JS_MaybeGC(cx);
     }
     return NS_OK;
 }
 
 nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
-                                   const char *filename, bool bGlobalContext,
-                                   bool bCallbacks, bool skipFirstLine)
+                                   const char *filename, bool globalContext,
+                                   bool callbacks, bool skipFirstLine,
+                                   bool isPrivileged)
+{
+    if (!sandboxEnabled) {
+        isPrivileged = true;
+    }
+    return EvaluateAdminConfigScript(isPrivileged ? autoconfigSystemSb : autoconfigSb,
+                                     js_buffer, length, filename,
+                                     globalContext, callbacks, skipFirstLine);
+
+}
+
+nsresult EvaluateAdminConfigScript(JS::HandleObject sandbox,
+                                   const char *js_buffer, size_t length,
+                                   const char *filename, bool globalContext,
+                                   bool callbacks, bool skipFirstLine)
 {
     nsresult rv = NS_OK;
 
     if (skipFirstLine) {
         /* In order to protect the privacy of the JavaScript preferences file
          * from loading by the browser, we make the first line unparseable
          * by JavaScript. We must skip that line here before executing
          * the JavaScript code.
@@ -99,17 +142,17 @@ nsresult EvaluateAdminConfigScript(const
 
     // Grab XPConnect.
     nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv)) {
         return rv;
     }
 
     AutoJSAPI jsapi;
-    if (!jsapi.Init(autoconfigSb)) {
+    if (!jsapi.Init(sandbox)) {
         return NS_ERROR_UNEXPECTED;
     }
     JSContext* cx = jsapi.cx();
 
     nsAutoCString script(js_buffer, length);
     JS::RootedValue v(cx);
 
     nsString convertedScript;
@@ -120,19 +163,22 @@ nsresult EvaluateAdminConfigScript(const
         nsContentUtils::ReportToConsoleNonLocalized(
             NS_LITERAL_STRING("Your AutoConfig file is ASCII. Please convert it to UTF-8."),
             nsIScriptError::warningFlag,
             NS_LITERAL_CSTRING("autoconfig"),
             nullptr);
         /* If the length is 0, the conversion failed. Fallback to ASCII */
         convertedScript = NS_ConvertASCIItoUTF16(script);
     }
-    JS::Rooted<JS::Value> value(cx, JS::BooleanValue(isUTF8));
-    if (!JS_DefineProperty(cx, autoconfigSb, "gIsUTF8", value, JSPROP_ENUMERATE)) {
-        return NS_ERROR_UNEXPECTED;
+    {
+        JSAutoRealm ac(cx, autoconfigSystemSb);
+        JS::Rooted<JS::Value> value(cx, JS::BooleanValue(isUTF8));
+        if (!JS_DefineProperty(cx, autoconfigSystemSb, "gIsUTF8", value, JSPROP_ENUMERATE)) {
+            return NS_ERROR_UNEXPECTED;
+        }
     }
     rv = xpc->EvalInSandboxObject(convertedScript, filename, cx,
-                                  autoconfigSb, &v);
+                                  sandbox, &v);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
new file mode 100644
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.h
@@ -0,0 +1,21 @@
+#ifndef nsConfigTriggers_h
+#define nsConfigTriggers_h
+
+#include "nscore.h"
+#include "js/TypeDecls.h"
+
+nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
+                                   const char *filename,
+                                   bool bGlobalContext,
+                                   bool bCallbacks,
+                                   bool skipFirstLine,
+                                   bool isPrivileged = false);
+
+nsresult EvaluateAdminConfigScript(JS::HandleObject sandbox,
+                                   const char *js_buffer, size_t length,
+                                   const char *filename,
+                                   bool bGlobalContext,
+                                   bool bCallbacks,
+                                   bool skipFirstLine);
+
+#endif // nsConfigTriggers_h
--- a/extensions/pref/autoconfig/src/nsReadConfig.cpp
+++ b/extensions/pref/autoconfig/src/nsReadConfig.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsReadConfig.h"
+#include "nsJSConfigTriggers.h"
+
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIAppStartup.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIAutoConfig.h"
 #include "nsIComponentManager.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIPrefBranch.h"
@@ -21,25 +23,19 @@
 #include "nsString.h"
 #include "nsCRT.h"
 #include "nspr.h"
 #include "nsXULAppAPI.h"
 #include "nsContentUtils.h"
 
 extern mozilla::LazyLogModule MCD;
 
-extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
-                                          const char *filename,
-                                          bool bGlobalContext,
-                                          bool bCallbacks,
-                                          bool skipFirstLine);
-extern nsresult CentralizedAdminPrefManagerInit();
+extern nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled);
 extern nsresult CentralizedAdminPrefManagerFinish();
 
-
 static nsresult DisplayError(void)
 {
     nsresult rv;
 
     nsCOMPtr<nsIPromptService> promptService = do_GetService("@mozilla.org/embedcomp/prompt-service;1");
     if (!promptService)
         return NS_ERROR_FAILURE;
 
@@ -94,17 +90,18 @@ nsReadConfig::~nsReadConfig()
 }
 
 NS_IMETHODIMP nsReadConfig::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
 {
     nsresult rv = NS_OK;
 
     if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) {
         rv = readConfigFile();
-        if (NS_FAILED(rv)) {
+        // Don't show error alerts if the sandbox is enabled
+        if (NS_FAILED(rv) && !sandboxEnabled) {
             rv = DisplayError();
             if (NS_FAILED(rv)) {
                 nsCOMPtr<nsIAppStartup> appStartup =
                     do_GetService(NS_APPSTARTUP_CONTRACTID);
                 if (appStartup)
                     appStartup->Quit(nsIAppStartup::eAttemptQuit);
             }
         }
@@ -134,16 +131,20 @@ nsresult nsReadConfig::readConfigFile()
 
     rv = prefService->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch));
     if (NS_FAILED(rv))
         return rv;
 
     // This preference is set in the all.js or all-ns.js (depending whether
     // running mozilla or netscp6)
 
+    bool sandboxEnabled = false;
+    rv = defaultPrefBranch->GetBoolPref("general.config.sandbox_enabled",
+                                        &sandboxEnabled);
+
     rv = defaultPrefBranch->GetCharPref("general.config.filename",
                                         lockFileName);
 
     MOZ_LOG(MCD, LogLevel::Debug, ("general.config.filename = %s\n", lockFileName.get()));
     if (NS_FAILED(rv))
         return rv;
 
     for (size_t index = 0, len = mozilla::ArrayLength(gBlockedConfigs); index < len;
@@ -154,17 +155,17 @@ nsresult nsReadConfig::readConfigFile()
       }
     }
 
     // This needs to be read only once.
     //
     if (!mRead) {
         // Initiate the new JS Context for Preference management
 
-        rv = CentralizedAdminPrefManagerInit();
+        rv = CentralizedAdminPrefManagerInit(sandboxEnabled);
         if (NS_FAILED(rv))
             return rv;
 
         // Open and evaluate function calls to set/lock/unlock prefs
         rv = openAndEvaluateJSFile("prefcalls.js", 0, false, false);
         if (NS_FAILED(rv))
             return rv;
 
@@ -296,15 +297,16 @@ nsresult nsReadConfig::openAndEvaluateJS
         if (obscureValue > 0) {
 
             // Unobscure file by subtracting some value from every char.
             for (uint32_t i = 0; i < amt; i++)
                 buf[i] -= obscureValue;
         }
         rv = EvaluateAdminConfigScript(buf, amt, aFileName,
                                        false, true,
-                                       isEncoded ? true:false);
+                                       isEncoded,
+                                       !isBinDir);
     }
     inStr->Close();
     free(buf);
 
     return rv;
 }
--- a/extensions/pref/autoconfig/src/prefcalls.js
+++ b/extensions/pref/autoconfig/src/prefcalls.js
@@ -1,14 +1,16 @@
 /* global processLDAPValues */
 /* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* globals gSandbox */
+
 const nsILDAPURL = Ci.nsILDAPURL;
 const LDAPURLContractID = "@mozilla.org/network/ldap-url;1";
 const nsILDAPSyncQuery = Ci.nsILDAPSyncQuery;
 const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1";
 const nsIPrefService = Ci.nsIPrefService;
 const PrefServiceContractID = "@mozilla.org/preferences-service;1";
 
 // ChromeUtils isn't available here, so we can't use Services.*
@@ -204,8 +206,30 @@ function getenv(name) {
         var environment = Cc["@mozilla.org/process/environment;1"].
             getService(Ci.nsIEnvironment);
         return environment.get(name);
     } catch (e) {
         displayError("getEnvironment", e);
     }
     return undefined;
 }
+
+var APIs = {
+    pref,
+    defaultPref,
+    lockPref,
+    unlockPref,
+    getPref,
+    clearPref,
+    setLDAPVersion,
+    getLDAPAttributes,
+    getLDAPValue,
+    displayError,
+    getenv,
+};
+
+for (let [defineAs, func] of Object.entries(APIs)) {
+    Cu.exportFunction(func, gSandbox, {defineAs});
+}
+
+Object.defineProperty(Cu.waiveXrays(gSandbox), "gIsUTF8", {
+  get: Cu.exportFunction(() => gIsUTF8, gSandbox),
+});
new file mode 100644
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg
@@ -0,0 +1,3 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+lockPref("_test.string.typeofComponents", typeof Components);
--- a/extensions/pref/autoconfig/test/unit/autoconfig.js
+++ b/extensions/pref/autoconfig/test/unit/autoconfig.js
@@ -1,5 +1,6 @@
 /* global pref */
+pref("general.config.sandbox_enabled", true);
 pref("general.config.filename", "autoconfig.cfg");
 pref("general.config.vendor", "autoconfig");
 pref("general.config.obscure_value", 0);
 
--- a/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
@@ -32,16 +32,21 @@ function run_test() {
     }, {
       filename: "autoconfig-latin1.cfg",
       prefs: {
         "_test.string.ASCII": "ASCII",
         "_test.string.non-ASCII": "日本語",
         "_test.string.getPref": "日本語",
         "_test.string.gIsUTF8": "false",
       }
+    }, {
+      filename: "autoconfig-chromecheck.cfg",
+      prefs: {
+        "_test.string.typeofComponents": "undefined",
+      }
     }];
 
     function testAutoConfig(test) {
       // Make sure pref values are unset.
       for (let prefName in test.prefs) {
         Assert.equal(Ci.nsIPrefBranch.PREF_INVALID, Services.prefs.getPrefType(prefName));
       }
 
--- a/extensions/pref/autoconfig/test/unit/xpcshell.ini
+++ b/extensions/pref/autoconfig/test/unit/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 head =
 skip-if = toolkit == 'android'
 support-files =
   autoconfig-all.cfg
   autoconfig-latin1.cfg
   autoconfig-utf8.cfg
+  autoconfig-chromecheck.cfg
   autoconfig.js
 
 [test_autoconfig.js]
 [test_autoconfig_nonascii.js]