commit 2c019453bd4ccb2b54e9e1097919e773f16d9c04 parent d0f3e2803ff78131b16a42d3b6edd6856a26ec04 Author: Kathy Brade <brade@pearlcrescent.com> Date: Fri, 13 Jan 2017 11:40:24 -0500 BB 4234: Use the Firefox Update Process for Base Browser. Windows: disable "runas" code path in updater (15201). Windows: avoid writing to the registry (16236). Also includes fixes for tickets 13047, 13301, 13356, 13594, 15406, 16014, 16909, 24476, and 25909. Also fix bug 27221: purge the startup cache if the Base Browser version changed (even if the Firefox version and build ID did not change), e.g., after a minor Base Browser update. Also fix 32616: Disable GetSecureOutputDirectoryPath() functionality. Bug 26048: potentially confusing "restart to update" message Within the update doorhanger, remove the misleading message that mentions that windows will be restored after an update is applied, and replace the "Restart and Restore" button label with an existing "Restart to update Tor Browser" string. Bug 28885: notify users that update is downloading Add a "Downloading Base Browser update" item which appears in the hamburger (app) menu while the update service is downloading a MAR file. Before this change, the browser did not indicate to the user that an update was in progress, which is especially confusing in Tor Browser because downloads often take some time. If the user clicks on the new menu item, the about dialog is opened to allow the user to see download progress. As part of this fix, the update service was changed to always show update-related messages in the hamburger menu, even if the update was started in the foreground via the about dialog or via the "Check for Tor Browser Update" toolbar menu item. This change is consistent with the Tor Browser goal of making sure users are informed about the update process. Removed #28885 parts of this patch which have been uplifted to Firefox. Diffstat:
30 files changed, 404 insertions(+), 64 deletions(-)
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in @@ -85,10 +85,12 @@ tools repackage:: $(DIST)/bin/$(MOZ_APP_NAME) $(objdir)/macbuild/Contents/MacOS- cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/firefox.icns '$(dist_dest)/Contents/Resources/firefox.icns' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/document.icns '$(dist_dest)/Contents/Resources/document.icns' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/Assets.car '$(dist_dest)/Contents/Resources/Assets.car' +ifndef BASE_BROWSER_UPDATE $(MKDIR) -p '$(dist_dest)/Contents/Library/LaunchServices' ifdef MOZ_UPDATER cp -f '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' '$(dist_dest)/Contents/Library/LaunchServices' endif +endif $(MKDIR) -p '$(dist_dest)/Contents/Frameworks' mv '$(dist_dest)/Contents/Resources/ChannelPrefs.framework' '$(dist_dest)/Contents/Frameworks' printf APPLMOZB > '$(dist_dest)/Contents/PkgInfo' diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js @@ -44,28 +44,21 @@ function init() { } // Include the build ID and display warning if this is an "a#" (nightly or aurora) build - let versionIdMap = new Map([ - ["base", "aboutDialog-version"], - ["base-nightly", "aboutDialog-version-nightly"], - ["base-arch", "aboutdialog-version-arch"], - ["base-arch-nightly", "aboutdialog-version-arch-nightly"], - ]); - let versionIdKey = "base"; + let versionId = "basebrowser-about-dialog-version"; let versionAttributes = { - version: AppConstants.MOZ_APP_VERSION_DISPLAY, + version: AppConstants.BASE_BROWSER_VERSION, + firefoxVersion: AppConstants.MOZ_APP_VERSION_DISPLAY, }; let arch = Services.sysinfo.get("arch"); if (["x86", "x86-64"].includes(arch)) { versionAttributes.bits = Services.appinfo.is64Bit ? 64 : 32; } else { - versionIdKey += "-arch"; versionAttributes.arch = arch; } let version = Services.appinfo.version; if (/a\d+$/.test(version)) { - versionIdKey += "-nightly"; let buildID = Services.appinfo.appBuildID; let year = buildID.slice(0, 4); let month = buildID.slice(4, 6); @@ -79,11 +72,7 @@ function init() { // Use Fluent arguments for append version and the architecture of the build let versionField = document.getElementById("version"); - document.l10n.setAttributes( - versionField, - versionIdMap.get(versionIdKey), - versionAttributes - ); + document.l10n.setAttributes(versionField, versionId, versionAttributes); // Show a release notes link if we have a URL. let relNotesLink = document.getElementById("releasenotes"); diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml @@ -35,6 +35,7 @@ <html:link rel="localization" href="branding/brand.ftl"/> <html:link rel="localization" href="browser/aboutDialog.ftl"/> + <html:link rel="localization" href="toolkit/global/base-browser.ftl"/> </linkset> <html:div id="aboutDialogContainer"> diff --git a/browser/base/moz.build b/browser/base/moz.build @@ -82,4 +82,7 @@ DEFINES["MOZ_APP_VERSION_DISPLAY"] = CONFIG["MOZ_APP_VERSION_DISPLAY"] # Do not include the Firefox app-license.html in about:license. # tor-browser#43901. +if CONFIG["BASE_BROWSER_UPDATE"]: + DEFINES["BASE_BROWSER_UPDATE"] = True + JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/BrowserContentHandler.sys.mjs b/browser/components/BrowserContentHandler.sys.mjs @@ -45,6 +45,9 @@ ChromeUtils.defineLazyGetter(lazy, "gWindowsAlertsService", () => { ?.QueryInterface(Ci.nsIWindowsAlertsService); }); +const FORK_VERSION_PREF = + "browser.startup.homepage_override.basebrowser.version"; + // One-time startup homepage override configurations const ONCE_DOMAINS = new Set(["mozilla.org", "firefox.com"]); const ONCE_PREF = "browser.startup.homepage_override.once"; @@ -120,7 +123,7 @@ const OVERRIDE_NEW_BUILD_ID = 3; * if this is the first run with a new profile. * OVERRIDE_NEW_MSTONE * if this is the first run with a build with a different Gecko milestone - * (i.e. right after an upgrade). + * or fork version (i.e. right after an upgrade). * OVERRIDE_NEW_BUILD_ID * if this is the first run with a new build ID of the same Gecko * milestone (i.e. after a nightly upgrade). @@ -139,6 +142,8 @@ function needHomepageOverride(updateMilestones = true) { var mstone = Services.appinfo.platformVersion; + var savedForkVersion = Services.prefs.getCharPref(FORK_VERSION_PREF, null); + var savedBuildID = Services.prefs.getCharPref( "browser.startup.homepage_override.buildID", "" @@ -167,10 +172,28 @@ function needHomepageOverride(updateMilestones = true) { "browser.startup.homepage_override.buildID", buildID ); + Services.prefs.setCharPref( + FORK_VERSION_PREF, + AppConstants.BASE_BROWSER_VERSION + ); } return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE; } + if (AppConstants.BASE_BROWSER_VERSION != savedForkVersion) { + if (updateMilestones) { + Services.prefs.setCharPref( + "browser.startup.homepage_override.buildID", + buildID + ); + Services.prefs.setCharPref( + FORK_VERSION_PREF, + AppConstants.BASE_BROWSER_VERSION + ); + } + return OVERRIDE_NEW_MSTONE; + } + if (buildID != savedBuildID) { if (updateMilestones) { Services.prefs.setCharPref( @@ -770,6 +793,10 @@ nsBrowserContentHandler.prototype = { "browser.startup.homepage_override.buildID", "unknown" ); + + // We do the same for the fork version. + let old_forkVersion = Services.prefs.getCharPref(FORK_VERSION_PREF, null); + override = needHomepageOverride(); // An object to measure the progress of handleUpdateSuccess let progress = { @@ -888,9 +915,10 @@ nsBrowserContentHandler.prototype = { } } + let old_version = old_forkVersion ? old_forkVersion : old_mstone; if ( update && - Services.vc.compare(update.appVersion, old_mstone) > 0 + Services.vc.compare(update.appVersion, old_version) > 0 ) { overridePage = getPostUpdateOverridePage( update, @@ -945,6 +973,10 @@ nsBrowserContentHandler.prototype = { ); overridePage = overridePage.replace("%OLD_VERSION%", old_mstone); + overridePage = overridePage.replace( + "%OLD_BASE_BROWSER_VERSION%", + old_forkVersion + ); break; } case OVERRIDE_NEW_BUILD_ID: { diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/browser/components/customizableui/content/panelUI.inc.xhtml @@ -202,7 +202,7 @@ hasicon="true" hidden="true"> <popupnotificationcontent id="update-restart-notification-content" orient="vertical"> - <description id="update-restart-description" data-lazy-l10n-id="appmenu-update-restart-message2"></description> + <description id="update-restart-description"> </description> </popupnotificationcontent> </popupnotification> diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js @@ -4670,7 +4670,7 @@ var gMainPane = { } // Initialize the Firefox Updates section. - let version = AppConstants.MOZ_APP_VERSION_DISPLAY; + let version = AppConstants.BASE_BROWSER_VERSION; // Include the build ID if this is an "a#" (nightly) build if (/a\d+$/.test(version)) { diff --git a/browser/config/mozconfigs/base-browser b/browser/config/mozconfigs/base-browser @@ -14,6 +14,7 @@ ac_add_options --enable-optimize ac_add_options --enable-rust-simd ac_add_options --disable-unverified-updates +ac_add_options --enable-base-browser-update ac_add_options --enable-bundled-fonts diff --git a/browser/installer/Makefile.in b/browser/installer/Makefile.in @@ -78,6 +78,10 @@ endif endif endif +ifdef BASE_BROWSER_UPDATE +DEFINES += -DBASE_BROWSER_UPDATE +endif + ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET))) DEFINES += -DMOZ_SHARED_MOZGLUE=1 endif diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in @@ -36,8 +36,10 @@ ; Mac bundle stuff @APPNAME@/Contents/Info.plist #ifdef MOZ_UPDATER +#ifndef BASE_BROWSER_UPDATE @APPNAME@/Contents/Library/LaunchServices #endif +#endif @APPNAME@/Contents/Frameworks @APPNAME@/Contents/PkgInfo @RESPATH@/firefox.icns diff --git a/build/application.ini.in b/build/application.ini.in @@ -52,5 +52,5 @@ ServerURL=@MOZ_CRASHREPORTER_URL@/submit?id=@MOZ_APP_ID@&version=@MOZ_APP_VERSIO #if MOZ_UPDATER [AppUpdate] -URL=https://@MOZ_APPUPDATE_HOST@/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml +URL=@BB_UPDATER_URL@/%CHANNEL%/%BUILD_TARGET%/%VERSION%/ALL #endif diff --git a/build/moz.build b/build/moz.build @@ -77,6 +77,7 @@ if CONFIG["MOZ_APP_BASENAME"]: "MAR_CHANNEL_ID", "MOZ_APP_REMOTINGNAME", "MOZ_CRASHREPORTER_URL", + "BB_UPDATER_URL", ): appini_defines[var] = CONFIG[var] diff --git a/build/moz.configure/update-programs.configure b/build/moz.configure/update-programs.configure @@ -291,3 +291,28 @@ with only_when("--enable-updater"): "You must enable at least one patch format when --enable-updater " "is used. Use --enable-bspatch or --enable-zucchini." ) + + +# Enable updater customization for Base Browser-based browsers +# ============================================================================== + +option("--enable-base-browser-update", help="Enable Base Browser update") + +set_config("BASE_BROWSER_UPDATE", True, when="--enable-base-browser-update") +set_define("BASE_BROWSER_UPDATE", True, when="--enable-base-browser-update") + + +# Updater URL +# ============================================================== + +option( + "--with-updater-url", + default="https://aus1.torproject.org/torbrowser/update_3/", + nargs=1, + help="Set the updater URL", +) + +set_config( + "BB_UPDATER_URL", + depends("--with-updater-url")(lambda x: x[0].rstrip("/")), +) diff --git a/mozconfig-linux-x86_64-dev b/mozconfig-linux-x86_64-dev @@ -15,6 +15,7 @@ ac_add_options --disable-strip ac_add_options --disable-install-strip ac_add_options --with-base-browser-version=dev-build +ac_add_options --disable-base-browser-update # Let's make sure no preference is enabling either Adobe's or Google's CDM. ac_add_options --disable-eme diff --git a/toolkit/.eslintrc.mjs b/toolkit/.eslintrc.mjs @@ -7,7 +7,7 @@ export default [ rules: { // XXX Bug 1326071 - This should be reduced down - probably to 20 or to // be removed & synced with the mozilla/recommended value. - complexity: ["error", 45], + complexity: ["error", 47], }, }, ]; diff --git a/toolkit/components/urlformatter/URLFormatter.sys.mjs b/toolkit/components/urlformatter/URLFormatter.sys.mjs @@ -134,6 +134,9 @@ nsURLFormatterService.prototype = { DISTRIBUTION_VERSION() { return this.distribution.version; }, + BB_VERSION() { + return AppConstants.BASE_BROWSER_VERSION; + }, }, formatURL: function uf_formatURL(aFormat) { diff --git a/toolkit/modules/AppConstants.sys.mjs b/toolkit/modules/AppConstants.sys.mjs @@ -276,4 +276,11 @@ export var AppConstants = Object.freeze({ #endif USE_LIBZ_RS: @USE_LIBZ_RS_BOOL@, + + BASE_BROWSER_UPDATE: +#ifdef BASE_BROWSER_UPDATE + true, +#else + false, +#endif }); diff --git a/toolkit/modules/UpdateUtils.sys.mjs b/toolkit/modules/UpdateUtils.sys.mjs @@ -81,7 +81,7 @@ export var UpdateUtils = { replacement = Services.appinfo.name; break; case "VERSION": - replacement = Services.appinfo.version; + replacement = AppConstants.BASE_BROWSER_VERSION; break; case "BUILD_ID": replacement = Services.appinfo.appBuildID; @@ -168,7 +168,8 @@ export var UpdateUtils = { * downloads and installs updates. This corresponds to whether or not the user * has selected "Automatically install updates" in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Base Browser and derivatives), this setting is shared + * across all profiles for the installation * and is read asynchronously from the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * @@ -184,7 +185,8 @@ export var UpdateUtils = { * updates" and "Check for updates but let you choose to install them" options * in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Base Browser and derivatives), this setting is shared + * across all profiles for the installation * and is written asynchronously to the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * @@ -256,7 +258,10 @@ export var UpdateUtils = { // setting is just to propagate it from a pref observer. This ensures that // the expected observers still get notified, even if a user manually // changes the pref value. - if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) { + if ( + AppConstants.BASE_BROWSER_UPDATE || + !UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED + ) { let initialConfig = {}; for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS @@ -324,7 +329,10 @@ export var UpdateUtils = { } } - if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { + if ( + AppConstants.BASE_BROWSER_UPDATE || + !this.PER_INSTALLATION_PREFS_SUPPORTED + ) { // If we don't have per-installation prefs, we use regular preferences. let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue); return Promise.resolve(prefValue); @@ -419,7 +427,10 @@ export var UpdateUtils = { ); } - if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { + if ( + AppConstants.BASE_BROWSER_UPDATE || + !this.PER_INSTALLATION_PREFS_SUPPORTED + ) { // If we don't have per-installation prefs, we use regular preferences. if (options.setDefaultOnly) { prefTypeFns.setProfileDefaultPref(prefName, value); @@ -555,14 +566,6 @@ UpdateUtils.PER_INSTALLATION_PREFS = { migrate: true, observerTopic: "auto-update-config-change", policyFn: () => { - if (!Services.policies.isAllowed("app-auto-updates-off")) { - // We aren't allowed to turn off auto-update - it is forced on. - return true; - } - if (!Services.policies.isAllowed("app-auto-updates-on")) { - // We aren't allowed to turn on auto-update - it is forced off. - return false; - } return null; }, }, diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build @@ -349,6 +349,9 @@ for var in ( ): DEFINES[f"{var}_BOOL"] = "true" if CONFIG[var] else "false" +if CONFIG["BASE_BROWSER_UPDATE"]: + DEFINES["BASE_BROWSER_UPDATE"] = True + JAR_MANIFESTS += ["jar.mn"] DEFINES["TOPOBJDIR"] = TOPOBJDIR diff --git a/toolkit/mozapps/extensions/AddonManager.sys.mjs b/toolkit/mozapps/extensions/AddonManager.sys.mjs @@ -39,6 +39,7 @@ const PREF_GLEAN_PING_ADDONS_UPDATED_IDLE_TIMEOUT_MS = "extensions.gleanPingAddons.updated.idleTimeout"; const PREF_GLEAN_PING_ADDONS_UPDATED_TESTING = "extensions.gleanPingAddons.updated.testing"; +const PREF_EM_LAST_FORK_VERSION = "extensions.lastBaseBrowserVersion"; const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; @@ -682,6 +683,24 @@ var AddonManagerInternal = { ); } + // To ensure that extension and plugin code gets a chance to run after + // each browser update, set appChanged = true when BASE_BROWSER_VERSION + // has changed even if the Mozilla app version has not changed. + const forkChanged = + AppConstants.BASE_BROWSER_VERSION !== + Services.prefs.getCharPref(PREF_EM_LAST_FORK_VERSION, ""); + if (forkChanged) { + // appChanged could be undefined (in case of a new profile) + if (appChanged === false) { + appChanged = true; + } + + Services.prefs.setCharPref( + PREF_EM_LAST_FORK_VERSION, + AppConstants.BASE_BROWSER_VERSION + ); + } + if (!AppConstants.NIGHTLY_BUILD) { PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs @@ -594,6 +594,11 @@ function areDirectoryEntriesWriteable(aDir) { * @return true if elevation is required, false otherwise */ function getElevationRequired() { + if (AppConstants.BASE_BROWSER_UPDATE) { + // To avoid potential security holes associated with running the updater + // process with elevated privileges, Tor Browser does not support elevation. + return false; + } if (AppConstants.platform != "macosx") { return false; } @@ -713,7 +718,10 @@ function getCanApplyUpdates() { return false; } - if (AppConstants.platform == "macosx" || AppConstants.platform == "win") { + if ( + !AppConstants.BASE_BROWSER_UPDATE && + (AppConstants.platform == "macosx" || AppConstants.platform == "win") + ) { LOG( "getCanApplyUpdates - bypass the write since elevation can be used " + "on macOS and Windows" @@ -1694,7 +1702,11 @@ function handleUpdateFailure(update) { ); cancelations++; Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations); - if (AppConstants.platform == "macosx") { + if (AppConstants.platform == "macosx" && AppConstants.BASE_BROWSER_UPDATE) { + cleanupActiveUpdates(); + update.statusText = + lazy.gUpdateBundle.GetStringFromName("elevationFailure"); + } else if (AppConstants.platform == "macosx") { let osxCancelations = Services.prefs.getIntPref( PREF_APP_UPDATE_CANCELATIONS_OSX, 0 @@ -1960,13 +1972,22 @@ function updateIsAtLeastAsOldAs(update, version, buildID) { } /** + * This returns the current version of the browser to use to check updates. + */ +function getCompatVersion() { + return AppConstants.BASE_BROWSER_VERSION + ? AppConstants.BASE_BROWSER_VERSION + : Services.appinfo.version; +} + +/** * This returns true if the passed update is the same version or older than * currently installed Firefox version. */ function updateIsAtLeastAsOldAsCurrentVersion(update) { return updateIsAtLeastAsOldAs( update, - Services.appinfo.version, + getCompatVersion(), Services.appinfo.appBuildID ); } @@ -2361,7 +2382,34 @@ class Update { this._patches.push(patch); } - if (!this._patches.length && !update.hasAttribute("unsupported")) { + if (update.hasAttribute("unsupported")) { + this.unsupported = "true" == update.getAttribute("unsupported"); + } else if (update.hasAttribute("minSupportedOSVersion")) { + let minOSVersion = update.getAttribute("minSupportedOSVersion"); + try { + let osVersion = Services.sysinfo.getProperty("version"); + this.unsupported = Services.vc.compare(osVersion, minOSVersion) < 0; + } catch (e) {} + } + if ( + !this.unsupported && + update.hasAttribute("minSupportedInstructionSet") + ) { + let minInstructionSet = update.getAttribute("minSupportedInstructionSet"); + if ( + ["MMX", "SSE", "SSE2", "SSE3", "SSE4A", "SSE4_1", "SSE4_2"].includes( + minInstructionSet + ) + ) { + try { + this.unsupported = !Services.sysinfo.getProperty( + "has" + minInstructionSet + ); + } catch (e) {} + } + } + + if (!this._patches.length && !this.unsupported) { throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); } @@ -2399,9 +2447,7 @@ class Update { if (!isNaN(attr.value)) { this.promptWaitTime = parseInt(attr.value); } - } else if (attr.name == "unsupported") { - this.unsupported = attr.value == "true"; - } else { + } else if (attr.name != "unsupported") { switch (attr.name) { case "appVersion": case "buildID": @@ -2427,7 +2473,7 @@ class Update { } if (!this.previousAppVersion) { - this.previousAppVersion = Services.appinfo.version; + this.previousAppVersion = getCompatVersion(); } if (!this.elevationFailure) { @@ -4252,7 +4298,7 @@ export class UpdateService { "UpdateService:downloadUpdate - Skipping download of update since " + "it is for an earlier or same application version and build ID.\n" + "current application version: " + - Services.appinfo.version + + getCompatVersion() + "\n" + "update application version : " + update.appVersion + @@ -5366,7 +5412,7 @@ export class CheckerService { } #getCanMigrate() { - if (AppConstants.platform != "win") { + if (AppConstants.platform != "win" || AppConstants.BASE_BROWSER_UPDATE) { return false; } diff --git a/toolkit/mozapps/update/UpdateServiceStub.sys.mjs b/toolkit/mozapps/update/UpdateServiceStub.sys.mjs @@ -93,10 +93,15 @@ export class UpdateServiceStub { #initOnlyStub(updateDir) { // We may need to migrate update data + // In Base Browser and derivatives, we skip this because we do not use an + // update agent and we do not want to store any data outside of the browser + // installation directory. + // For more info, see https://bugzilla.mozilla.org/show_bug.cgi?id=1458314 let prefUpdateDirMigrated = PREF_PREFIX_UPDATE_DIR_MIGRATED + updateDir.leafName; if ( AppConstants.platform == "win" && + !AppConstants.BASE_BROWSER_UPDATE && !Services.prefs.getBoolPref(prefUpdateDirMigrated, false) ) { Services.prefs.setBoolPref(prefUpdateDirMigrated, true); diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp @@ -67,6 +67,13 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, * @return TRUE if successful */ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { +#ifdef BASE_BROWSER_UPDATE + // This function is used to support the maintenance service and elevated + // updates and is therefore not called by Base Browser's updater. We stub + // it out to avoid any chance that the Base Browser updater will create + // files under C:\Program Files (x86)\ or a similar location. + return FALSE; +#else PWSTR progFilesX86; if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE, nullptr, &progFilesX86))) { @@ -100,6 +107,7 @@ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { } return TRUE; +#endif } /** diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm @@ -377,6 +377,7 @@ bool ObtainUpdaterArguments(int* aArgc, char*** aArgv, @end +#ifndef BASE_BROWSER_UPDATE bool ServeElevatedUpdate(int aArgc, const char** aArgv, const char* aMARChannelID) { MacAutoreleasePool pool; @@ -395,6 +396,7 @@ bool ServeElevatedUpdate(int aArgc, const char** aArgv, [updater release]; return didSucceed; } +#endif bool IsOwnedByGroupAdmin(const char* aAppBundle) { MacAutoreleasePool pool; diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp @@ -93,8 +93,10 @@ void LaunchMacApp(int argc, const char** argv); void LaunchMacPostProcess(const char* aAppBundle); bool ObtainUpdaterArguments(int* aArgc, char*** aArgv, MARChannelStringTable* aMARStrings); +# ifndef BASE_BROWSER_UPDATE bool ServeElevatedUpdate(int aArgc, const char** aArgv, const char* aMARChannelID); +# endif void SetGroupOwnershipAndPermissions(const char* aAppBundle); bool PerformInstallationFromDMG(int argc, char** argv); struct UpdateServerThreadArgs { @@ -945,6 +947,11 @@ static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest, if (S_ISLNK(sInfo.st_mode)) { return ensure_copy_symlink(path, dest); } + + // Ignore Unix domain sockets. See #20691. + if (S_ISSOCK(sInfo.st_mode)) { + return 0; + } #endif if (!S_ISDIR(sInfo.st_mode)) { @@ -1040,7 +1047,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, return OK; } -#ifdef XP_WIN +#if defined(XP_WIN) && !defined(BASE_BROWSER_UPDATE) // Remove the directory pointed to by path and all of its files and // sub-directories. If a file is in use move it to the tobedeleted directory // and attempt to schedule removal of the file on reboot @@ -1173,6 +1180,8 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { relBackup, relPath)); return WRITE_ERROR_DELETE_BACKUP; } + +# if !defined(BASE_BROWSER_UPDATE) // The MoveFileEx call to remove the file on OS reboot will fail if the // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key // but this is ok since the installer / uninstaller will delete the @@ -1189,6 +1198,7 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { "file: " LOG_S, relPath)); } +# endif } #else if (rv) { @@ -2936,7 +2946,9 @@ static int ProcessReplaceRequest() { if (NS_taccess(deleteDir, F_OK)) { NS_tmkdir(deleteDir, 0755); } +# if !defined(BASE_BROWSER_UPDATE) remove_recursive_on_reboot(tmpDir, deleteDir); +# endif #endif } @@ -3051,8 +3063,14 @@ static void UpdateThreadFunc(void* param) { if (rv == OK) { rv = PopulategMARStrings(); if (rv == OK) { +# ifdef BASE_BROWSER_VERSION_QUOTED + // Use the base browser version to prevent downgrade attacks. + const char* appVersion = BASE_BROWSER_VERSION_QUOTED; +# else + const char* appVersion = MOZ_APP_VERSION; +# endif rv = gArchiveReader.VerifyProductInformation( - gMARStrings.MARChannelID.get(), MOZ_APP_VERSION); + gMARStrings.MARChannelID.get(), appVersion); } } #endif @@ -3156,12 +3174,16 @@ static void UpdateThreadFunc(void* param) { #ifdef XP_MACOSX static void ServeElevatedUpdateThreadFunc(void* param) { +# ifdef BASE_BROWSER_UPDATE + WriteStatusFile(ELEVATION_CANCELED); +# else UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv, threadArgs->marChannelID); if (!gSucceeded) { WriteStatusFile(ELEVATION_CANCELED); } +# endif QuitProgressUI(); } @@ -3368,20 +3390,24 @@ int NS_main(int argc, NS_tchar** argv) { // _usually_ be unelevated and the second invocation should always be // elevated. `gInvocation` can be used for that purpose. bool isElevated = -#ifdef XP_WIN - // While is it technically redundant to check LocalSystem in addition to - // Admin given the former contains privileges of the latter, we have opt - // to verify both. A few reasons for this decision include the off chance - // that the Windows security model changes in the future and weird system - // setups where someone has modified the group lists in surprising ways. - // - // We use this to detect if we were launched from the Maintenance Service - // under LocalSystem or UAC under the user's account, and therefore can - // proceed with an install to `Program Files` or `Program Files(x86)`. - isAdmin.unwrap() || isLocalSystem.unwrap(); +#ifdef BASE_BROWSER_UPDATE + false; +#elif defined(XP_WIN) + // While is it technically redundant to check LocalSystem in addition to + // Admin given the former contains privileges of the latter, we have opt + // to verify both. A few reasons for this decision include the off + // chance that the Windows security model changes in the future and + // weird system setups where someone has modified the group lists in + // surprising ways. + // + // We use this to detect if we were launched from the Maintenance + // Service under LocalSystem or UAC under the user's account, and + // therefore can proceed with an install to `Program Files` or `Program + // Files(x86)`. + isAdmin.unwrap() || isLocalSystem.unwrap(); #elif defined(XP_MACOSX) - strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != - 0; + strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != + 0; #else false; #endif @@ -4145,6 +4171,14 @@ int NS_main(int argc, NS_tchar** argv) { if (!useService && !noServiceFallback && (updateLockFileHandle == INVALID_HANDLE_VALUE || forceServiceFallback)) { +# ifdef BASE_BROWSER_UPDATE + // To avoid potential security issues such as CVE-2015-0833, do not + // attempt to elevate privileges. Instead, write a "failed" message to + // the update status file (this function will return immediately after + // the CloseHandle(elevatedFileHandle) call below). + LOG(("Refusing to elevate in Base Browser.")); + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); +# else LOG(("Elevating via a UAC prompt")); // Get the secure ID before trying to update so it is possible to // determine if the updater has created a new one. @@ -4227,6 +4261,7 @@ int NS_main(int argc, NS_tchar** argv) { WriteStatusFile(ELEVATION_CANCELED); LOG(("Elevation canceled.")); } +# endif /* BASE_BROWSER_UPDATE */ } else { LOG(("Not showing a UAC prompt.")); LOG(("useService=%s", useService ? "true" : "false")); @@ -4612,6 +4647,7 @@ int NS_main(int argc, NS_tchar** argv) { if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) { LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", DELETE_DIR, errno)); +# if !defined(BASE_BROWSER_UPDATE) // The directory probably couldn't be removed due to it containing files // that are in use and will be removed on OS reboot. The call to remove // the directory on OS reboot is done after the calls to remove the files @@ -4631,6 +4667,7 @@ int NS_main(int argc, NS_tchar** argv) { "directory: " LOG_S, DELETE_DIR)); } +# endif /* BASE_BROWSER_UPDATE */ } #endif /* XP_WIN */ diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h @@ -27,9 +27,11 @@ extern "C" { */ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0); void LaunchMacApp(int aArgc, char** aArgv); +#ifndef BASE_BROWSER_UPDATE bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0); bool InstallPrivilegedHelper(); void AbortElevatedUpdate(); +#endif } #endif diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm @@ -148,6 +148,7 @@ void LaunchMacApp(int aArgc, char** aArgv) { LaunchMacAppWithBundle(launchPath, arguments); } +#ifndef BASE_BROWSER_UPDATE bool InstallPrivilegedHelper() { AuthorizationRef authRef = NULL; OSStatus status = AuthorizationCreate( @@ -231,3 +232,4 @@ bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) { } return didSucceed; } +#endif diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp @@ -3583,6 +3583,11 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion, gLastAppBuildID.Assign(gAppData->buildID); nsAutoCString buf; + + nsAutoCString forkVersion(BASE_BROWSER_VERSION_QUOTED); + rv = parser.GetString("Compatibility", "LastBaseBrowserVersion", buf); + if (NS_FAILED(rv) || !forkVersion.Equals(buf)) return false; + rv = parser.GetString("Compatibility", "LastOSABI", buf); if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false; @@ -3662,6 +3667,12 @@ static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion, PR_Write(fd, kHeader, sizeof(kHeader) - 1); PR_Write(fd, aVersion.get(), aVersion.Length()); + nsAutoCString forkVersion(BASE_BROWSER_VERSION_QUOTED); + static const char kForkVersionHeader[] = + NS_LINEBREAK "LastBaseBrowserVersion="; + PR_Write(fd, kForkVersionHeader, sizeof(kForkVersionHeader) - 1); + PR_Write(fd, forkVersion.get(), forkVersion.Length()); + static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI="; PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1); PR_Write(fd, aOSABI.get(), aOSABI.Length()); @@ -5169,8 +5180,18 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { NS_ENSURE_SUCCESS(rv, 1); rv = exeFile->GetParent(getter_AddRefs(exeDir)); NS_ENSURE_SUCCESS(rv, 1); + +# ifdef BASE_BROWSER_VERSION_QUOTED + nsAutoCString compatVersion(BASE_BROWSER_VERSION_QUOTED); +# endif ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc, - gRestartArgv, mAppData->version); + gRestartArgv, +# ifdef BASE_BROWSER_VERSION_QUOTED + compatVersion.get() +# else + mAppData->version +# endif + ); } else { if (CheckArg("test-should-not-process-updates") || EnvHasValue("MOZ_TEST_SHOULD_NOT_PROCESS_UPDATES")) { diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp @@ -122,6 +122,13 @@ static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) { return NS_OK; } +#ifdef DEBUG +static void dump_argv(const char* aPrefix, char** argv, int argc) { + printf("%s - %d args\n", aPrefix, argc); + for (int i = 0; i < argc; ++i) printf(" %d: %s\n", i, argv[i]); +} +#endif + static bool GetFile(nsIFile* dir, const nsACString& name, nsCOMPtr<nsIFile>& result) { nsresult rv; @@ -201,6 +208,34 @@ enum UpdateStatus { eAppliedService, }; +#ifdef DEBUG +static const char* UpdateStatusToString(UpdateStatus aStatus) { + const char* rv = "unknown"; + switch (aStatus) { + case eNoUpdateAction: + rv = "NoUpdateAction"; + break; + case ePendingUpdate: + rv = "PendingUpdate"; + break; + case ePendingService: + rv = "PendingService"; + break; + case ePendingElevate: + rv = "PendingElevate"; + break; + case eAppliedUpdate: + rv = "AppliedUpdate"; + break; + case eAppliedService: + rv = "AppliedService"; + break; + } + + return rv; +} +#endif + /** * Returns a value indicating what needs to be done in order to handle an * update. @@ -273,6 +308,11 @@ static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) { return false; } +#ifdef DEBUG + printf("IsOlderVersion checking appVersion %s against updateVersion %s\n", + appVersion, buf); +#endif + return mozilla::Version(appVersion) > buf; } @@ -441,6 +481,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, gfxPlatformMac::WaitForFontRegistration(); } +# ifndef BASE_BROWSER_UPDATE // We need to detect whether elevation is required for this update. This can // occur when an admin user installs the application, but another admin // user attempts to update (see bug 394984). @@ -467,6 +508,7 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } } } +# endif #endif nsAutoCString applyToDirPath; @@ -582,6 +624,9 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } LOG(("spawning updater process [%s]\n", updaterPath.get())); +#ifdef DEBUG + dump_argv("ApplyUpdate updater", argv, argc); +#endif #if defined(XP_UNIX) && !defined(XP_MACOSX) // We use execv to spawn the updater process on all UNIX systems except Mac @@ -619,11 +664,15 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } } #elif defined(XP_MACOSX) +# ifdef DEBUG +dump_argv("ApplyUpdate after SetupMacCommandLine", argv, argc); +# endif if (restart) { // Ensure we've added URLs to load into the app command line if we're // restarting. CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart); +# ifndef BASE_BROWSER_UPDATE if (needElevation) { bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid); free(argv); @@ -633,6 +682,7 @@ if (restart) { } exit(0); } +# endif } if (isStaged) { @@ -705,13 +755,31 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, nsresult rv; #ifdef XP_WIN +# if defined(BASE_BROWSER_UPDATE) + // Try to remove the "tobedeleted" directory which, if present, contains + // files that could not be removed during a previous update (e.g., DLLs + // that were in use and therefore locked by Windows). + nsCOMPtr<nsIFile> deleteDir; + nsresult winrv = appDir->Clone(getter_AddRefs(deleteDir)); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->AppendNative("tobedeleted"_ns); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->Remove(true); + } + } +# else // If we're in a package, we know any updates that we find are not for us. if (mozilla::widget::WinUtils::HasPackageIdentity()) { return NS_OK; } +# endif #endif nsCOMPtr<nsIFile> updatesDir; +#ifdef DEBUG + printf("ProcessUpdates updateRootDir: %s appVersion: %s\n", + updRootDir->HumanReadablePath().get(), appVersion); +#endif rv = updRootDir->Clone(getter_AddRefs(updatesDir)); NS_ENSURE_SUCCESS(rv, rv); rv = updatesDir->AppendNative("updates"_ns); @@ -731,6 +799,12 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, nsCOMPtr<nsIFile> statusFile; UpdateStatus status = GetUpdateStatus(updatesDir, statusFile); +#ifdef DEBUG + printf("ProcessUpdates status: %s (%d)\n", UpdateStatusToString(status), + status); + printf("ProcessUpdates updatesDir: %s\n", + updatesDir->HumanReadablePath().get()); +#endif switch (status) { case ePendingUpdate: case ePendingService: { @@ -798,13 +872,16 @@ nsUpdateProcessor::ProcessUpdate() { NS_ENSURE_SUCCESS(rv, rv); } + nsAutoCString appVersion; +#ifdef BASE_BROWSER_VERSION_QUOTED + appVersion = BASE_BROWSER_VERSION_QUOTED; +#else nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1", &rv); NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString appVersion; rv = appInfo->GetVersion(appVersion); NS_ENSURE_SUCCESS(rv, rv); +#endif // Copy the parameters to the StagedUpdateInfo structure shared with the // worker thread. diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp @@ -980,7 +980,51 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, rv = appFile->GetParent(getter_AddRefs(updRoot)); NS_ENSURE_SUCCESS(rv, rv); -#ifdef XP_MACOSX +#if defined(BASE_BROWSER_UPDATE) + // For Base Browser and derivatives, we store update history, etc. within the + // UpdateInfo directory under the user data directory. +# if defined(ANDROID) +# error "The Base Browser updater is not supported on Android." +# elif defined(XP_MACOSX) + rv = GetUserDataDirectory(getter_AddRefs(updRoot), false); + NS_ENSURE_SUCCESS(rv, rv); +# else + bool isPortable = true; + rv = GetIsPortableMode(&isPortable); + NS_ENSURE_SUCCESS(rv, rv); + if (isPortable) { + rv = GetUserDataDirectoryHome(getter_AddRefs(updRoot), false); + } else { + rv = GetUserDataDirectory(getter_AddRefs(updRoot), true); + } + NS_ENSURE_SUCCESS(rv, rv); +# endif + rv = updRoot->AppendNative("UpdateInfo"_ns); + NS_ENSURE_SUCCESS(rv, rv); + +# if defined(XP_MACOSX) + // Since the data directory may be shared among different installations of the + // application, embed the app path in the update dir so that the update + // history is partitioned. This is much less likely to be an issue on Linux or + // Windows, because our packages for those platforms include a "container" + // folder that provides partitioning by default, and we do not support use of + // a shared, OS-recommended area for user data on those platforms. + nsAutoString appPath; + rv = appFile->GetPath(appPath); + NS_ENSURE_SUCCESS(rv, rv); + int32_t dotIndex = appPath.RFind(u".app"); + if (dotIndex == kNotFound) { + dotIndex = appPath.Length(); + } + appPath = Substring(appPath, 1, dotIndex - 1); + rv = updRoot->AppendRelativePath(appPath); + NS_ENSURE_SUCCESS(rv, rv); +# endif + + updRoot.forget(aResult); + return NS_OK; +// The rest of the function is for ! BASE_BROWSER_UPDATE +#elif defined(XP_MACOSX) nsCOMPtr<nsIFile> appRootDirFile; nsCOMPtr<nsIFile> localDir; nsAutoString appDirPath;