tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mbrowser/app/profile/000-tor-browser.js | 1+
Mbrowser/components/BrowserComponents.manifest | 1+
Mbrowser/components/places/PlacesUIUtils.sys.mjs | 6+++++-
Mbrowser/components/places/content/controller.js | 1+
Mdom/base/ContentAreaDropListener.sys.mjs | 16++++++++++++++--
Mtoolkit/components/places/PlacesUtils.sys.mjs | 4++++
Atoolkit/modules/DragDropFilter.sys.mjs | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/modules/moz.build | 1+
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", ]