tor-browser

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

commit 4958c48cef9558cf04518d6444a4cb856601253a
parent 66d71bee5be2322498fd6e01aca3a2f3f0746869
Author: hackademix <giorgio@maone.net>
Date:   Mon, 21 Aug 2023 13:13:57 +0200

BB 42019: Empty browser's clipboard on browser shutdown

Diffstat:
Mbrowser/app/profile/001-base-profile.js | 3+++
Mbrowser/components/BrowserComponents.manifest | 1+
Abrowser/modules/ClipboardPrivacy.sys.mjs | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/modules/moz.build | 1+
4 files changed, 183 insertions(+), 0 deletions(-)

diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js @@ -131,6 +131,9 @@ pref("browser.pagethumbnails.capturing_disabled", true); pref("browser.backup.enabled", false); pref("browser.backup.scheduled.enabled", false); +// Empty clipboard content from private windows on exit (tor-browser#42154) +pref("browser.privatebrowsing.preserveClipboard", false); + // tor-browser#42611: Do not include the URL of the image, when copying it. // Also, do not save clipboard in history/cloud. pref("clipboard.imageAsFile.enabled", false); diff --git a/browser/components/BrowserComponents.manifest b/browser/components/BrowserComponents.manifest @@ -59,6 +59,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs category browser-window-delayed-startup resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.maybeWarnAboutDisabledContentSandbox category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.observeContentSandboxPref #endif +category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.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/modules/ClipboardPrivacy.sys.mjs b/browser/modules/ClipboardPrivacy.sys.mjs @@ -0,0 +1,178 @@ +/* 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +/** + * Empty clipboard content from private windows on exit. + * + * See tor-browser#42154. + */ +export const ClipboardPrivacy = { + _lastClipboardHash: null, + _globalActivation: false, + _isPrivateClipboard: false, + _hasher: null, + _shuttingDown: false, + _log: null, + + _createTransferable() { + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + return trans; + }, + _computeClipboardHash() { + const flavors = ["text/x-moz-url", "text/plain"]; + if ( + !Services.clipboard.hasDataMatchingFlavors( + flavors, + Ci.nsIClipboard.kGlobalClipboard + ) + ) { + return null; + } + const trans = this._createTransferable(); + flavors.forEach(trans.addDataFlavor); + try { + Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); + const clipboardContent = {}; + trans.getAnyTransferData({}, clipboardContent); + const { data } = clipboardContent.value.QueryInterface( + Ci.nsISupportsString + ); + const bytes = new TextEncoder().encode(data); + const hasher = (this._hasher ||= Cc[ + "@mozilla.org/security/hash;1" + ].createInstance(Ci.nsICryptoHash)); + hasher.init(hasher.SHA256); + hasher.update(bytes, bytes.length); + return hasher.finish(true); + } catch (e) {} + return null; + }, + + init() { + this._log = console.createInstance({ + prefix: "ClipboardPrivacy", + }); + this._lastClipboardHash = this._computeClipboardHash(); + + // Here we track changes in active window / application, + // by filtering focus events and window closures. + const handleActivation = (win, activation) => { + if (activation) { + if (!this._globalActivation) { + // focus changed within this window, bail out. + return; + } + this._globalActivation = false; + } else if (!Services.focus.activeWindow) { + // focus is leaving this window: + // let's track whether it remains within the browser. + lazy.setTimeout(() => { + this._globalActivation = !Services.focus.activeWindow; + }, 100); + } + + const checkClipboardContent = () => { + const clipboardHash = this._computeClipboardHash(); + if (clipboardHash !== this._lastClipboardHash) { + this._isPrivateClipboard = + !activation && + (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || + lazy.PrivateBrowsingUtils.isWindowPrivate(win)); + this._lastClipboardHash = clipboardHash; + this._log.debug( + `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.` + ); + } + }; + + if (win.closed) { + checkClipboardContent(); + } else { + // defer clipboard access on DOM events to work-around tor-browser#42306 + lazy.setTimeout(checkClipboardContent, 0); + } + }; + const focusListener = e => + e.isTrusted && handleActivation(e.currentTarget, e.type === "focusin"); + const initWindow = win => { + for (const e of ["focusin", "focusout"]) { + win.addEventListener(e, focusListener); + } + }; + for (const w of Services.ww.getWindowEnumerator()) { + initWindow(w); + } + Services.ww.registerNotification((win, event) => { + switch (event) { + case "domwindowopened": + initWindow(win); + break; + case "domwindowclosed": + handleActivation(win, false); + if ( + this._isPrivateClipboard && + lazy.PrivateBrowsingUtils.isWindowPrivate(win) && + (this._shuttingDown || + !Array.from(Services.ww.getWindowEnumerator()).find( + w => + lazy.PrivateBrowsingUtils.isWindowPrivate(w) && + // We need to filter out the HIDDEN WebExtensions window, + // which might be private as well but is not UI-relevant. + !w.location.href.startsWith("chrome://extensions/") + )) + ) { + // no more private windows, empty private content if needed + this.emptyPrivate(); + } + } + }); + + lazy.AsyncShutdown.appShutdownConfirmed.addBlocker( + "ClipboardPrivacy: removing private data", + () => { + this._shuttingDown = true; + this.emptyPrivate(); + } + ); + }, + emptyPrivate() { + if ( + this._isPrivateClipboard && + !Services.prefs.getBoolPref( + "browser.privatebrowsing.preserveClipboard", + false + ) && + this._lastClipboardHash === this._computeClipboardHash() + ) { + // nsIClipboard.emptyClipboard() does nothing in Wayland: + // we'll set an empty string as a work-around. + const trans = this._createTransferable(); + const flavor = "text/plain"; + trans.addDataFlavor(flavor); + const emptyString = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + emptyString.data = ""; + trans.setTransferData(flavor, emptyString); + const { clipboard } = Services, + { kGlobalClipboard } = clipboard; + clipboard.setData(trans, null, kGlobalClipboard); + clipboard.emptyClipboard(kGlobalClipboard); + this._lastClipboardHash = null; + this._isPrivateClipboard = false; + this._log.info("Private clipboard emptied."); + } + }, +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build @@ -148,6 +148,7 @@ EXTRA_JS_MODULES += [ MOZ_SRC_FILES += [ "CanvasPermissionPromptHelper.sys.mjs", + "ClipboardPrivacy.sys.mjs", "ContextId.sys.mjs", "FaviconUtils.sys.mjs", "ObserverForwarder.sys.mjs",