commit 9b797a78f8b435bf641eaa89f3f0ebb92179eacb
parent cf3d98bdce7917a28a12acb4845d0973738b3126
Author: hackademix <giorgio@maone.net>
Date: Mon, 12 Dec 2022 21:09:15 +0100
TB 8324: Prevent DNS proxy bypasses caused by Drag&Drop
Bug 41613: Skip Drang & Drop filtering for DNS-safe URLs
Diffstat:
8 files changed, 166 insertions(+), 3 deletions(-)
diff --git a/browser/app/profile/000-tor-browser.js b/browser/app/profile/000-tor-browser.js
@@ -140,3 +140,4 @@ pref("browser.torMoat.loglevel", "Warn");
pref("browser.tordomainisolator.loglevel", "Warn");
pref("browser.torcircuitpanel.loglevel", "Log");
pref("browser.tor_android.log_level", "Info");
+pref("browser.dragdropfilter.log_level", "Warn");
diff --git a/browser/components/BrowserComponents.manifest b/browser/components/BrowserComponents.manifest
@@ -60,6 +60,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs
#endif
category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init
category browser-first-window-ready moz-src:///browser/modules/SecurityLevelNotification.sys.mjs SecurityLevelNotification.ready
+category browser-first-window-ready moz-src:///toolkit/modules/DragDropFilter.sys.mjs DragDropFilter.init
category browser-idle-startup moz-src:///browser/components/places/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars
category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes
diff --git a/browser/components/places/PlacesUIUtils.sys.mjs b/browser/components/places/PlacesUIUtils.sys.mjs
@@ -1774,7 +1774,11 @@ ChromeUtils.defineLazyGetter(PlacesUIUtils, "URI_FLAVORS", () => {
];
});
ChromeUtils.defineLazyGetter(PlacesUIUtils, "SUPPORTED_FLAVORS", () => {
- return [...PlacesUIUtils.PLACES_FLAVORS, ...PlacesUIUtils.URI_FLAVORS];
+ return [
+ ...PlacesUIUtils.PLACES_FLAVORS,
+ ...PlacesUIUtils.URI_FLAVORS,
+ "application/x-torbrowser-opaque",
+ ];
});
ChromeUtils.defineLazyGetter(PlacesUIUtils, "promptLocalization", () => {
diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js
@@ -1277,6 +1277,7 @@ PlacesController.prototype = {
[
PlacesUtils.TYPE_X_MOZ_PLACE,
PlacesUtils.TYPE_X_MOZ_URL,
+ "application/x-torbrowser-opaque",
PlacesUtils.TYPE_PLAINTEXT,
].forEach(type => xferable.addDataFlavor(type));
diff --git a/dom/base/ContentAreaDropListener.sys.mjs b/dom/base/ContentAreaDropListener.sys.mjs
@@ -2,6 +2,12 @@
* 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/. */
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ OpaqueDrag: "moz-src:///toolkit/modules/DragDropFilter.sys.mjs",
+});
+
// This component is used for handling dragover and drop of urls.
//
// It checks to see whether a drop of a url is allowed. For instance, a url
@@ -40,10 +46,15 @@ ContentAreaDropListener.prototype = {
}
}
- type = "text/x-moz-url";
- if (types.contains(type)) {
+ for (let type of ["text/x-moz-url", "application/x-torbrowser-opaque"]) {
+ if (!types.contains(type)) {
+ continue;
+ }
data = dt.mozGetDataAt(type, i);
if (data) {
+ if (type === "application/x-torbrowser-opaque") {
+ ({ type, value: data = "" } = lazy.OpaqueDrag.retrieve(data));
+ }
let lines = data.split("\n");
for (let i = 0, length = lines.length; i < length; i += 2) {
this._addLink(links, lines[i], lines[i + 1], type);
@@ -236,6 +247,7 @@ ContentAreaDropListener.prototype = {
if (
!types.includes("application/x-moz-file") &&
!types.includes("text/x-moz-url") &&
+ !types.includes("application/x-torbrowser-opaque") &&
!types.includes("text/uri-list") &&
!types.includes("text/x-moz-text-internal") &&
!types.includes("text/plain")
diff --git a/toolkit/components/places/PlacesUtils.sys.mjs b/toolkit/components/places/PlacesUtils.sys.mjs
@@ -12,6 +12,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Bookmarks: "resource://gre/modules/Bookmarks.sys.mjs",
History: "resource://gre/modules/History.sys.mjs",
+ OpaqueDrag: "moz-src:///toolkit/modules/DragDropFilter.sys.mjs",
PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});
@@ -1230,6 +1231,9 @@ export var PlacesUtils = {
/** @type {{uri: string, title: string, type: string}[]} */
let validNodes = [];
let invalidNodes = [];
+ if (type === "application/x-torbrowser-opaque") {
+ ({ value: blob, type } = lazy.OpaqueDrag.retrieve(blob));
+ }
switch (type) {
case this.TYPE_X_MOZ_PLACE:
case this.TYPE_X_MOZ_PLACE_SEPARATOR:
diff --git a/toolkit/modules/DragDropFilter.sys.mjs b/toolkit/modules/DragDropFilter.sys.mjs
@@ -0,0 +1,139 @@
+/*************************************************************************
+ * Drag and Drop Handler.
+ *
+ * Implements an observer that filters drag events to prevent OS
+ * access to URLs (a potential proxy bypass vector).
+ *************************************************************************/
+
+const lazy = {};
+
+ChromeUtils.defineLazyGetter(lazy, "logger", () => {
+ // Keep the logger lazy, because it is used only in the parent process.
+ // For some reason, Mozilla considers reading the preference linked to the
+ // level in the children illegal (and triggers a crash when
+ // fission.enforceBlocklistedPrefsInSubprocesses is true).
+ // (Or maybe this crash used to happen when the logger was not lazy, and maybe
+ // the preferences were not ready, yet?)
+ return console.createInstance({
+ maxLogLevelPref: "browser.dragdropfilter.log_level",
+ prefix: "DragDropFilter",
+ });
+});
+
+const URLISH_TYPES = Object.freeze([
+ "text/x-moz-url",
+ "text/x-moz-url-data",
+ "text/uri-list",
+ "application/x-moz-file-promise-url",
+]);
+
+const MAIN_PROCESS =
+ Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
+
+const EMPTY_PAYLOAD = {};
+export const OpaqueDrag = {
+ listening: false,
+ payload: EMPTY_PAYLOAD,
+ store(value, type) {
+ let opaqueKey = crypto.randomUUID();
+ this.payload = { opaqueKey, value, type };
+ if (!this.listening && MAIN_PROCESS) {
+ Services.ppmm.addMessageListener(
+ "DragDropFilter:GetOpaqueDrag",
+ () => this.payload
+ );
+ this.listening = true;
+ }
+ return opaqueKey;
+ },
+ retrieve(key) {
+ let { opaqueKey, value, type } = this.payload;
+ if (opaqueKey === key) {
+ return { value, type };
+ }
+ if (!MAIN_PROCESS) {
+ this.payload = Services.cpmm.sendSyncMessage(
+ "DragDropFilter:GetOpaqueDrag"
+ )[0];
+ if (key === this.payload.opaqueKey) {
+ return this.retrieve(key);
+ }
+ }
+ return EMPTY_PAYLOAD;
+ },
+};
+
+export const DragDropFilter = {
+ init() {
+ if (MAIN_PROCESS) {
+ lazy.logger.info(
+ "Observed profile-after-change: registering the observer."
+ );
+ // We want to update our status in the main process only, in order to
+ // serve the same opaque drag payload in every process.
+ try {
+ Services.obs.addObserver(this, "on-datatransfer-available");
+ } catch (e) {
+ lazy.logger.error("Failed to register drag observer", e);
+ }
+ }
+ },
+
+ observe(subject, topic) {
+ if (topic === "on-datatransfer-available") {
+ lazy.logger.debug("The DataTransfer is available");
+ this.filterDataTransferURLs(subject);
+ }
+ },
+
+ filterDataTransferURLs(aDataTransfer) {
+ for (let i = 0, count = aDataTransfer.mozItemCount; i < count; ++i) {
+ lazy.logger.debug(`Inspecting the data transfer: ${i}.`);
+ const types = aDataTransfer.mozTypesAt(i);
+ const urlType = "text/x-moz-url";
+ // Fallback url type, to be parsed by this browser but not externally
+ const INTERNAL_FALLBACK = "application/x-torbrowser-opaque";
+ if (types.contains(urlType)) {
+ const links = aDataTransfer.mozGetDataAt(urlType, i);
+ // Skip DNS-safe URLs (no hostname, e.g. RFC 3966 tel:)
+ const mayLeakDNS = links.split("\n").some(link => {
+ return URL.parse(link)?.hostname ?? false;
+ });
+ if (!mayLeakDNS) {
+ continue;
+ }
+ const opaqueKey = OpaqueDrag.store(links, urlType);
+ aDataTransfer.mozSetDataAt(INTERNAL_FALLBACK, opaqueKey, i);
+ }
+ for (const maybeUrlType of types) {
+ lazy.logger.debug(`Type is: ${maybeUrlType}.`);
+ if (URLISH_TYPES.includes(maybeUrlType)) {
+ lazy.logger.info(
+ `Removing transfer data ${aDataTransfer.mozGetDataAt(
+ maybeUrlType,
+ i
+ )}`
+ );
+ // Once we find a URL, we remove also all the other types that can be
+ // read outside the browser, to be sure the URL is not leaked.
+ for (const type of types) {
+ if (
+ type !== INTERNAL_FALLBACK &&
+ type !== "text/x-moz-place" && // don't touch bookmarks
+ type !== "application/x-moz-file" // don't touch downloads
+ ) {
+ aDataTransfer.mozClearDataAt(type, i);
+ }
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ opaqueDrag: {
+ get(opaqueKey) {
+ return OpaqueDrag.retrieve(opaqueKey);
+ },
+ },
+};
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
@@ -225,6 +225,7 @@ EXTRA_JS_MODULES += [
MOZ_SRC_FILES += [
"ColorPickerPanel.sys.mjs",
"DateTimePickerPanel.sys.mjs",
+ "DragDropFilter.sys.mjs",
"InputPickerPanelCommon.sys.mjs",
]