1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/maintenanceservice/workmonitor.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,633 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include <shlobj.h> 1.9 +#include <shlwapi.h> 1.10 +#include <wtsapi32.h> 1.11 +#include <userenv.h> 1.12 +#include <shellapi.h> 1.13 + 1.14 +#pragma comment(lib, "wtsapi32.lib") 1.15 +#pragma comment(lib, "userenv.lib") 1.16 +#pragma comment(lib, "shlwapi.lib") 1.17 +#pragma comment(lib, "ole32.lib") 1.18 +#pragma comment(lib, "rpcrt4.lib") 1.19 + 1.20 +#include "nsWindowsHelpers.h" 1.21 + 1.22 +#include "workmonitor.h" 1.23 +#include "serviceinstall.h" 1.24 +#include "servicebase.h" 1.25 +#include "registrycertificates.h" 1.26 +#include "uachelper.h" 1.27 +#include "updatehelper.h" 1.28 +#include "errors.h" 1.29 + 1.30 +// Wait 15 minutes for an update operation to run at most. 1.31 +// Updates usually take less than a minute so this seems like a 1.32 +// significantly large and safe amount of time to wait. 1.33 +static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; 1.34 +char16_t* MakeCommandLine(int argc, char16_t **argv); 1.35 +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); 1.36 +BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, 1.37 + LPCWSTR newFileName); 1.38 + 1.39 +/* 1.40 + * Read the update.status file and sets isApplying to true if 1.41 + * the status is set to applying 1.42 + * 1.43 + * @param updateDirPath The directory where update.status is stored 1.44 + * @param isApplying Out parameter for specifying if the status 1.45 + * is set to applying or not. 1.46 + * @return TRUE if the information was filled. 1.47 +*/ 1.48 +static BOOL 1.49 +IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) 1.50 +{ 1.51 + isApplying = FALSE; 1.52 + WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'}; 1.53 + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); 1.54 + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { 1.55 + LOG_WARN(("Could not append path for update.status file")); 1.56 + return FALSE; 1.57 + } 1.58 + 1.59 + nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, 1.60 + FILE_SHARE_READ | 1.61 + FILE_SHARE_WRITE | 1.62 + FILE_SHARE_DELETE, 1.63 + nullptr, OPEN_EXISTING, 0, nullptr)); 1.64 + 1.65 + if (INVALID_HANDLE_VALUE == statusFile) { 1.66 + LOG_WARN(("Could not open update.status file")); 1.67 + return FALSE; 1.68 + } 1.69 + 1.70 + char buf[32] = { 0 }; 1.71 + DWORD read; 1.72 + if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) { 1.73 + LOG_WARN(("Could not read from update.status file")); 1.74 + return FALSE; 1.75 + } 1.76 + 1.77 + LOG(("updater.exe returned status: %s", buf)); 1.78 + 1.79 + const char kApplying[] = "applying"; 1.80 + isApplying = strncmp(buf, kApplying, 1.81 + sizeof(kApplying) - 1) == 0; 1.82 + return TRUE; 1.83 +} 1.84 + 1.85 +/** 1.86 + * Determines whether we're staging an update. 1.87 + * 1.88 + * @param argc The argc value normally sent to updater.exe 1.89 + * @param argv The argv value normally sent to updater.exe 1.90 + * @param boolean True if we're staging an update 1.91 + */ 1.92 +static bool 1.93 +IsUpdateBeingStaged(int argc, LPWSTR *argv) 1.94 +{ 1.95 + // PID will be set to -1 if we're supposed to stage an update. 1.96 + return argc == 4 && !wcscmp(argv[3], L"-1"); 1.97 +} 1.98 + 1.99 +/** 1.100 + * Gets the installation directory from the arguments passed to updater.exe. 1.101 + * 1.102 + * @param argcTmp The argc value normally sent to updater.exe 1.103 + * @param argvTmp The argv value normally sent to updater.exe 1.104 + * @param aResultDir Buffer to hold the installation directory. 1.105 + */ 1.106 +static BOOL 1.107 +GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1]) 1.108 +{ 1.109 + if (argcTmp < 2) { 1.110 + return FALSE; 1.111 + } 1.112 + wcsncpy(aResultDir, argvTmp[2], MAX_PATH); 1.113 + WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); 1.114 + // Make sure that the path does not include trailing backslashes 1.115 + if (backSlash && (backSlash[1] == L'\0')) { 1.116 + *backSlash = L'\0'; 1.117 + } 1.118 + bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); 1.119 + bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); 1.120 + if (backgroundUpdate || replaceRequest) { 1.121 + return PathRemoveFileSpecW(aResultDir); 1.122 + } 1.123 + return TRUE; 1.124 +} 1.125 + 1.126 +/** 1.127 + * Runs an update process as the service using the SYSTEM account. 1.128 + * 1.129 + * @param argc The number of arguments in argv 1.130 + * @param argv The arguments normally passed to updater.exe 1.131 + * argv[0] must be the path to updater.exe 1.132 + * @param processStarted Set to TRUE if the process was started. 1.133 + * @return TRUE if the update process was run had a return code of 0. 1.134 + */ 1.135 +BOOL 1.136 +StartUpdateProcess(int argc, 1.137 + LPWSTR *argv, 1.138 + LPCWSTR installDir, 1.139 + BOOL &processStarted) 1.140 +{ 1.141 + LOG(("Starting update process as the service in session 0.")); 1.142 + STARTUPINFO si = {0}; 1.143 + si.cb = sizeof(STARTUPINFO); 1.144 + si.lpDesktop = L"winsta0\\Default"; 1.145 + PROCESS_INFORMATION pi = {0}; 1.146 + 1.147 + // The updater command line is of the form: 1.148 + // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] 1.149 + LPWSTR cmdLine = MakeCommandLine(argc, argv); 1.150 + 1.151 + // If we're about to start the update process from session 0, 1.152 + // then we should not show a GUI. This only really needs to be done 1.153 + // on Vista and higher, but it's better to keep everything consistent 1.154 + // across all OS if it's of no harm. 1.155 + if (argc >= 2 ) { 1.156 + // Setting the desktop to blank will ensure no GUI is displayed 1.157 + si.lpDesktop = L""; 1.158 + si.dwFlags |= STARTF_USESHOWWINDOW; 1.159 + si.wShowWindow = SW_HIDE; 1.160 + } 1.161 + 1.162 + // We move the updater.ini file out of the way because we will handle 1.163 + // executing PostUpdate through the service. We handle PostUpdate from 1.164 + // the service because there are some per user things that happen that 1.165 + // can't run in session 0 which we run updater.exe in. 1.166 + // Once we are done running updater.exe we rename updater.ini back so 1.167 + // that if there were any errors the next updater.exe will run correctly. 1.168 + WCHAR updaterINI[MAX_PATH + 1]; 1.169 + WCHAR updaterINITemp[MAX_PATH + 1]; 1.170 + BOOL selfHandlePostUpdate = FALSE; 1.171 + // We use the updater.ini from the same directory as the updater.exe 1.172 + // because of background updates. 1.173 + if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") && 1.174 + PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) { 1.175 + selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp, 1.176 + MOVEFILE_REPLACE_EXISTING); 1.177 + } 1.178 + 1.179 + // Add an env var for MOZ_USING_SERVICE so the updater.exe can 1.180 + // do anything special that it needs to do for service updates. 1.181 + // Search in updater.cpp for more info on MOZ_USING_SERVICE. 1.182 + putenv(const_cast<char*>("MOZ_USING_SERVICE=1")); 1.183 + LOG(("Starting service with cmdline: %ls", cmdLine)); 1.184 + processStarted = CreateProcessW(argv[0], cmdLine, 1.185 + nullptr, nullptr, FALSE, 1.186 + CREATE_DEFAULT_ERROR_MODE, 1.187 + nullptr, 1.188 + nullptr, &si, &pi); 1.189 + // Empty value on putenv is how you remove an env variable in Windows 1.190 + putenv(const_cast<char*>("MOZ_USING_SERVICE=")); 1.191 + 1.192 + BOOL updateWasSuccessful = FALSE; 1.193 + if (processStarted) { 1.194 + // Wait for the updater process to finish 1.195 + LOG(("Process was started... waiting on result.")); 1.196 + DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); 1.197 + if (WAIT_TIMEOUT == waitRes) { 1.198 + // We waited a long period of time for updater.exe and it never finished 1.199 + // so kill it. 1.200 + TerminateProcess(pi.hProcess, 1); 1.201 + } else { 1.202 + // Check the return code of updater.exe to make sure we get 0 1.203 + DWORD returnCode; 1.204 + if (GetExitCodeProcess(pi.hProcess, &returnCode)) { 1.205 + LOG(("Process finished with return code %d.", returnCode)); 1.206 + // updater returns 0 if successful. 1.207 + updateWasSuccessful = (returnCode == 0); 1.208 + } else { 1.209 + LOG_WARN(("Process finished but could not obtain return code.")); 1.210 + } 1.211 + } 1.212 + CloseHandle(pi.hProcess); 1.213 + CloseHandle(pi.hThread); 1.214 + 1.215 + // Check just in case updater.exe didn't change the status from 1.216 + // applying. If this is the case we report an error. 1.217 + BOOL isApplying = FALSE; 1.218 + if (IsStatusApplying(argv[1], isApplying) && isApplying) { 1.219 + if (updateWasSuccessful) { 1.220 + LOG(("update.status is still applying even know update " 1.221 + " was successful.")); 1.222 + if (!WriteStatusFailure(argv[1], 1.223 + SERVICE_STILL_APPLYING_ON_SUCCESS)) { 1.224 + LOG_WARN(("Could not write update.status still applying on" 1.225 + " success error.")); 1.226 + } 1.227 + // Since we still had applying we know updater.exe didn't do its 1.228 + // job correctly. 1.229 + updateWasSuccessful = FALSE; 1.230 + } else { 1.231 + LOG_WARN(("update.status is still applying and update was not successful.")); 1.232 + if (!WriteStatusFailure(argv[1], 1.233 + SERVICE_STILL_APPLYING_ON_FAILURE)) { 1.234 + LOG_WARN(("Could not write update.status still applying on" 1.235 + " success error.")); 1.236 + } 1.237 + } 1.238 + } 1.239 + } else { 1.240 + DWORD lastError = GetLastError(); 1.241 + LOG_WARN(("Could not create process as current user, " 1.242 + "updaterPath: %ls; cmdLine: %ls. (%d)", 1.243 + argv[0], cmdLine, lastError)); 1.244 + } 1.245 + 1.246 + // Now that we're done with the update, restore back the updater.ini file 1.247 + // We use it ourselves, and also we want it back in case we had any type 1.248 + // of error so that the normal update process can use it. 1.249 + if (selfHandlePostUpdate) { 1.250 + MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); 1.251 + 1.252 + // Only run the PostUpdate if the update was successful 1.253 + if (updateWasSuccessful && argc > 2) { 1.254 + LPCWSTR updateInfoDir = argv[1]; 1.255 + bool backgroundUpdate = IsUpdateBeingStaged(argc, argv); 1.256 + 1.257 + // Launch the PostProcess with admin access in session 0. This is 1.258 + // actually launching the post update process but it takes in the 1.259 + // callback app path to figure out where to apply to. 1.260 + // The PostUpdate process with user only access will be done inside 1.261 + // the unelevated updater.exe after the update process is complete 1.262 + // from the service. We don't know here which session to start 1.263 + // the user PostUpdate process from. 1.264 + // Note that we don't need to do this if we're just staging the 1.265 + // update in the background, as the PostUpdate step runs when 1.266 + // performing the replacing in that case. 1.267 + if (!backgroundUpdate) { 1.268 + LOG(("Launching post update process as the service in session 0.")); 1.269 + if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) { 1.270 + LOG_WARN(("The post update process could not be launched." 1.271 + " installDir: %ls, updateInfoDir: %ls", 1.272 + installDir, updateInfoDir)); 1.273 + } 1.274 + } 1.275 + } 1.276 + } 1.277 + 1.278 + free(cmdLine); 1.279 + return updateWasSuccessful; 1.280 +} 1.281 + 1.282 +/** 1.283 + * Processes a software update command 1.284 + * 1.285 + * @param argc The number of arguments in argv 1.286 + * @param argv The arguments normally passed to updater.exe 1.287 + * argv[0] must be the path to updater.exe 1.288 + * @return TRUE if the update was successful. 1.289 + */ 1.290 +BOOL 1.291 +ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) 1.292 +{ 1.293 + BOOL result = TRUE; 1.294 + if (argc < 3) { 1.295 + LOG_WARN(("Not enough command line parameters specified. " 1.296 + "Updating update.status.")); 1.297 + 1.298 + // We can only update update.status if argv[1] exists. argv[1] is 1.299 + // the directory where the update.status file exists. 1.300 + if (argc < 2 || 1.301 + !WriteStatusFailure(argv[1], 1.302 + SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { 1.303 + LOG_WARN(("Could not write update.status service update failure. (%d)", 1.304 + GetLastError())); 1.305 + } 1.306 + return FALSE; 1.307 + } 1.308 + 1.309 + WCHAR installDir[MAX_PATH + 1] = {L'\0'}; 1.310 + if (!GetInstallationDir(argc, argv, installDir)) { 1.311 + LOG_WARN(("Could not get the installation directory")); 1.312 + if (!WriteStatusFailure(argv[1], 1.313 + SERVICE_INSTALLDIR_ERROR)) { 1.314 + LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); 1.315 + } 1.316 + return FALSE; 1.317 + } 1.318 + 1.319 + // Make sure the path to the updater to use for the update is local. 1.320 + // We do this check to make sure that file locking is available for 1.321 + // race condition security checks. 1.322 + BOOL isLocal = FALSE; 1.323 + if (!IsLocalFile(argv[0], isLocal) || !isLocal) { 1.324 + LOG_WARN(("Filesystem in path %ls is not supported (%d)", 1.325 + argv[0], GetLastError())); 1.326 + if (!WriteStatusFailure(argv[1], 1.327 + SERVICE_UPDATER_NOT_FIXED_DRIVE)) { 1.328 + LOG_WARN(("Could not write update.status service update failure. (%d)", 1.329 + GetLastError())); 1.330 + } 1.331 + return FALSE; 1.332 + } 1.333 + 1.334 + nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, 1.335 + nullptr, OPEN_EXISTING, 0, nullptr)); 1.336 + if (INVALID_HANDLE_VALUE == noWriteLock) { 1.337 + LOG_WARN(("Could not set no write sharing access on file. (%d)", 1.338 + GetLastError())); 1.339 + if (!WriteStatusFailure(argv[1], 1.340 + SERVICE_COULD_NOT_LOCK_UPDATER)) { 1.341 + LOG_WARN(("Could not write update.status service update failure. (%d)", 1.342 + GetLastError())); 1.343 + } 1.344 + return FALSE; 1.345 + } 1.346 + 1.347 + // Verify that the updater.exe that we are executing is the same 1.348 + // as the one in the installation directory which we are updating. 1.349 + // The installation dir that we are installing to is installDir. 1.350 + WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; 1.351 + wcsncpy(installDirUpdater, installDir, MAX_PATH); 1.352 + if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { 1.353 + LOG_WARN(("Install directory updater could not be determined.")); 1.354 + result = FALSE; 1.355 + } 1.356 + 1.357 + BOOL updaterIsCorrect; 1.358 + if (result && !VerifySameFiles(argv[0], installDirUpdater, 1.359 + updaterIsCorrect)) { 1.360 + LOG_WARN(("Error checking if the updaters are the same.\n" 1.361 + "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); 1.362 + result = FALSE; 1.363 + } 1.364 + 1.365 + if (result && !updaterIsCorrect) { 1.366 + LOG_WARN(("The updaters do not match, updater will not run.")); 1.367 + result = FALSE; 1.368 + } 1.369 + 1.370 + if (result) { 1.371 + LOG(("updater.exe was compared successfully to the installation directory" 1.372 + " updater.exe.")); 1.373 + } else { 1.374 + if (!WriteStatusFailure(argv[1], 1.375 + SERVICE_UPDATER_COMPARE_ERROR)) { 1.376 + LOG_WARN(("Could not write update.status updater compare failure.")); 1.377 + } 1.378 + return FALSE; 1.379 + } 1.380 + 1.381 + // Check to make sure the updater.exe module has the unique updater identity. 1.382 + // This is a security measure to make sure that the signed executable that 1.383 + // we will run is actually an updater. 1.384 + HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, 1.385 + LOAD_LIBRARY_AS_DATAFILE); 1.386 + if (!updaterModule) { 1.387 + LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); 1.388 + result = FALSE; 1.389 + } else { 1.390 + char updaterIdentity[64]; 1.391 + if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, 1.392 + updaterIdentity, sizeof(updaterIdentity))) { 1.393 + LOG_WARN(("The updater.exe application does not contain the Mozilla" 1.394 + " updater identity.")); 1.395 + result = FALSE; 1.396 + } 1.397 + 1.398 + if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { 1.399 + LOG_WARN(("The updater.exe identity string is not valid.")); 1.400 + result = FALSE; 1.401 + } 1.402 + FreeLibrary(updaterModule); 1.403 + } 1.404 + 1.405 + if (result) { 1.406 + LOG(("The updater.exe application contains the Mozilla" 1.407 + " updater identity.")); 1.408 + } else { 1.409 + if (!WriteStatusFailure(argv[1], 1.410 + SERVICE_UPDATER_IDENTITY_ERROR)) { 1.411 + LOG_WARN(("Could not write update.status no updater identity.")); 1.412 + } 1.413 + return TRUE; 1.414 + } 1.415 + 1.416 + // Check for updater.exe sign problems 1.417 + BOOL updaterSignProblem = FALSE; 1.418 +#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK 1.419 + updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, 1.420 + argv[0]); 1.421 +#endif 1.422 + 1.423 + // Only proceed with the update if we have no signing problems 1.424 + if (!updaterSignProblem) { 1.425 + BOOL updateProcessWasStarted = FALSE; 1.426 + if (StartUpdateProcess(argc, argv, installDir, 1.427 + updateProcessWasStarted)) { 1.428 + LOG(("updater.exe was launched and run successfully!")); 1.429 + LogFlush(); 1.430 + 1.431 + // Don't attempt to update the service when the update is being staged. 1.432 + if (!IsUpdateBeingStaged(argc, argv)) { 1.433 + // We might not execute code after StartServiceUpdate because 1.434 + // the service installer will stop the service if it is running. 1.435 + StartServiceUpdate(installDir); 1.436 + } 1.437 + } else { 1.438 + result = FALSE; 1.439 + LOG_WARN(("Error running update process. Updating update.status (%d)", 1.440 + GetLastError())); 1.441 + LogFlush(); 1.442 + 1.443 + // If the update process was started, then updater.exe is responsible for 1.444 + // setting the failure code. If it could not be started then we do the 1.445 + // work. We set an error instead of directly setting status pending 1.446 + // so that the app.update.service.errors pref can be updated when 1.447 + // the callback app restarts. 1.448 + if (!updateProcessWasStarted) { 1.449 + if (!WriteStatusFailure(argv[1], 1.450 + SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { 1.451 + LOG_WARN(("Could not write update.status service update failure. (%d)", 1.452 + GetLastError())); 1.453 + } 1.454 + } 1.455 + } 1.456 + } else { 1.457 + result = FALSE; 1.458 + LOG_WARN(("Could not start process due to certificate check error on " 1.459 + "updater.exe. Updating update.status. (%d)", GetLastError())); 1.460 + 1.461 + // When there is a certificate check error on the updater.exe application, 1.462 + // we want to write out the error. 1.463 + if (!WriteStatusFailure(argv[1], 1.464 + SERVICE_UPDATER_SIGN_ERROR)) { 1.465 + LOG_WARN(("Could not write pending state to update.status. (%d)", 1.466 + GetLastError())); 1.467 + } 1.468 + } 1.469 + 1.470 + return result; 1.471 +} 1.472 + 1.473 +/** 1.474 + * Obtains the updater path alongside a subdir of the service binary. 1.475 + * The purpose of this function is to return a path that is likely high 1.476 + * integrity and therefore more safe to execute code from. 1.477 + * 1.478 + * @param serviceUpdaterPath Out parameter for the path where the updater 1.479 + * should be copied to. 1.480 + * @return TRUE if a file path was obtained. 1.481 + */ 1.482 +BOOL 1.483 +GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) 1.484 +{ 1.485 + if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) { 1.486 + LOG_WARN(("Could not obtain module filename when attempting to " 1.487 + "use a secure updater path. (%d)", GetLastError())); 1.488 + return FALSE; 1.489 + } 1.490 + 1.491 + if (!PathRemoveFileSpecW(serviceUpdaterPath)) { 1.492 + LOG_WARN(("Couldn't remove file spec when attempting to use a secure " 1.493 + "updater path. (%d)", GetLastError())); 1.494 + return FALSE; 1.495 + } 1.496 + 1.497 + if (!PathAppendSafe(serviceUpdaterPath, L"update")) { 1.498 + LOG_WARN(("Couldn't append file spec when attempting to use a secure " 1.499 + "updater path. (%d)", GetLastError())); 1.500 + return FALSE; 1.501 + } 1.502 + 1.503 + CreateDirectoryW(serviceUpdaterPath, nullptr); 1.504 + 1.505 + if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) { 1.506 + LOG_WARN(("Couldn't append file spec when attempting to use a secure " 1.507 + "updater path. (%d)", GetLastError())); 1.508 + return FALSE; 1.509 + } 1.510 + 1.511 + return TRUE; 1.512 +} 1.513 + 1.514 +/** 1.515 + * Deletes the passed in updater path and the associated updater.ini file. 1.516 + * 1.517 + * @param serviceUpdaterPath The path to delete. 1.518 + * @return TRUE if a file was deleted. 1.519 + */ 1.520 +BOOL 1.521 +DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) 1.522 +{ 1.523 + BOOL result = FALSE; 1.524 + if (serviceUpdaterPath[0]) { 1.525 + result = DeleteFileW(serviceUpdaterPath); 1.526 + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && 1.527 + GetLastError() != ERROR_FILE_NOT_FOUND) { 1.528 + LOG_WARN(("Could not delete service updater path: '%ls'.", 1.529 + serviceUpdaterPath)); 1.530 + } 1.531 + 1.532 + WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' }; 1.533 + if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, 1.534 + L"updater.ini")) { 1.535 + result = DeleteFileW(updaterINIPath); 1.536 + if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && 1.537 + GetLastError() != ERROR_FILE_NOT_FOUND) { 1.538 + LOG_WARN(("Could not delete service updater INI path: '%ls'.", 1.539 + updaterINIPath)); 1.540 + } 1.541 + } 1.542 + } 1.543 + return result; 1.544 +} 1.545 + 1.546 +/** 1.547 + * Executes a service command. 1.548 + * 1.549 + * @param argc The number of arguments in argv 1.550 + * @param argv The service command line arguments, argv[0] and argv[1] 1.551 + * and automatically included by Windows. argv[2] is the 1.552 + * service command. 1.553 + * 1.554 + * @return FALSE if there was an error executing the service command. 1.555 + */ 1.556 +BOOL 1.557 +ExecuteServiceCommand(int argc, LPWSTR *argv) 1.558 +{ 1.559 + if (argc < 3) { 1.560 + LOG_WARN(("Not enough command line arguments to execute a service command")); 1.561 + return FALSE; 1.562 + } 1.563 + 1.564 + // The tests work by making sure the log has changed, so we put a 1.565 + // unique ID in the log. 1.566 + RPC_WSTR guidString = RPC_WSTR(L""); 1.567 + GUID guid; 1.568 + HRESULT hr = CoCreateGuid(&guid); 1.569 + if (SUCCEEDED(hr)) { 1.570 + UuidToString(&guid, &guidString); 1.571 + } 1.572 + LOG(("Executing service command %ls, ID: %ls", 1.573 + argv[2], reinterpret_cast<LPCWSTR>(guidString))); 1.574 + RpcStringFree(&guidString); 1.575 + 1.576 + BOOL result = FALSE; 1.577 + if (!lstrcmpi(argv[2], L"software-update")) { 1.578 + 1.579 + // Use the passed in command line arguments for the update, except for the 1.580 + // path to updater.exe. We copy updater.exe to a the directory of the 1.581 + // MozillaMaintenance service so that a low integrity process cannot 1.582 + // replace the updater.exe at any point and use that for the update. 1.583 + // It also makes DLL injection attacks harder. 1.584 + LPWSTR oldUpdaterPath = argv[3]; 1.585 + WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' }; 1.586 + result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging 1.587 + if (result) { 1.588 + LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.", 1.589 + oldUpdaterPath, secureUpdaterPath)); 1.590 + DeleteSecureUpdater(secureUpdaterPath); 1.591 + result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE); 1.592 + } 1.593 + 1.594 + if (!result) { 1.595 + LOG_WARN(("Could not copy path to secure location. (%d)", 1.596 + GetLastError())); 1.597 + if (argc > 4 && !WriteStatusFailure(argv[4], 1.598 + SERVICE_COULD_NOT_COPY_UPDATER)) { 1.599 + LOG_WARN(("Could not write update.status could not copy updater error")); 1.600 + } 1.601 + } else { 1.602 + 1.603 + // We obtained the path and copied it successfully, update the path to 1.604 + // use for the service update. 1.605 + argv[3] = secureUpdaterPath; 1.606 + 1.607 + WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; 1.608 + WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; 1.609 + if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, 1.610 + L"updater.ini") && 1.611 + PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath, 1.612 + L"updater.ini")) { 1.613 + // This is non fatal if it fails there is no real harm 1.614 + if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) { 1.615 + LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)", 1.616 + oldUpdaterINIPath, secureUpdaterINIPath, GetLastError())); 1.617 + } 1.618 + } 1.619 + 1.620 + result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); 1.621 + DeleteSecureUpdater(secureUpdaterPath); 1.622 + } 1.623 + 1.624 + // We might not reach here if the service install succeeded 1.625 + // because the service self updates itself and the service 1.626 + // installer will stop the service. 1.627 + LOG(("Service command %ls complete.", argv[2])); 1.628 + } else { 1.629 + LOG_WARN(("Service command not recognized: %ls.", argv[2])); 1.630 + // result is already set to FALSE 1.631 + } 1.632 + 1.633 + LOG(("service command %ls complete with result: %ls.", 1.634 + argv[1], (result ? L"Success" : L"Failure"))); 1.635 + return TRUE; 1.636 +}