[tor-commits] [tor-browser] 01/05: Bug 40925: Implemented the SecurityLevel backend

gitolite role git at cupani.torproject.org
Fri Jul 15 19:15:22 UTC 2022


This is an automated email from the git hooks/post-receive script.

richard pushed a commit to branch tor-browser-91.11.0esr-12.0-1
in repository tor-browser.

commit d5e39bf2f23cc179fe8359bbf9d0a07f88b0bf01
Author: Pier Angelo Vendrame <pierov at torproject.org>
AuthorDate: Fri Jul 8 16:19:41 2022 +0200

    Bug 40925: Implemented the SecurityLevel backend
---
 browser/components/securitylevel/SecurityLevel.jsm | 458 +++++++++++++++++++++
 .../securitylevel/SecurityLevel.manifest           |   1 +
 browser/components/securitylevel/components.conf   |  10 +
 browser/components/securitylevel/moz.build         |  12 +
 browser/installer/package-manifest.in              |   2 +
 5 files changed, 483 insertions(+)

diff --git a/browser/components/securitylevel/SecurityLevel.jsm b/browser/components/securitylevel/SecurityLevel.jsm
new file mode 100644
index 0000000000000..bd7c803511fff
--- /dev/null
+++ b/browser/components/securitylevel/SecurityLevel.jsm
@@ -0,0 +1,458 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["SecurityLevel"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const BrowserTopics = Object.freeze({
+  ProfileAfterChange: "profile-after-change",
+});
+
+const { ExtensionUtils } = ChromeUtils.import(
+  "resource://gre/modules/ExtensionUtils.jsm"
+);
+const { MessageChannel } = ChromeUtils.import(
+  "resource://gre/modules/MessageChannel.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
+});
+
+// Logger adapted from CustomizableUI.jsm
+XPCOMUtils.defineLazyGetter(this, "logger", () => {
+  let scope = {};
+  ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
+  let consoleOptions = {
+    maxLogLevel: "info",
+    prefix: "SecurityLevel",
+  };
+  return new scope.ConsoleAPI(consoleOptions);
+});
+
+// The Security Settings prefs in question.
+const kSliderPref = "extensions.torbutton.security_slider";
+const kCustomPref = "extensions.torbutton.security_custom";
+const kSliderMigration = "extensions.torbutton.security_slider_migration";
+
+// __getPrefValue(prefName)__
+// Returns the current value of a preference, regardless of its type.
+var getPrefValue = function(prefName) {
+  switch (Services.prefs.getPrefType(prefName)) {
+    case Services.prefs.PREF_BOOL:
+      return Services.prefs.getBoolPref(prefName);
+    case Services.prefs.PREF_INT:
+      return Services.prefs.getIntPref(prefName);
+    case Services.prefs.PREF_STRING:
+      return Services.prefs.getCharPref(prefName);
+    default:
+      return null;
+  }
+};
+
+// __bindPref(prefName, prefHandler, init)__
+// Applies prefHandler whenever the value of the pref changes.
+// If init is true, applies prefHandler to the current value.
+// Returns a zero-arg function that unbinds the pref.
+var bindPref = function(prefName, prefHandler, init = false) {
+  let update = () => {
+      prefHandler(getPrefValue(prefName));
+    },
+    observer = {
+      observe(subject, topic, data) {
+        if (data === prefName) {
+          update();
+        }
+      },
+    };
+  Services.prefs.addObserver(prefName, observer);
+  if (init) {
+    update();
+  }
+  return () => {
+    Services.prefs.removeObserver(prefName, observer);
+  };
+};
+
+// __bindPrefAndInit(prefName, prefHandler)__
+// Applies prefHandler to the current value of pref specified by prefName.
+// Re-applies prefHandler whenever the value of the pref changes.
+// Returns a zero-arg function that unbinds the pref.
+var bindPrefAndInit = (prefName, prefHandler) =>
+  bindPref(prefName, prefHandler, true);
+
+async function waitForExtensionMessage(extensionId, checker = () => {}) {
+  const { torWaitForExtensionMessage } = ExtensionParent;
+  if (torWaitForExtensionMessage) {
+    return torWaitForExtensionMessage(extensionId, checker);
+  }
+
+  // Old messaging <= 78
+  return new Promise(resolve => {
+    const listener = ({ data }) => {
+      for (const msg of data) {
+        if (msg.recipient.extensionId === extensionId) {
+          const deserialized = msg.data.deserialize({});
+          if (checker(deserialized)) {
+            Services.mm.removeMessageListener(
+              "MessageChannel:Messages",
+              listener
+            );
+            resolve(deserialized);
+          }
+        }
+      }
+    };
+    Services.mm.addMessageListener("MessageChannel:Messages", listener);
+  });
+}
+
+async function sendExtensionMessage(extensionId, message) {
+  const { torSendExtensionMessage } = ExtensionParent;
+  if (torSendExtensionMessage) {
+    return torSendExtensionMessage(extensionId, message);
+  }
+
+  // Old messaging <= 78
+  Services.cpmm.sendAsyncMessage("MessageChannel:Messages", [
+    {
+      messageName: "Extension:Message",
+      sender: { id: extensionId, extensionId },
+      recipient: { extensionId },
+      data: new StructuredCloneHolder(message),
+      channelId: ExtensionUtils.getUniqueId(),
+      responseType: MessageChannel.RESPONSE_NONE,
+    },
+  ]);
+  return undefined;
+}
+
+// ## NoScript settings
+
+// Minimum and maximum capability states as controlled by NoScript.
+const max_caps = [
+  "fetch",
+  "font",
+  "frame",
+  "media",
+  "object",
+  "other",
+  "script",
+  "webgl",
+  "noscript",
+];
+const min_caps = ["frame", "other", "noscript"];
+
+// Untrusted capabilities for [Standard, Safer, Safest] safety levels.
+const untrusted_caps = [
+  max_caps, // standard safety: neither http nor https
+  ["frame", "font", "object", "other", "noscript"], // safer: http
+  min_caps, // safest: neither http nor https
+];
+
+// Default capabilities for [Standard, Safer, Safest] safety levels.
+const default_caps = [
+  max_caps, // standard: both http and https
+  ["fetch", "font", "frame", "object", "other", "script", "noscript"], // safer: https only
+  min_caps, // safest: both http and https
+];
+
+// __noscriptSettings(safetyLevel)__.
+// Produces NoScript settings with policy according to
+// the safetyLevel which can be:
+// 0 = Standard, 1 = Safer, 2 = Safest
+//
+// At the "Standard" safety level, we leave all sites at
+// default with maximal capabilities. Essentially no content
+// is blocked.
+//
+// At "Safer", we set all http sites to untrusted,
+// and all https sites to default. Scripts are only permitted
+// on https sites. Neither type of site is supposed to allow
+// media, but both allow fonts (as we used in legacy NoScript).
+//
+// At "Safest", all sites are at default with minimal
+// capabilities. Most things are blocked.
+let noscriptSettings = safetyLevel => ({
+  __meta: {
+    name: "updateSettings",
+    recipientInfo: null,
+  },
+  policy: {
+    DEFAULT: {
+      capabilities: default_caps[safetyLevel],
+      temp: false,
+    },
+    TRUSTED: {
+      capabilities: max_caps,
+      temp: false,
+    },
+    UNTRUSTED: {
+      capabilities: untrusted_caps[safetyLevel],
+      temp: false,
+    },
+    sites: {
+      trusted: [],
+      untrusted: [[], ["http:"], []][safetyLevel],
+      custom: {},
+      temp: [],
+    },
+    enforced: true,
+    autoAllowTop: false,
+  },
+  isTorBrowser: true,
+  tabId: -1,
+});
+
+// ## Communications
+
+// The extension ID for NoScript (WebExtension)
+const noscriptID = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
+
+// Ensure binding only occurs once.
+let initialized = false;
+
+// __initialize()__.
+// The main function that binds the NoScript settings to the security
+// slider pref state.
+var initializeNoScriptControl = () => {
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  try {
+    // LegacyExtensionContext is not there anymore. Using raw
+    // Services.cpmm.sendAsyncMessage mechanism to communicate with
+    // NoScript.
+
+    // The component that handles WebExtensions' sendMessage.
+
+    // __setNoScriptSettings(settings)__.
+    // NoScript listens for internal settings with onMessage. We can send
+    // a new settings JSON object according to NoScript's
+    // protocol and these are accepted! See the use of
+    // `browser.runtime.onMessage.addListener(...)` in NoScript's bg/main.js.
+
+    // TODO: Is there a better way?
+    let sendNoScriptSettings = settings =>
+      sendExtensionMessage(noscriptID, settings);
+
+    // __setNoScriptSafetyLevel(safetyLevel)__.
+    // Set NoScript settings according to a particular safety level
+    // (security slider level): 0 = Standard, 1 = Safer, 2 = Safest
+    let setNoScriptSafetyLevel = safetyLevel =>
+      sendNoScriptSettings(noscriptSettings(safetyLevel));
+
+    // __securitySliderToSafetyLevel(sliderState)__.
+    // Converts the "extensions.torbutton.security_slider" pref value
+    // to a "safety level" value: 0 = Standard, 1 = Safer, 2 = Safest
+    let securitySliderToSafetyLevel = sliderState =>
+      [undefined, 2, 1, 1, 0][sliderState];
+
+    // Wait for the first message from NoScript to arrive, and then
+    // bind the security_slider pref to the NoScript settings.
+    let messageListener = a => {
+      try {
+        logger.debug("Message received from NoScript:", a);
+        let noscriptPersist = Services.prefs.getBoolPref(
+          "extensions.torbutton.noscript_persist",
+          false
+        );
+        let noscriptInited = Services.prefs.getBoolPref(
+          "extensions.torbutton.noscript_inited",
+          false
+        );
+        // Set the noscript safety level once if we have never run noscript
+        // before, or if we are not allowing noscript per-site settings to be
+        // persisted between browser sessions. Otherwise make sure that the
+        // security slider position, if changed, will rewrite the noscript
+        // settings.
+        bindPref(
+          kSliderPref,
+          sliderState =>
+            setNoScriptSafetyLevel(securitySliderToSafetyLevel(sliderState)),
+          !noscriptPersist || !noscriptInited
+        );
+        if (!noscriptInited) {
+          Services.prefs.setBoolPref(
+            "extensions.torbutton.noscript_inited",
+            true
+          );
+        }
+      } catch (e) {
+        logger.exception(e);
+      }
+    };
+    waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
+      messageListener
+    );
+    logger.info("Listening for messages from NoScript.");
+  } catch (e) {
+    logger.exception(e);
+  }
+};
+
+// ### Constants
+
+// __kSecuritySettings__.
+// A table of all prefs bound to the security slider, and the value
+// for each security setting. Note that 2-m and 3-m are identical,
+// corresponding to the old 2-medium-high setting. We also separately
+// bind NoScript settings to the extensions.torbutton.security_slider
+// (see noscript-control.js).
+/* eslint-disable */
+const kSecuritySettings = {
+  // Preference name :                                          [0, 1-high 2-m    3-m    4-low]
+  "javascript.options.ion" :                                    [,  false, false, false, true ],
+  "javascript.options.baselinejit" :                            [,  false, false, false, true ],
+  "javascript.options.native_regexp" :                          [,  false, false, false, true ],
+  "mathml.disabled" :                                           [,  true,  true,  true,  false],
+  "gfx.font_rendering.graphite.enabled" :                       [,  false, false, false, true ],
+  "gfx.font_rendering.opentype_svg.enabled" :                   [,  false, false, false, true ],
+  "svg.disabled" :                                              [,  true,  false, false, false],
+  "javascript.options.asmjs" :                                  [,  false, false, false, true ],
+  "javascript.options.wasm" :                                   [,  false, false, false, true ],
+  "dom.security.https_only_mode_send_http_background_request" : [,  false, false, false, true ],
+};
+/* eslint-enable */
+
+// ### Prefs
+
+// __write_setting_to_prefs(settingIndex)__.
+// Take a given setting index and write the appropriate pref values
+// to the pref database.
+var write_setting_to_prefs = function(settingIndex) {
+  Object.keys(kSecuritySettings).forEach(prefName =>
+    Services.prefs.setBoolPref(
+      prefName,
+      kSecuritySettings[prefName][settingIndex]
+    )
+  );
+};
+
+// __read_setting_from_prefs()__.
+// Read the current pref values, and decide if any of our
+// security settings matches. Otherwise return null.
+var read_setting_from_prefs = function(prefNames) {
+  prefNames = prefNames || Object.keys(kSecuritySettings);
+  for (let settingIndex of [1, 2, 3, 4]) {
+    let possibleSetting = true;
+    // For the given settingIndex, check if all current pref values
+    // match the setting.
+    for (let prefName of prefNames) {
+      if (
+        kSecuritySettings[prefName][settingIndex] !==
+        Services.prefs.getBoolPref(prefName)
+      ) {
+        possibleSetting = false;
+      }
+    }
+    if (possibleSetting) {
+      // We have a match!
+      return settingIndex;
+    }
+  }
+  // No matching setting; return null.
+  return null;
+};
+
+// __watch_security_prefs(onSettingChanged)__.
+// Whenever a pref bound to the security slider changes, onSettingChanged
+// is called with the new security setting value (1,2,3,4 or null).
+// Returns a zero-arg function that ends this binding.
+var watch_security_prefs = function(onSettingChanged) {
+  let prefNames = Object.keys(kSecuritySettings);
+  let unbindFuncs = [];
+  for (let prefName of prefNames) {
+    unbindFuncs.push(
+      bindPrefAndInit(prefName, () =>
+        onSettingChanged(read_setting_from_prefs())
+      )
+    );
+  }
+  // Call all the unbind functions.
+  return () => unbindFuncs.forEach(unbind => unbind());
+};
+
+// __initialized__.
+// Have we called initialize() yet?
+var initializedSecPrefs = false;
+
+// __initialize()__.
+// Defines the behavior of "extensions.torbutton.security_custom",
+// "extensions.torbutton.security_slider", and the security-sensitive
+// prefs declared in kSecuritySettings.
+var initializeSecurityPrefs = function() {
+  // Only run once.
+  if (initializedSecPrefs) {
+    return;
+  }
+  logger.info("Initializing security-prefs.js");
+  initializedSecPrefs = true;
+  // When security_custom is set to false, apply security_slider setting
+  // to the security-sensitive prefs.
+  bindPrefAndInit(kCustomPref, function(custom) {
+    if (custom === false) {
+      write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref));
+    }
+  });
+  // If security_slider is given a new value, then security_custom should
+  // be set to false.
+  bindPref(kSliderPref, function(prefIndex) {
+    Services.prefs.setBoolPref(kCustomPref, false);
+    write_setting_to_prefs(prefIndex);
+  });
+  // If a security-sensitive pref changes, then decide if the set of pref values
+  // constitutes a security_slider setting or a custom value.
+  watch_security_prefs(settingIndex => {
+    if (settingIndex === null) {
+      Services.prefs.setBoolPref(kCustomPref, true);
+    } else {
+      Services.prefs.setIntPref(kSliderPref, settingIndex);
+      Services.prefs.setBoolPref(kCustomPref, false);
+    }
+  });
+  // Migrate from old medium-low (3) to new medium (2).
+  if (
+    Services.prefs.getBoolPref(kCustomPref) === false &&
+    Services.prefs.getIntPref(kSliderPref) === 3
+  ) {
+    Services.prefs.setIntPref(kSliderPref, 2);
+    write_setting_to_prefs(2);
+  }
+
+  // Revert #33613 fix
+  if (Services.prefs.getIntPref(kSliderMigration, 0) < 2) {
+    // We can't differentiate between users having flipped `javascript.enabled`
+    // to `false` before it got governed by the security settings vs. those who
+    // had it flipped due to #33613. Reset the preference for everyone.
+    if (Services.prefs.getIntPref(kSliderPref) === 1) {
+      Services.prefs.setBoolPref("javascript.enabled", true);
+    }
+    Services.prefs.clearUserPref("media.webaudio.enabled");
+    Services.prefs.setIntPref(kSliderMigration, 2);
+  }
+  logger.info("security-prefs.js initialization complete");
+};
+
+// This class is used to initialize the security level stuff at the startup
+class SecurityLevel {
+  QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
+
+  init() {
+    initializeNoScriptControl();
+    initializeSecurityPrefs();
+  }
+
+  observe(aSubject, aTopic, aData) {
+    if (aTopic == BrowserTopics.ProfileAfterChange) {
+      this.init();
+      Services.obs.removeObserver(this, aTopic);
+    }
+  }
+}
diff --git a/browser/components/securitylevel/SecurityLevel.manifest b/browser/components/securitylevel/SecurityLevel.manifest
new file mode 100644
index 0000000000000..0bfbd2ba1ac7b
--- /dev/null
+++ b/browser/components/securitylevel/SecurityLevel.manifest
@@ -0,0 +1 @@
+category profile-after-change SecurityLevel @torproject.org/security-level;1
diff --git a/browser/components/securitylevel/components.conf b/browser/components/securitylevel/components.conf
new file mode 100644
index 0000000000000..4b9263ce7f0f2
--- /dev/null
+++ b/browser/components/securitylevel/components.conf
@@ -0,0 +1,10 @@
+Classes = [
+    {
+        "cid": "{c602ffe5-abf4-40d0-a944-26738b81efdb}",
+        "contract_ids": [
+            "@torproject.org/security-level;1",
+        ],
+        "jsm": "resource:///modules/SecurityLevel.jsm",
+        "constructor": "SecurityLevel",
+    }
+]
diff --git a/browser/components/securitylevel/moz.build b/browser/components/securitylevel/moz.build
index 2661ad7cb9f3d..33573a3706f97 100644
--- a/browser/components/securitylevel/moz.build
+++ b/browser/components/securitylevel/moz.build
@@ -1 +1,13 @@
 JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+    "SecurityLevel.jsm",
+]
+
+XPCOM_MANIFESTS += [
+    "components.conf",
+]
+
+EXTRA_COMPONENTS += [
+    "SecurityLevel.manifest",
+]
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index 8009a4d83ec5f..f32d574813a74 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -486,3 +486,5 @@ i686/gmp-clearkey/0.1/clearkey.dll
 ; build, which, practically speaking, is the case.
 @BINPATH@/gmp-clearkey/0.1/manifest.json
 #endif
+
+ at RESPATH@/browser/components/SecurityLevel.manifest

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tor-commits mailing list