summaryrefslogtreecommitdiffstats
path: root/onlineupdate/source/service/workmonitor.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'onlineupdate/source/service/workmonitor.cxx')
-rw-r--r--onlineupdate/source/service/workmonitor.cxx770
1 files changed, 770 insertions, 0 deletions
diff --git a/onlineupdate/source/service/workmonitor.cxx b/onlineupdate/source/service/workmonitor.cxx
new file mode 100644
index 000000000..6228b1e6c
--- /dev/null
+++ b/onlineupdate/source/service/workmonitor.cxx
@@ -0,0 +1,770 @@
+/* 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>
+#include <cstddef>
+
+#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")
+
+#include "workmonitor.hxx"
+#include "serviceinstall.hxx"
+#include "servicebase.hxx"
+#include "registrycertificates.hxx"
+#include "uachelper.h"
+#include "updatehelper.h"
+#include "errors.h"
+#include "windowsHelper.hxx"
+
+// 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;
+wchar_t* MakeCommandLine(int argc, wchar_t **argv);
+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+
+/*
+ * Read the update.status file and sets isApplying to true if
+ * the status is set to applying.
+ *
+ * @param updateDirPath The directory where update.status is stored
+ * @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 updateDirPath, BOOL &isApplying)
+{
+ isApplying = FALSE;
+ WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
+ wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
+ if (!PathAppendSafe(updateStatusFilePath, L"update.status"))
+ {
+ LOG_WARN(("Could not append path for update.status file"));
+ return FALSE;
+ }
+
+ AutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ,
+ FILE_SHARE_READ |
+ FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+
+ if (statusFile == INVALID_HANDLE_VALUE)
+ {
+ LOG_WARN(("Could not open update.status file"));
+ return FALSE;
+ }
+
+ char buf[32] = { 0 };
+ DWORD read;
+ if (!ReadFile(statusFile.get(), buf, sizeof(buf), &read, nullptr))
+ {
+ LOG_WARN(("Could not read from update.status file"));
+ return FALSE;
+ }
+
+ LOG(("updater.exe returned status: %s", buf));
+
+ 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)
+{
+ LOG(("Starting update process as the service in session 0."));
+ STARTUPINFO si = {0};
+ si.cb = sizeof(STARTUPINFO);
+ si.lpDesktop = L"winsta0\\Default";
+ PROCESS_INFORMATION pi = {0};
+
+ // The updater command line is of the form:
+ // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
+ LPWSTR cmdLine = 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 = L"";
+ si.dwFlags |= STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ }
+
+ // We move the updater.ini file out of the way because we will handle
+ // executing PostUpdate through the service. We handle PostUpdate from
+ // the service because there are some per user things that happen that
+ // can't run in session 0 which we run updater.exe in.
+ // Once we are done running updater.exe we rename updater.ini back so
+ // that if there were any errors the next updater.exe will run correctly.
+ WCHAR updaterINI[MAX_PATH + 1];
+ WCHAR updaterINITemp[MAX_PATH + 1];
+ BOOL selfHandlePostUpdate = FALSE;
+ // We use the updater.ini from the same directory as the updater.exe
+ // because of background updates.
+ if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") &&
+ PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp"))
+ {
+ selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp,
+ MOVEFILE_REPLACE_EXISTING);
+ }
+
+ // Add an env var for 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 USING_SERVICE.
+ putenv(const_cast<char*>("USING_SERVICE=1"));
+ LOG(("Starting service with cmdline: %ls", cmdLine));
+ processStarted = CreateProcessW(argv[0], cmdLine,
+ nullptr, nullptr, FALSE,
+ CREATE_DEFAULT_ERROR_MODE,
+ nullptr,
+ nullptr, &si, &pi);
+ // Empty value on putenv is how you remove an env variable in Windows
+ putenv(const_cast<char*>("USING_SERVICE="));
+
+ BOOL updateWasSuccessful = FALSE;
+ if (processStarted)
+ {
+ // 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);
+ }
+ 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 %d.", returnCode));
+ // updater returns 0 if successful.
+ updateWasSuccessful = (returnCode == 0);
+ }
+ else
+ {
+ LOG_WARN(("Process finished but could not obtain return code."));
+ }
+ }
+ 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 know 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."));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_STILL_APPLYING_ON_FAILURE))
+ {
+ LOG_WARN(("Could not write update.status still applying on"
+ " success error."));
+ }
+ }
+ }
+ }
+ else
+ {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create process as current user, "
+ "updaterPath: %ls; cmdLine: %ls. (%d)",
+ argv[0], cmdLine, lastError));
+ }
+
+ // Now that we're done with the update, restore back the updater.ini file
+ // We use it ourselves, and also we want it back in case we had any type
+ // of error so that the normal update process can use it.
+ if (selfHandlePostUpdate)
+ {
+ MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING);
+
+ // Only run the PostUpdate if the update was successful
+ if (updateWasSuccessful && argc > index)
+ {
+ LPCWSTR updateInfoDir = argv[1];
+ bool stagingUpdate = IsUpdateBeingStaged(argc, argv);
+
+ // Launch the PostProcess with admin access in session 0. This is
+ // actually launching the post update process but it takes in the
+ // callback app path to figure out where to apply to.
+ // The PostUpdate process with user only access will be done inside
+ // the unelevated updater.exe after the update process is complete
+ // from the service. We don't know here which session to start
+ // the user PostUpdate process from.
+ // Note that we don't need to do this if we're just staging the
+ // update in the background, as the PostUpdate step runs when
+ // performing the replacing in that case.
+ if (!stagingUpdate)
+ {
+ LOG(("Launching post update process as the service in session 0."));
+ if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr))
+ {
+ LOG_WARN(("The post update process could not be launched."
+ " installDir: %ls, updateInfoDir: %ls",
+ installDir, updateInfoDir));
+ }
+ }
+ }
+ }
+
+ free(cmdLine);
+ return updateWasSuccessful;
+}
+
+/**
+ * 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. (%d)",
+ 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;
+ }
+
+ // 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(argv[0], isLocal) || !isLocal)
+ {
+ LOG_WARN(("Filesystem in path %ls is not supported (%d)",
+ argv[0], GetLastError()));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_NOT_FIXED_DRIVE))
+ {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ GetLastError()));
+ }
+ return FALSE;
+ }
+
+ AutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+ if (noWriteLock == INVALID_HANDLE_VALUE)
+ {
+ LOG_WARN(("Could not set no write sharing access on file. (%d)",
+ GetLastError()));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_COULD_NOT_LOCK_UPDATER))
+ {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ 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."));
+ result = FALSE;
+ }
+
+ BOOL updaterIsCorrect = FALSE;
+ if (result && !VerifySameFiles(argv[0], installDirUpdater,
+ updaterIsCorrect))
+ {
+ LOG_WARN(("Error checking if the updaters are the same.\n"
+ "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater));
+ result = FALSE;
+ }
+
+ if (result && !updaterIsCorrect)
+ {
+ LOG_WARN(("The updaters do not match, updater will not run.\n"
+ "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater));
+ result = FALSE;
+ }
+
+ if (result)
+ {
+ LOG(("updater.exe was compared successfully to the installation directory"
+ " updater.exe."));
+ }
+ else
+ {
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_COMPARE_ERROR))
+ {
+ LOG_WARN(("Could not write update.status updater compare failure."));
+ }
+ return FALSE;
+ }
+
+ // 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.
+ HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr,
+ LOAD_LIBRARY_AS_DATAFILE);
+ if (!updaterModule)
+ {
+ LOG_WARN(("updater.exe module could not be loaded. (%d)", 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(argv[1],
+ SERVICE_UPDATER_IDENTITY_ERROR))
+ {
+ LOG_WARN(("Could not write update.status no updater identity."));
+ }
+ return TRUE;
+ }
+
+ // Check for updater.exe sign problems
+ BOOL updaterSignProblem = FALSE;
+#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
+ updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir,
+ argv[0]);
+#endif
+
+ // Only proceed with the update if we have no signing problems
+ if (!updaterSignProblem)
+ {
+ 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 (%d)",
+ 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. (%d)",
+ GetLastError()));
+ }
+ }
+ }
+ }
+ else
+ {
+ result = FALSE;
+ LOG_WARN(("Could not start process due to certificate check error on "
+ "updater.exe. Updating update.status. (%d)", 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. (%d)",
+ 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. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(serviceUpdaterPath))
+ {
+ LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
+ "updater path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(serviceUpdaterPath, L"update"))
+ {
+ LOG_WARN(("Couldn't append file spec when attempting to use a secure "
+ "updater path. (%d)", 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. (%d)", 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] and argv[1]
+ * and automatically included by Windows. 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.
+ RPC_WSTR guidString = RPC_WSTR(L"");
+ GUID guid;
+ HRESULT hr = CoCreateGuid(&guid);
+ if (SUCCEEDED(hr))
+ {
+ UuidToString(&guid, &guidString);
+ }
+ LOG(("Executing service command %ls, ID: %ls",
+ argv[2], reinterpret_cast<LPCWSTR>(guidString)));
+ RpcStringFree(&guidString);
+
+ BOOL result = FALSE;
+ if (!lstrcmpi(argv[2], L"software-update"))
+ {
+
+ // Use the passed in command line arguments for the update, except for the
+ // path to updater.exe. We copy updater.exe to 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.
+ LPWSTR oldUpdaterPath = argv[3];
+ WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
+ result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging
+ if (result)
+ {
+ LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
+ oldUpdaterPath, secureUpdaterPath));
+ DeleteSecureUpdater(secureUpdaterPath);
+ result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE);
+ }
+
+ if (!result)
+ {
+ LOG_WARN(("Could not copy path to secure location. (%d)",
+ GetLastError()));
+ if (argc > 4 && !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 oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
+ WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
+ if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath,
+ L"updater.ini") &&
+ PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath,
+ L"updater.ini"))
+ {
+ // This is non fatal if it fails there is no real harm
+ if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE))
+ {
+ LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
+ oldUpdaterINIPath, 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 TRUE;
+}