Bug 1455601 - Sandbox AutoConfig if sandbox_enabled pref is set. r?kmag draft
authorMichael Kaply <mozilla@kaply.com>
Wed, 23 May 2018 12:21:53 -0500
changeset 798955 ad42d9c4fc178abff2730a8e0d6f63ef01264e35
parent 798084 b75acf9652937ce79a9bf02de843c100db0e5ec7
push id110896
push usermozilla@kaply.com
push dateWed, 23 May 2018 19:27:18 +0000
reviewerskmag
bugs1455601
milestone62.0a1
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]