diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/maintenanceservice/workmonitor.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/maintenanceservice/workmonitor.cpp')
-rw-r--r-- | toolkit/components/maintenanceservice/workmonitor.cpp | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp new file mode 100644 index 0000000000..96a098d547 --- /dev/null +++ b/toolkit/components/maintenanceservice/workmonitor.cpp @@ -0,0 +1,812 @@ +/* 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/. */ + +#include <shlobj.h> +#include <shlwapi.h> +#include <wtsapi32.h> +#include <userenv.h> +#include <shellapi.h> + +#ifndef __MINGW32__ +# pragma comment(lib, "wtsapi32.lib") +# pragma comment(lib, "userenv.lib") +# pragma comment(lib, "shlwapi.lib") +# pragma comment(lib, "ole32.lib") +# pragma comment(lib, "rpcrt4.lib") +#endif + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "nsWindowsHelpers.h" +#include "mozilla/UniquePtr.h" + +using mozilla::UniquePtr; + +#include "workmonitor.h" +#include "serviceinstall.h" +#include "servicebase.h" +#include "registrycertificates.h" +#include "uachelper.h" +#include "updatehelper.h" +#include "pathhash.h" +#include "updatererrors.h" +#include "commonupdatedir.h" + +// Wait 15 minutes for an update operation to run at most. +// Updates usually take less than a minute so this seems like a +// significantly large and safe amount of time to wait. +static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, + LPCWSTR newFileName); +BOOL DoesFallbackKeyExist(); + +/* + * Reads the secure update status file and sets isApplying to true if the status + * is set to applying. + * + * @param patchDirPath + * The update patch directory path + * @param isApplying Out parameter for specifying if the status + * is set to applying or not. + * @return TRUE if the information was filled. + */ +static BOOL IsStatusApplying(LPCWSTR patchDirPath, BOOL& isApplying) { + isApplying = FALSE; + WCHAR statusFilePath[MAX_PATH + 1] = {L'\0'}; + if (!GetSecureOutputFilePath(patchDirPath, L".status", statusFilePath)) { + LOG_WARN(("Could not get path for the secure update status file")); + return FALSE; + } + + nsAutoHandle statusFile( + CreateFileW(statusFilePath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, 0, nullptr)); + + if (INVALID_HANDLE_VALUE == statusFile) { + LOG_WARN(("Could not open update.status file")); + return FALSE; + } + + char buf[32] = {0}; + DWORD read; + if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) { + LOG_WARN(("Could not read from update.status file")); + return FALSE; + } + + const char kApplying[] = "applying"; + isApplying = strncmp(buf, kApplying, sizeof(kApplying) - 1) == 0; + return TRUE; +} + +/** + * Determines whether we're staging an update. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @return boolean True if we're staging an update + */ +static bool IsUpdateBeingStaged(int argc, LPWSTR* argv) { + // PID will be set to -1 if we're supposed to stage an update. + return (argc == 4 && !wcscmp(argv[3], L"-1")) || + (argc == 5 && !wcscmp(argv[4], L"-1")); +} + +/** + * Determines whether the param only contains digits. + * + * @param str The string to check + * @param boolean True if the param only contains digits + */ +static bool IsDigits(WCHAR* str) { + while (*str) { + if (!iswdigit(*str++)) { + return FALSE; + } + } + return TRUE; +} + +/** + * Determines whether the command line contains just the directory to apply the + * update to (old command line) or if it contains the installation directory and + * the directory to apply the update to. + * + * @param argc The argc value normally sent to updater.exe + * @param argv The argv value normally sent to updater.exe + * @param boolean True if the command line contains just the directory to apply + * the update to + */ +static bool IsOldCommandline(int argc, LPWSTR* argv) { + return (argc == 4 && !wcscmp(argv[3], L"-1")) || + (argc >= 4 && (wcsstr(argv[3], L"/replace") || IsDigits(argv[3]))); +} + +/** + * Gets the installation directory from the arguments passed to updater.exe. + * + * @param argcTmp The argc value normally sent to updater.exe + * @param argvTmp The argv value normally sent to updater.exe + * @param aResultDir Buffer to hold the installation directory. + */ +static BOOL GetInstallationDir(int argcTmp, LPWSTR* argvTmp, + WCHAR aResultDir[MAX_PATH + 1]) { + int index = 3; + if (IsOldCommandline(argcTmp, argvTmp)) { + index = 2; + } + + if (argcTmp < index) { + return FALSE; + } + + wcsncpy(aResultDir, argvTmp[2], MAX_PATH); + WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); + // Make sure that the path does not include trailing backslashes + if (backSlash && (backSlash[1] == L'\0')) { + *backSlash = L'\0'; + } + + // The new command line's argv[2] is always the installation directory. + if (index == 2) { + bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); + bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); + if (backgroundUpdate || replaceRequest) { + return PathRemoveFileSpecW(aResultDir); + } + } + return TRUE; +} + +/** + * Runs an update process as the service using the SYSTEM account. + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * @param processStarted Set to TRUE if the process was started. + * @return TRUE if the update process was run had a return code of 0. + */ +BOOL StartUpdateProcess(int argc, LPWSTR* argv, LPCWSTR installDir, + BOOL& processStarted) { + processStarted = FALSE; + + LOG(("Starting update process as the service in session 0.")); + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + ZeroMemory(&pi, sizeof(pi)); + si.cb = sizeof(si); + si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\Default"); // -Wwritable-strings + + // The updater command line is of the form: + // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] + auto cmdLine = mozilla::MakeCommandLine(argc, argv); + + int index = 3; + if (IsOldCommandline(argc, argv)) { + index = 2; + } + + // If we're about to start the update process from session 0, + // then we should not show a GUI. This only really needs to be done + // on Vista and higher, but it's better to keep everything consistent + // across all OS if it's of no harm. + if (argc >= index) { + // Setting the desktop to blank will ensure no GUI is displayed + si.lpDesktop = const_cast<LPWSTR>(L""); // -Wwritable-strings + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + } + + // Add an env var for MOZ_USING_SERVICE so the updater.exe can + // do anything special that it needs to do for service updates. + // Search in updater.cpp for more info on MOZ_USING_SERVICE. + putenv(const_cast<char*>("MOZ_USING_SERVICE=1")); + + LOG(("Starting service with cmdline: %ls", cmdLine.get())); + processStarted = + CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE, + CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr, &si, &pi); + + BOOL updateWasSuccessful = FALSE; + if (processStarted) { + BOOL processTerminated = FALSE; + BOOL noProcessExitCode = FALSE; + // Wait for the updater process to finish + LOG(("Process was started... waiting on result.")); + DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); + if (WAIT_TIMEOUT == waitRes) { + // We waited a long period of time for updater.exe and it never finished + // so kill it. + TerminateProcess(pi.hProcess, 1); + processTerminated = TRUE; + } else { + // Check the return code of updater.exe to make sure we get 0 + DWORD returnCode; + if (GetExitCodeProcess(pi.hProcess, &returnCode)) { + LOG(("Process finished with return code %lu.", returnCode)); + // updater returns 0 if successful. + updateWasSuccessful = (returnCode == 0); + } else { + LOG_WARN(("Process finished but could not obtain return code.")); + noProcessExitCode = TRUE; + } + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + // Check just in case updater.exe didn't change the status from + // applying. If this is the case we report an error. + BOOL isApplying = FALSE; + if (IsStatusApplying(argv[1], isApplying) && isApplying) { + if (updateWasSuccessful) { + LOG( + ("update.status is still applying even though update was " + "successful.")); + if (!WriteStatusFailure(argv[1], SERVICE_STILL_APPLYING_ON_SUCCESS)) { + LOG_WARN( + ("Could not write update.status still applying on " + "success error.")); + } + // Since we still had applying we know updater.exe didn't do its + // job correctly. + updateWasSuccessful = FALSE; + } else { + LOG_WARN( + ("update.status is still applying and update was not successful.")); + int failcode = SERVICE_STILL_APPLYING_ON_FAILURE; + if (noProcessExitCode) { + failcode = SERVICE_STILL_APPLYING_NO_EXIT_CODE; + } else if (processTerminated) { + failcode = SERVICE_STILL_APPLYING_TERMINATED; + } + if (!WriteStatusFailure(argv[1], failcode)) { + LOG_WARN( + ("Could not write update.status still applying on " + "failure error.")); + } + } + } + } else { + DWORD lastError = GetLastError(); + LOG_WARN( + ("Could not create process as current user, " + "updaterPath: %ls; cmdLine: %ls. (%lu)", + argv[0], cmdLine.get(), lastError)); + } + + // Empty value on putenv is how you remove an env variable in Windows + putenv(const_cast<char*>("MOZ_USING_SERVICE=")); + + return updateWasSuccessful; +} + +/** + * Validates a file as an official updater. + * + * @param updater Path to the updater to validate + * @param installDir Path to the application installation + * being updated + * @param updateDir Update applyTo direcotry, + * where logs will be written + * + * @return true if updater is the path to a valid updater + */ +static bool UpdaterIsValid(LPWSTR updater, LPWSTR installDir, + LPWSTR updateDir) { + // Make sure the path to the updater to use for the update is local. + // We do this check to make sure that file locking is available for + // race condition security checks. + BOOL isLocal = FALSE; + if (!IsLocalFile(updater, isLocal) || !isLocal) { + LOG_WARN(("Filesystem in path %ls is not supported (%lu)", updater, + GetLastError())); + if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_NOT_FIXED_DRIVE)) { + LOG_WARN(("Could not write update.status service update failure. (%lu)", + GetLastError())); + } + return false; + } + + nsAutoHandle noWriteLock(CreateFileW(updater, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (INVALID_HANDLE_VALUE == noWriteLock) { + LOG_WARN(("Could not set no write sharing access on file. (%lu)", + GetLastError())); + if (!WriteStatusFailure(updateDir, SERVICE_COULD_NOT_LOCK_UPDATER)) { + LOG_WARN(("Could not write update.status service update failure. (%lu)", + GetLastError())); + } + return false; + } + + // Verify that the updater.exe that we are executing is the same + // as the one in the installation directory which we are updating. + // The installation dir that we are installing to is installDir. + WCHAR installDirUpdater[MAX_PATH + 1] = {L'\0'}; + wcsncpy(installDirUpdater, installDir, MAX_PATH); + if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { + LOG_WARN(("Install directory updater could not be determined.")); + return false; + } + + BOOL updaterIsCorrect; + if (!VerifySameFiles(updater, installDirUpdater, updaterIsCorrect)) { + LOG_WARN( + ("Error checking if the updaters are the same.\n" + "Path 1: %ls\nPath 2: %ls", + updater, installDirUpdater)); + return false; + } + + if (!updaterIsCorrect) { + LOG_WARN( + ("The updaters do not match, updater will not run.\n" + "Path 1: %ls\nPath 2: %ls", + updater, installDirUpdater)); + if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_COMPARE_ERROR)) { + LOG_WARN(("Could not write update.status updater compare failure.")); + } + return false; + } + + LOG( + ("updater.exe was compared successfully to the installation directory" + " updater.exe.")); + + // Check to make sure the updater.exe module has the unique updater identity. + // This is a security measure to make sure that the signed executable that + // we will run is actually an updater. + bool result = true; + HMODULE updaterModule = + LoadLibraryEx(updater, nullptr, LOAD_LIBRARY_AS_DATAFILE); + if (!updaterModule) { + LOG_WARN(("updater.exe module could not be loaded. (%lu)", GetLastError())); + result = false; + } else { + char updaterIdentity[64]; + if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, updaterIdentity, + sizeof(updaterIdentity))) { + LOG_WARN( + ("The updater.exe application does not contain the Mozilla" + " updater identity.")); + result = false; + } + + if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { + LOG_WARN(("The updater.exe identity string is not valid.")); + result = false; + } + FreeLibrary(updaterModule); + } + + if (result) { + LOG( + ("The updater.exe application contains the Mozilla" + " updater identity.")); + } else { + if (!WriteStatusFailure(updateDir, SERVICE_UPDATER_IDENTITY_ERROR)) { + LOG_WARN(("Could not write update.status no updater identity.")); + } + return false; + } + +#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK + return DoesBinaryMatchAllowedCertificates(installDir, updater); +#else + return true; +#endif +} + +/** + * Processes a software update command + * + * @param argc The number of arguments in argv + * @param argv The arguments normally passed to updater.exe + * argv[0] must be the path to updater.exe + * + * @return TRUE if the update was successful. + */ +BOOL ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR* argv) { + BOOL result = TRUE; + if (argc < 3) { + LOG_WARN( + ("Not enough command line parameters specified. " + "Updating update.status.")); + + // We can only update update.status if argv[1] exists. argv[1] is + // the directory where the update.status file exists. + if (argc < 2 || + !WriteStatusFailure(argv[1], SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { + LOG_WARN(("Could not write update.status service update failure. (%lu)", + GetLastError())); + } + return FALSE; + } + + WCHAR installDir[MAX_PATH + 1] = {L'\0'}; + if (!GetInstallationDir(argc, argv, installDir)) { + LOG_WARN(("Could not get the installation directory")); + if (!WriteStatusFailure(argv[1], SERVICE_INSTALLDIR_ERROR)) { + LOG_WARN( + ("Could not write update.status for GetInstallationDir failure.")); + } + return FALSE; + } + + if (UpdaterIsValid(argv[0], installDir, argv[1])) { + BOOL updateProcessWasStarted = FALSE; + if (StartUpdateProcess(argc, argv, installDir, updateProcessWasStarted)) { + LOG(("updater.exe was launched and run successfully!")); + LogFlush(); + + // Don't attempt to update the service when the update is being staged. + if (!IsUpdateBeingStaged(argc, argv)) { + // We might not execute code after StartServiceUpdate because + // the service installer will stop the service if it is running. + StartServiceUpdate(installDir); + } + } else { + result = FALSE; + LOG_WARN(("Error running update process. Updating update.status (%lu)", + GetLastError())); + LogFlush(); + + // If the update process was started, then updater.exe is responsible for + // setting the failure code. If it could not be started then we do the + // work. We set an error instead of directly setting status pending + // so that the app.update.service.errors pref can be updated when + // the callback app restarts. + if (!updateProcessWasStarted) { + if (!WriteStatusFailure(argv[1], + SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { + LOG_WARN( + ("Could not write update.status service update failure. (%lu)", + GetLastError())); + } + } + } + } else { + result = FALSE; + LOG_WARN( + ("Could not start process due to certificate check error on " + "updater.exe. Updating update.status. (%lu)", + GetLastError())); + + // When there is a certificate check error on the updater.exe application, + // we want to write out the error. + if (!WriteStatusFailure(argv[1], SERVICE_UPDATER_SIGN_ERROR)) { + LOG_WARN(("Could not write pending state to update.status. (%lu)", + GetLastError())); + } + } + + return result; +} + +/** + * Obtains the updater path alongside a subdir of the service binary. + * The purpose of this function is to return a path that is likely high + * integrity and therefore more safe to execute code from. + * + * @param serviceUpdaterPath Out parameter for the path where the updater + * should be copied to. + * @return TRUE if a file path was obtained. + */ +BOOL GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) { + if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) { + LOG_WARN( + ("Could not obtain module filename when attempting to " + "use a secure updater path. (%lu)", + GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(serviceUpdaterPath)) { + LOG_WARN( + ("Couldn't remove file spec when attempting to use a secure " + "updater path. (%lu)", + GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(serviceUpdaterPath, L"update")) { + LOG_WARN( + ("Couldn't append file spec when attempting to use a secure " + "updater path. (%lu)", + GetLastError())); + return FALSE; + } + + CreateDirectoryW(serviceUpdaterPath, nullptr); + + if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) { + LOG_WARN( + ("Couldn't append file spec when attempting to use a secure " + "updater path. (%lu)", + GetLastError())); + return FALSE; + } + + return TRUE; +} + +/** + * Deletes the passed in updater path and the associated updater.ini file. + * + * @param serviceUpdaterPath The path to delete. + * @return TRUE if a file was deleted. + */ +BOOL DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) { + BOOL result = FALSE; + if (serviceUpdaterPath[0]) { + result = DeleteFileW(serviceUpdaterPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) { + LOG_WARN(("Could not delete service updater path: '%ls'.", + serviceUpdaterPath)); + } + + WCHAR updaterINIPath[MAX_PATH + 1] = {L'\0'}; + if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, + L"updater.ini")) { + result = DeleteFileW(updaterINIPath); + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && + GetLastError() != ERROR_FILE_NOT_FOUND) { + LOG_WARN(("Could not delete service updater INI path: '%ls'.", + updaterINIPath)); + } + } + } + return result; +} + +/** + * Executes a service command. + * + * @param argc The number of arguments in argv + * @param argv The service command line arguments, argv[0] is automatically + * included by Windows, argv[1] is unused but hardcoded to + * "MozillaMaintenance", and argv[2] is the service command. + * + * @return FALSE if there was an error executing the service command. + */ +BOOL ExecuteServiceCommand(int argc, LPWSTR* argv) { + if (argc < 3) { + LOG_WARN( + ("Not enough command line arguments to execute a service command")); + return FALSE; + } + + // The tests work by making sure the log has changed, so we put a + // unique ID in the log. + WCHAR uuidString[MAX_PATH + 1] = {L'\0'}; + if (GetUUIDString(uuidString)) { + LOG(("Executing service command %ls, ID: %ls", argv[2], uuidString)); + } else { + // The ID is only used by tests, so failure to allocate it isn't fatal. + LOG(("Executing service command %ls", argv[2])); + } + + BOOL result = FALSE; + if (!lstrcmpi(argv[2], L"software-update")) { + // This check is also performed in updater.cpp and is performed here + // as well since the maintenance service can be called directly. + if (argc <= 4 || !IsValidFullPath(argv[4])) { + // Since the status file is written to the patch directory and the patch + // directory is invalid don't write the status file. + LOG_WARN(("The patch directory path is not valid for this application.")); + return FALSE; + } + + // The patch directory path must end with updates\0 to use the maintenance + // service. + size_t fullPathLen = NS_tstrlen(argv[4]); + size_t relPathLen = NS_tstrlen(PATCH_DIR_PATH); + if (relPathLen > fullPathLen) { + LOG_WARN( + ("The patch directory path length is not valid for this " + "application.")); + return FALSE; + } + + if (_wcsnicmp(argv[4] + fullPathLen - relPathLen, PATCH_DIR_PATH, + relPathLen) != 0) { + LOG_WARN( + ("The patch directory path subdirectory is not valid for this " + "application.")); + return FALSE; + } + + // Remove the secure output files so it is easier to determine when new + // files are created in the unelevated updater. + RemoveSecureOutputFiles(argv[4]); + + // Create a new secure ID for this update. + if (!WriteSecureIDFile(argv[4])) { + LOG_WARN(("Unable to write to secure ID file.")); + return FALSE; + } + + // This check is also performed in updater.cpp and is performed here + // as well since the maintenance service can be called directly. + if (argc <= 5 || !IsValidFullPath(argv[5]) + // This build flag is used as a handy proxy to tell when we're a build made + // for local testing, because there isn't much other reason to set it. +#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK + || !IsProgramFilesPath(argv[5]) +#endif + ) { + LOG_WARN( + ("The install directory path is not valid for this application.")); + if (!WriteStatusFailure(argv[4], + SERVICE_INVALID_INSTALL_DIR_PATH_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + + if (!IsOldCommandline(argc - 3, argv + 3)) { + // This check is also performed in updater.cpp and is performed here + // as well since the maintenance service can be called directly. + if (argc <= 6 || !IsValidFullPath(argv[6])) { + LOG_WARN( + ("The working directory path is not valid for this application.")); + if (!WriteStatusFailure(argv[4], + SERVICE_INVALID_WORKING_DIR_PATH_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + + // These checks are also performed in updater.cpp and is performed here + // as well since the maintenance service can be called directly. + if (_wcsnicmp(argv[6], argv[5], MAX_PATH) != 0) { + if (argc <= 7 || + (wcscmp(argv[7], L"-1") != 0 && !wcsstr(argv[7], L"/replace"))) { + LOG_WARN( + ("Installation directory and working directory must be the " + "same for non-staged updates. Exiting.")); + if (!WriteStatusFailure(argv[4], SERVICE_INVALID_APPLYTO_DIR_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + + NS_tchar workingDirParent[MAX_PATH]; + NS_tsnprintf(workingDirParent, + sizeof(workingDirParent) / sizeof(workingDirParent[0]), + NS_T("%s"), argv[6]); + if (!PathRemoveFileSpecW(workingDirParent)) { + LOG_WARN( + ("Couldn't remove file spec when attempting to verify the " + "working directory path. (%lu)", + GetLastError())); + if (!WriteStatusFailure(argv[4], REMOVE_FILE_SPEC_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + + if (_wcsnicmp(workingDirParent, argv[5], MAX_PATH) != 0) { + LOG_WARN( + ("The apply-to directory must be the same as or " + "a child of the installation directory! Exiting.")); + if (!WriteStatusFailure(argv[4], + SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + } + } + + // Use the passed in command line arguments for the update, except for the + // path to updater.exe. We always look for updater.exe in the installation + // directory, then we copy that updater.exe to a the directory of the + // MozillaMaintenance service so that a low integrity process cannot + // replace the updater.exe at any point and use that for the update. + // It also makes DLL injection attacks harder. + WCHAR installDir[MAX_PATH + 1] = {L'\0'}; + if (!GetInstallationDir(argc - 3, argv + 3, installDir)) { + LOG_WARN(("Could not get the installation directory")); + if (!WriteStatusFailure(argv[4], SERVICE_INSTALLDIR_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + + if (!DoesFallbackKeyExist()) { + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + if (CalculateRegistryPathFromFilePath(installDir, + maintenanceServiceKey)) { + LOG(("Checking for Maintenance Service registry. key: '%ls'", + maintenanceServiceKey)); + HKEY baseKey = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, + &baseKey) != ERROR_SUCCESS) { + LOG_WARN(("The maintenance service registry key does not exist.")); + if (!WriteStatusFailure(argv[4], SERVICE_INSTALL_DIR_REG_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + RegCloseKey(baseKey); + } else { + if (!WriteStatusFailure(argv[4], SERVICE_CALC_REG_PATH_ERROR)) { + LOG_WARN(("Could not write update.status for previous failure.")); + } + return FALSE; + } + } + + WCHAR installDirUpdater[MAX_PATH + 1] = {L'\0'}; + wcsncpy(installDirUpdater, installDir, MAX_PATH); + if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { + LOG_WARN(("Install directory updater could not be determined.")); + result = FALSE; + } + + result = UpdaterIsValid(installDirUpdater, installDir, argv[4]); + + WCHAR secureUpdaterPath[MAX_PATH + 1] = {L'\0'}; + if (result) { + result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging + } + if (result) { + LOG( + ("Passed in path: '%ls' (ignored); " + "Install dir has: '%ls'; " + "Using this path for updating: '%ls'.", + argv[3], installDirUpdater, secureUpdaterPath)); + DeleteSecureUpdater(secureUpdaterPath); + result = CopyFileW(installDirUpdater, secureUpdaterPath, FALSE); + } + + if (!result) { + LOG_WARN( + ("Could not copy path to secure location. (%lu)", GetLastError())); + if (!WriteStatusFailure(argv[4], SERVICE_COULD_NOT_COPY_UPDATER)) { + LOG_WARN( + ("Could not write update.status could not copy updater error")); + } + } else { + // We obtained the path and copied it successfully, update the path to + // use for the service update. + argv[3] = secureUpdaterPath; + + WCHAR installDirUpdaterINIPath[MAX_PATH + 1] = {L'\0'}; + WCHAR secureUpdaterINIPath[MAX_PATH + 1] = {L'\0'}; + if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, + L"updater.ini") && + PathGetSiblingFilePath(installDirUpdaterINIPath, installDirUpdater, + L"updater.ini")) { + // This is non fatal if it fails there is no real harm + if (!CopyFileW(installDirUpdaterINIPath, secureUpdaterINIPath, FALSE)) { + LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%lu)", + installDirUpdaterINIPath, secureUpdaterINIPath, + GetLastError())); + } + } + + result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); + DeleteSecureUpdater(secureUpdaterPath); + } + + // We might not reach here if the service install succeeded + // because the service self updates itself and the service + // installer will stop the service. + LOG(("Service command %ls complete.", argv[2])); + } else { + LOG_WARN(("Service command not recognized: %ls.", argv[2])); + // result is already set to FALSE + } + + LOG(("service command %ls complete with result: %ls.", argv[1], + (result ? L"Success" : L"Failure"))); + return result; +} |