tor-browser

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

commit 9e491f2a6fefcdeff634c3c9b6cdda127d836e3d
parent ee231a59a0abee2a974a5280b4e75031951a1e48
Author: David P. <daparks@mozilla.com>
Date:   Thu, 23 Oct 2025 22:07:12 +0000

Bug 1990634: Block manual and scheduled backups if managed profiles exist r=profiles-reviewers,mconley,kpatenio,mossop

There is are known issues (bug 1990980) with backups of managed profiles.  We
need to prevent the user from requesting backups until that issue is fixed.
This is done by making sure that browser.backup.archive.enabled and
browser.backup.restore.enabled are false if browser.profiles.created is true.

Differential Revision: https://phabricator.services.mozilla.com/D267709

Diffstat:
Mbrowser/components/backup/BackupService.sys.mjs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/backup/tests/xpcshell/test_BackupService.js | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/profiles/SelectableProfileService.sys.mjs | 12++++++++++++
3 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -19,6 +19,7 @@ import { } from "chrome://browser/content/backup/backup-constants.mjs"; import { BackupError } from "resource:///modules/backup/BackupError.mjs"; +const BACKUP_PREFS_UI_PREF_NAME = "browser.backup.preferences.ui.enabled"; const BACKUP_DIR_PREF_NAME = "browser.backup.location"; const BACKUP_ERROR_CODE_PREF_NAME = "browser.backup.errorCode"; const SCHEDULED_BACKUPS_ENABLED_PREF_NAME = "browser.backup.scheduled.enabled"; @@ -1025,6 +1026,15 @@ export class BackupService extends EventTarget { return null; } + static get #observedPrefs() { + return [ + BACKUP_PREFS_UI_PREF_NAME, + BACKUP_ARCHIVE_ENABLED_PREF_NAME, + BACKUP_RESTORE_ENABLED_PREF_NAME, + SCHEDULED_BACKUPS_ENABLED_PREF_NAME, + ]; + } + /** * Returns a reference to a BackupService singleton. If this is the first time * that this getter is accessed, this causes the BackupService singleton to be @@ -3756,6 +3766,15 @@ export class BackupService extends EventTarget { Services.obs.addObserver(this.#observer, "session-cookie-changed"); Services.obs.addObserver(this.#observer, "newtab-linkBlocked"); Services.obs.addObserver(this.#observer, "quit-application-granted"); + Services.obs.addObserver(this.#observer, "sps-profile-created"); + + for (const pref of BackupService.#observedPrefs) { + Services.prefs.addObserver(pref, this.#observer); + } + if (lazy.SelectableProfileService.hasCreatedSelectableProfiles()) { + // Trigger our observer, to make sure backups are disabled. + this.onObserve(null, "sps-profile-created", "true"); + } } /** @@ -3792,12 +3811,26 @@ export class BackupService extends EventTarget { Services.obs.removeObserver(this.#observer, "session-cookie-changed"); Services.obs.removeObserver(this.#observer, "newtab-linkBlocked"); Services.obs.removeObserver(this.#observer, "quit-application-granted"); + + for (const pref of BackupService.#observedPrefs) { + Services.prefs.removeObserver(pref, this.#observer); + } + this.#observer = null; this.#regenerationDebouncer.disarm(); this.#backupWriteAbortController.abort(); } + #disableBackupAndRestore() { + if (lazy.SelectableProfileService.hasCreatedSelectableProfiles()) { + // This will end up clearing SCHEDULED_BACKUPS_ENABLED_PREF_NAME. + this.setScheduledBackups(false); + Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false); + Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, false); + } + } + /** * Called by this.#observer on idle from the nsIUserIdleService or * quit-application-granted from the nsIObserverService. Exposed as a public @@ -3867,6 +3900,29 @@ export class BackupService extends EventTarget { } break; } + case "sps-profile-created": { + // Disallow (scheduled and manual) backups of selectable profiles and + // restores from selectable profiles. See bug 1990980. + if (data == "true") { + this.#disableBackupAndRestore(); + } else { + lazy.logConsole.error("Received sps-profile-created = false"); + } + break; + } + case "nsPref:changed": { + switch (data) { + case BACKUP_ARCHIVE_ENABLED_PREF_NAME: + case BACKUP_RESTORE_ENABLED_PREF_NAME: + case SCHEDULED_BACKUPS_ENABLED_PREF_NAME: + case BACKUP_PREFS_UI_PREF_NAME: + // Disallow (scheduled and manual) backups of selectable profiles and + // restores from selectable profiles. See bug 1990980. + this.#disableBackupAndRestore(); + break; + } + break; + } } } diff --git a/browser/components/backup/tests/xpcshell/test_BackupService.js b/browser/components/backup/tests/xpcshell/test_BackupService.js @@ -808,6 +808,126 @@ add_task( ); /** + * Tests that the existence of selectable profiles prevent backups (see bug + * 1990980). + * + * @param {boolean} aSetCreatedSelectableProfilesBeforeSchedulingBackups + * If true (respectively, false), set browser.profiles.created before + * (respectively, after) attempting to setScheduledBackups. + */ +async function testSelectableProfilesPreventBackup( + aSetCreatedSelectableProfilesBeforeSchedulingBackups +) { + let sandbox = sinon.createSandbox(); + Services.fog.testResetFOG(); + const TEST_UID = "ThisIsMyTestUID"; + const TEST_EMAIL = "foxy@mozilla.org"; + sandbox.stub(UIState, "get").returns({ + status: UIState.STATUS_SIGNED_IN, + uid: TEST_UID, + email: TEST_EMAIL, + }); + + const SELECTABLE_PROFILES_CREATED_PREF = "browser.profiles.created"; + + // Make sure backup and restore are enabled. + const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled"; + const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled"; + Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true); + Assert.ok( + Services.prefs.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME), + `${BACKUP_ARCHIVE_ENABLED_PREF_NAME} = true` + ); + Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true); + Assert.ok( + Services.prefs.getBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME), + `${BACKUP_RESTORE_ENABLED_PREF_NAME} = true` + ); + + // Make sure created profiles pref is not set until we want it to be. + Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF, false); + Assert.ok( + !Services.prefs.getBoolPref(SELECTABLE_PROFILES_CREATED_PREF), + `${SELECTABLE_PROFILES_CREATED_PREF} = false` + ); + + const setHasSelectableProfiles = () => { + // "Enable" selectable profiles by pref. + Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF, true); + Assert.ok( + Services.prefs.getBoolPref(SELECTABLE_PROFILES_CREATED_PREF), + "set has selectable profiles | browser.profiles.created = true" + ); + }; + + if (aSetCreatedSelectableProfilesBeforeSchedulingBackups) { + setHasSelectableProfiles(); + } + + let bs = new BackupService({}); + bs.initBackupScheduler(); + bs.setScheduledBackups(true); + + const SCHEDULED_BACKUP_ENABLED_PREF = "browser.backup.scheduled.enabled"; + if (!aSetCreatedSelectableProfilesBeforeSchedulingBackups) { + Assert.ok( + Services.prefs.getBoolPref(SCHEDULED_BACKUP_ENABLED_PREF, true), + "enabled scheduled backups | browser.backup.scheduled.enabled = true" + ); + registerCleanupFunction(() => { + // Just in case the test fails. + bs.setScheduledBackups(false); + info("cleared scheduled backups"); + }); + + setHasSelectableProfiles(); + } + + // browser.backup.archive.enabled, browser.backup.restore.enabled and + // browser.backup.scheduled.enabled should ignore earlier setting and be + // false. + Assert.ok( + !Services.prefs.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true), + `backups are disabled | ${BACKUP_ARCHIVE_ENABLED_PREF_NAME} = false` + ); + Assert.ok( + !Services.prefs.getBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true), + `restore is disabled | ${BACKUP_RESTORE_ENABLED_PREF_NAME} = false` + ); + Assert.ok( + !Services.prefs.getBoolPref(SCHEDULED_BACKUP_ENABLED_PREF, true), + `scheduled backups are not enabled | ${SCHEDULED_BACKUP_ENABLED_PREF} = false` + ); + + // Test cleanup + if (!aSetCreatedSelectableProfilesBeforeSchedulingBackups) { + bs.uninitBackupScheduler(); + } + + Services.prefs.clearUserPref(SELECTABLE_PROFILES_CREATED_PREF); + // These tests assume that backups and restores have been enabled. + Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true); + Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true); + sandbox.restore(); +} + +add_task( + async function test_managing_profiles_before_scheduling_prevents_backup() { + await testSelectableProfilesPreventBackup( + true /* aSetCreatedSelectableProfilesBeforeSchedulingBackups */ + ); + } +); + +add_task( + async function test_managing_profiles_after_scheduling_prevents_backup() { + await testSelectableProfilesPreventBackup( + false /* aSetCreatedSelectableProfilesBeforeSchedulingBackups */ + ); + } +); + +/** * Tests that if there's a post-recovery.json file in the profile directory * when checkForPostRecovery() is called, that it is processed, and the * postRecovery methods on the associated BackupResources are called with the diff --git a/browser/components/profiles/SelectableProfileService.sys.mjs b/browser/components/profiles/SelectableProfileService.sys.mjs @@ -231,6 +231,14 @@ class SelectableProfileServiceClass extends EventEmitter { () => this.updateEnabledState(), "profile-after-change" ); + + Services.prefs.addObserver(PROFILES_CREATED_PREF_NAME, () => + Services.obs.notifyObservers( + null, + "sps-profile-created", + lazy.PROFILES_CREATED ? "true" : "false" + ) + ); } // Migrate any early users who created profiles before the datastore service @@ -242,6 +250,10 @@ class SelectableProfileServiceClass extends EventEmitter { } } + hasCreatedSelectableProfiles() { + return Services.prefs.getBoolPref(PROFILES_CREATED_PREF_NAME, false); + } + #getEnabledState() { if (!Services.policies.isAllowed("profileManagement")) { return false;