toolkit/components/maintenanceservice/workmonitor.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include <shlobj.h>
michael@0 6 #include <shlwapi.h>
michael@0 7 #include <wtsapi32.h>
michael@0 8 #include <userenv.h>
michael@0 9 #include <shellapi.h>
michael@0 10
michael@0 11 #pragma comment(lib, "wtsapi32.lib")
michael@0 12 #pragma comment(lib, "userenv.lib")
michael@0 13 #pragma comment(lib, "shlwapi.lib")
michael@0 14 #pragma comment(lib, "ole32.lib")
michael@0 15 #pragma comment(lib, "rpcrt4.lib")
michael@0 16
michael@0 17 #include "nsWindowsHelpers.h"
michael@0 18
michael@0 19 #include "workmonitor.h"
michael@0 20 #include "serviceinstall.h"
michael@0 21 #include "servicebase.h"
michael@0 22 #include "registrycertificates.h"
michael@0 23 #include "uachelper.h"
michael@0 24 #include "updatehelper.h"
michael@0 25 #include "errors.h"
michael@0 26
michael@0 27 // Wait 15 minutes for an update operation to run at most.
michael@0 28 // Updates usually take less than a minute so this seems like a
michael@0 29 // significantly large and safe amount of time to wait.
michael@0 30 static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
michael@0 31 char16_t* MakeCommandLine(int argc, char16_t **argv);
michael@0 32 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
michael@0 33 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
michael@0 34 LPCWSTR newFileName);
michael@0 35
michael@0 36 /*
michael@0 37 * Read the update.status file and sets isApplying to true if
michael@0 38 * the status is set to applying
michael@0 39 *
michael@0 40 * @param updateDirPath The directory where update.status is stored
michael@0 41 * @param isApplying Out parameter for specifying if the status
michael@0 42 * is set to applying or not.
michael@0 43 * @return TRUE if the information was filled.
michael@0 44 */
michael@0 45 static BOOL
michael@0 46 IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying)
michael@0 47 {
michael@0 48 isApplying = FALSE;
michael@0 49 WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
michael@0 50 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
michael@0 51 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
michael@0 52 LOG_WARN(("Could not append path for update.status file"));
michael@0 53 return FALSE;
michael@0 54 }
michael@0 55
michael@0 56 nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ,
michael@0 57 FILE_SHARE_READ |
michael@0 58 FILE_SHARE_WRITE |
michael@0 59 FILE_SHARE_DELETE,
michael@0 60 nullptr, OPEN_EXISTING, 0, nullptr));
michael@0 61
michael@0 62 if (INVALID_HANDLE_VALUE == statusFile) {
michael@0 63 LOG_WARN(("Could not open update.status file"));
michael@0 64 return FALSE;
michael@0 65 }
michael@0 66
michael@0 67 char buf[32] = { 0 };
michael@0 68 DWORD read;
michael@0 69 if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) {
michael@0 70 LOG_WARN(("Could not read from update.status file"));
michael@0 71 return FALSE;
michael@0 72 }
michael@0 73
michael@0 74 LOG(("updater.exe returned status: %s", buf));
michael@0 75
michael@0 76 const char kApplying[] = "applying";
michael@0 77 isApplying = strncmp(buf, kApplying,
michael@0 78 sizeof(kApplying) - 1) == 0;
michael@0 79 return TRUE;
michael@0 80 }
michael@0 81
michael@0 82 /**
michael@0 83 * Determines whether we're staging an update.
michael@0 84 *
michael@0 85 * @param argc The argc value normally sent to updater.exe
michael@0 86 * @param argv The argv value normally sent to updater.exe
michael@0 87 * @param boolean True if we're staging an update
michael@0 88 */
michael@0 89 static bool
michael@0 90 IsUpdateBeingStaged(int argc, LPWSTR *argv)
michael@0 91 {
michael@0 92 // PID will be set to -1 if we're supposed to stage an update.
michael@0 93 return argc == 4 && !wcscmp(argv[3], L"-1");
michael@0 94 }
michael@0 95
michael@0 96 /**
michael@0 97 * Gets the installation directory from the arguments passed to updater.exe.
michael@0 98 *
michael@0 99 * @param argcTmp The argc value normally sent to updater.exe
michael@0 100 * @param argvTmp The argv value normally sent to updater.exe
michael@0 101 * @param aResultDir Buffer to hold the installation directory.
michael@0 102 */
michael@0 103 static BOOL
michael@0 104 GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1])
michael@0 105 {
michael@0 106 if (argcTmp < 2) {
michael@0 107 return FALSE;
michael@0 108 }
michael@0 109 wcsncpy(aResultDir, argvTmp[2], MAX_PATH);
michael@0 110 WCHAR* backSlash = wcsrchr(aResultDir, L'\\');
michael@0 111 // Make sure that the path does not include trailing backslashes
michael@0 112 if (backSlash && (backSlash[1] == L'\0')) {
michael@0 113 *backSlash = L'\0';
michael@0 114 }
michael@0 115 bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp);
michael@0 116 bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace"));
michael@0 117 if (backgroundUpdate || replaceRequest) {
michael@0 118 return PathRemoveFileSpecW(aResultDir);
michael@0 119 }
michael@0 120 return TRUE;
michael@0 121 }
michael@0 122
michael@0 123 /**
michael@0 124 * Runs an update process as the service using the SYSTEM account.
michael@0 125 *
michael@0 126 * @param argc The number of arguments in argv
michael@0 127 * @param argv The arguments normally passed to updater.exe
michael@0 128 * argv[0] must be the path to updater.exe
michael@0 129 * @param processStarted Set to TRUE if the process was started.
michael@0 130 * @return TRUE if the update process was run had a return code of 0.
michael@0 131 */
michael@0 132 BOOL
michael@0 133 StartUpdateProcess(int argc,
michael@0 134 LPWSTR *argv,
michael@0 135 LPCWSTR installDir,
michael@0 136 BOOL &processStarted)
michael@0 137 {
michael@0 138 LOG(("Starting update process as the service in session 0."));
michael@0 139 STARTUPINFO si = {0};
michael@0 140 si.cb = sizeof(STARTUPINFO);
michael@0 141 si.lpDesktop = L"winsta0\\Default";
michael@0 142 PROCESS_INFORMATION pi = {0};
michael@0 143
michael@0 144 // The updater command line is of the form:
michael@0 145 // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
michael@0 146 LPWSTR cmdLine = MakeCommandLine(argc, argv);
michael@0 147
michael@0 148 // If we're about to start the update process from session 0,
michael@0 149 // then we should not show a GUI. This only really needs to be done
michael@0 150 // on Vista and higher, but it's better to keep everything consistent
michael@0 151 // across all OS if it's of no harm.
michael@0 152 if (argc >= 2 ) {
michael@0 153 // Setting the desktop to blank will ensure no GUI is displayed
michael@0 154 si.lpDesktop = L"";
michael@0 155 si.dwFlags |= STARTF_USESHOWWINDOW;
michael@0 156 si.wShowWindow = SW_HIDE;
michael@0 157 }
michael@0 158
michael@0 159 // We move the updater.ini file out of the way because we will handle
michael@0 160 // executing PostUpdate through the service. We handle PostUpdate from
michael@0 161 // the service because there are some per user things that happen that
michael@0 162 // can't run in session 0 which we run updater.exe in.
michael@0 163 // Once we are done running updater.exe we rename updater.ini back so
michael@0 164 // that if there were any errors the next updater.exe will run correctly.
michael@0 165 WCHAR updaterINI[MAX_PATH + 1];
michael@0 166 WCHAR updaterINITemp[MAX_PATH + 1];
michael@0 167 BOOL selfHandlePostUpdate = FALSE;
michael@0 168 // We use the updater.ini from the same directory as the updater.exe
michael@0 169 // because of background updates.
michael@0 170 if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") &&
michael@0 171 PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) {
michael@0 172 selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp,
michael@0 173 MOVEFILE_REPLACE_EXISTING);
michael@0 174 }
michael@0 175
michael@0 176 // Add an env var for MOZ_USING_SERVICE so the updater.exe can
michael@0 177 // do anything special that it needs to do for service updates.
michael@0 178 // Search in updater.cpp for more info on MOZ_USING_SERVICE.
michael@0 179 putenv(const_cast<char*>("MOZ_USING_SERVICE=1"));
michael@0 180 LOG(("Starting service with cmdline: %ls", cmdLine));
michael@0 181 processStarted = CreateProcessW(argv[0], cmdLine,
michael@0 182 nullptr, nullptr, FALSE,
michael@0 183 CREATE_DEFAULT_ERROR_MODE,
michael@0 184 nullptr,
michael@0 185 nullptr, &si, &pi);
michael@0 186 // Empty value on putenv is how you remove an env variable in Windows
michael@0 187 putenv(const_cast<char*>("MOZ_USING_SERVICE="));
michael@0 188
michael@0 189 BOOL updateWasSuccessful = FALSE;
michael@0 190 if (processStarted) {
michael@0 191 // Wait for the updater process to finish
michael@0 192 LOG(("Process was started... waiting on result."));
michael@0 193 DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
michael@0 194 if (WAIT_TIMEOUT == waitRes) {
michael@0 195 // We waited a long period of time for updater.exe and it never finished
michael@0 196 // so kill it.
michael@0 197 TerminateProcess(pi.hProcess, 1);
michael@0 198 } else {
michael@0 199 // Check the return code of updater.exe to make sure we get 0
michael@0 200 DWORD returnCode;
michael@0 201 if (GetExitCodeProcess(pi.hProcess, &returnCode)) {
michael@0 202 LOG(("Process finished with return code %d.", returnCode));
michael@0 203 // updater returns 0 if successful.
michael@0 204 updateWasSuccessful = (returnCode == 0);
michael@0 205 } else {
michael@0 206 LOG_WARN(("Process finished but could not obtain return code."));
michael@0 207 }
michael@0 208 }
michael@0 209 CloseHandle(pi.hProcess);
michael@0 210 CloseHandle(pi.hThread);
michael@0 211
michael@0 212 // Check just in case updater.exe didn't change the status from
michael@0 213 // applying. If this is the case we report an error.
michael@0 214 BOOL isApplying = FALSE;
michael@0 215 if (IsStatusApplying(argv[1], isApplying) && isApplying) {
michael@0 216 if (updateWasSuccessful) {
michael@0 217 LOG(("update.status is still applying even know update "
michael@0 218 " was successful."));
michael@0 219 if (!WriteStatusFailure(argv[1],
michael@0 220 SERVICE_STILL_APPLYING_ON_SUCCESS)) {
michael@0 221 LOG_WARN(("Could not write update.status still applying on"
michael@0 222 " success error."));
michael@0 223 }
michael@0 224 // Since we still had applying we know updater.exe didn't do its
michael@0 225 // job correctly.
michael@0 226 updateWasSuccessful = FALSE;
michael@0 227 } else {
michael@0 228 LOG_WARN(("update.status is still applying and update was not successful."));
michael@0 229 if (!WriteStatusFailure(argv[1],
michael@0 230 SERVICE_STILL_APPLYING_ON_FAILURE)) {
michael@0 231 LOG_WARN(("Could not write update.status still applying on"
michael@0 232 " success error."));
michael@0 233 }
michael@0 234 }
michael@0 235 }
michael@0 236 } else {
michael@0 237 DWORD lastError = GetLastError();
michael@0 238 LOG_WARN(("Could not create process as current user, "
michael@0 239 "updaterPath: %ls; cmdLine: %ls. (%d)",
michael@0 240 argv[0], cmdLine, lastError));
michael@0 241 }
michael@0 242
michael@0 243 // Now that we're done with the update, restore back the updater.ini file
michael@0 244 // We use it ourselves, and also we want it back in case we had any type
michael@0 245 // of error so that the normal update process can use it.
michael@0 246 if (selfHandlePostUpdate) {
michael@0 247 MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING);
michael@0 248
michael@0 249 // Only run the PostUpdate if the update was successful
michael@0 250 if (updateWasSuccessful && argc > 2) {
michael@0 251 LPCWSTR updateInfoDir = argv[1];
michael@0 252 bool backgroundUpdate = IsUpdateBeingStaged(argc, argv);
michael@0 253
michael@0 254 // Launch the PostProcess with admin access in session 0. This is
michael@0 255 // actually launching the post update process but it takes in the
michael@0 256 // callback app path to figure out where to apply to.
michael@0 257 // The PostUpdate process with user only access will be done inside
michael@0 258 // the unelevated updater.exe after the update process is complete
michael@0 259 // from the service. We don't know here which session to start
michael@0 260 // the user PostUpdate process from.
michael@0 261 // Note that we don't need to do this if we're just staging the
michael@0 262 // update in the background, as the PostUpdate step runs when
michael@0 263 // performing the replacing in that case.
michael@0 264 if (!backgroundUpdate) {
michael@0 265 LOG(("Launching post update process as the service in session 0."));
michael@0 266 if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) {
michael@0 267 LOG_WARN(("The post update process could not be launched."
michael@0 268 " installDir: %ls, updateInfoDir: %ls",
michael@0 269 installDir, updateInfoDir));
michael@0 270 }
michael@0 271 }
michael@0 272 }
michael@0 273 }
michael@0 274
michael@0 275 free(cmdLine);
michael@0 276 return updateWasSuccessful;
michael@0 277 }
michael@0 278
michael@0 279 /**
michael@0 280 * Processes a software update command
michael@0 281 *
michael@0 282 * @param argc The number of arguments in argv
michael@0 283 * @param argv The arguments normally passed to updater.exe
michael@0 284 * argv[0] must be the path to updater.exe
michael@0 285 * @return TRUE if the update was successful.
michael@0 286 */
michael@0 287 BOOL
michael@0 288 ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
michael@0 289 {
michael@0 290 BOOL result = TRUE;
michael@0 291 if (argc < 3) {
michael@0 292 LOG_WARN(("Not enough command line parameters specified. "
michael@0 293 "Updating update.status."));
michael@0 294
michael@0 295 // We can only update update.status if argv[1] exists. argv[1] is
michael@0 296 // the directory where the update.status file exists.
michael@0 297 if (argc < 2 ||
michael@0 298 !WriteStatusFailure(argv[1],
michael@0 299 SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
michael@0 300 LOG_WARN(("Could not write update.status service update failure. (%d)",
michael@0 301 GetLastError()));
michael@0 302 }
michael@0 303 return FALSE;
michael@0 304 }
michael@0 305
michael@0 306 WCHAR installDir[MAX_PATH + 1] = {L'\0'};
michael@0 307 if (!GetInstallationDir(argc, argv, installDir)) {
michael@0 308 LOG_WARN(("Could not get the installation directory"));
michael@0 309 if (!WriteStatusFailure(argv[1],
michael@0 310 SERVICE_INSTALLDIR_ERROR)) {
michael@0 311 LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
michael@0 312 }
michael@0 313 return FALSE;
michael@0 314 }
michael@0 315
michael@0 316 // Make sure the path to the updater to use for the update is local.
michael@0 317 // We do this check to make sure that file locking is available for
michael@0 318 // race condition security checks.
michael@0 319 BOOL isLocal = FALSE;
michael@0 320 if (!IsLocalFile(argv[0], isLocal) || !isLocal) {
michael@0 321 LOG_WARN(("Filesystem in path %ls is not supported (%d)",
michael@0 322 argv[0], GetLastError()));
michael@0 323 if (!WriteStatusFailure(argv[1],
michael@0 324 SERVICE_UPDATER_NOT_FIXED_DRIVE)) {
michael@0 325 LOG_WARN(("Could not write update.status service update failure. (%d)",
michael@0 326 GetLastError()));
michael@0 327 }
michael@0 328 return FALSE;
michael@0 329 }
michael@0 330
michael@0 331 nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ,
michael@0 332 nullptr, OPEN_EXISTING, 0, nullptr));
michael@0 333 if (INVALID_HANDLE_VALUE == noWriteLock) {
michael@0 334 LOG_WARN(("Could not set no write sharing access on file. (%d)",
michael@0 335 GetLastError()));
michael@0 336 if (!WriteStatusFailure(argv[1],
michael@0 337 SERVICE_COULD_NOT_LOCK_UPDATER)) {
michael@0 338 LOG_WARN(("Could not write update.status service update failure. (%d)",
michael@0 339 GetLastError()));
michael@0 340 }
michael@0 341 return FALSE;
michael@0 342 }
michael@0 343
michael@0 344 // Verify that the updater.exe that we are executing is the same
michael@0 345 // as the one in the installation directory which we are updating.
michael@0 346 // The installation dir that we are installing to is installDir.
michael@0 347 WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' };
michael@0 348 wcsncpy(installDirUpdater, installDir, MAX_PATH);
michael@0 349 if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
michael@0 350 LOG_WARN(("Install directory updater could not be determined."));
michael@0 351 result = FALSE;
michael@0 352 }
michael@0 353
michael@0 354 BOOL updaterIsCorrect;
michael@0 355 if (result && !VerifySameFiles(argv[0], installDirUpdater,
michael@0 356 updaterIsCorrect)) {
michael@0 357 LOG_WARN(("Error checking if the updaters are the same.\n"
michael@0 358 "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater));
michael@0 359 result = FALSE;
michael@0 360 }
michael@0 361
michael@0 362 if (result && !updaterIsCorrect) {
michael@0 363 LOG_WARN(("The updaters do not match, updater will not run."));
michael@0 364 result = FALSE;
michael@0 365 }
michael@0 366
michael@0 367 if (result) {
michael@0 368 LOG(("updater.exe was compared successfully to the installation directory"
michael@0 369 " updater.exe."));
michael@0 370 } else {
michael@0 371 if (!WriteStatusFailure(argv[1],
michael@0 372 SERVICE_UPDATER_COMPARE_ERROR)) {
michael@0 373 LOG_WARN(("Could not write update.status updater compare failure."));
michael@0 374 }
michael@0 375 return FALSE;
michael@0 376 }
michael@0 377
michael@0 378 // Check to make sure the updater.exe module has the unique updater identity.
michael@0 379 // This is a security measure to make sure that the signed executable that
michael@0 380 // we will run is actually an updater.
michael@0 381 HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr,
michael@0 382 LOAD_LIBRARY_AS_DATAFILE);
michael@0 383 if (!updaterModule) {
michael@0 384 LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError()));
michael@0 385 result = FALSE;
michael@0 386 } else {
michael@0 387 char updaterIdentity[64];
michael@0 388 if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY,
michael@0 389 updaterIdentity, sizeof(updaterIdentity))) {
michael@0 390 LOG_WARN(("The updater.exe application does not contain the Mozilla"
michael@0 391 " updater identity."));
michael@0 392 result = FALSE;
michael@0 393 }
michael@0 394
michael@0 395 if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) {
michael@0 396 LOG_WARN(("The updater.exe identity string is not valid."));
michael@0 397 result = FALSE;
michael@0 398 }
michael@0 399 FreeLibrary(updaterModule);
michael@0 400 }
michael@0 401
michael@0 402 if (result) {
michael@0 403 LOG(("The updater.exe application contains the Mozilla"
michael@0 404 " updater identity."));
michael@0 405 } else {
michael@0 406 if (!WriteStatusFailure(argv[1],
michael@0 407 SERVICE_UPDATER_IDENTITY_ERROR)) {
michael@0 408 LOG_WARN(("Could not write update.status no updater identity."));
michael@0 409 }
michael@0 410 return TRUE;
michael@0 411 }
michael@0 412
michael@0 413 // Check for updater.exe sign problems
michael@0 414 BOOL updaterSignProblem = FALSE;
michael@0 415 #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
michael@0 416 updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir,
michael@0 417 argv[0]);
michael@0 418 #endif
michael@0 419
michael@0 420 // Only proceed with the update if we have no signing problems
michael@0 421 if (!updaterSignProblem) {
michael@0 422 BOOL updateProcessWasStarted = FALSE;
michael@0 423 if (StartUpdateProcess(argc, argv, installDir,
michael@0 424 updateProcessWasStarted)) {
michael@0 425 LOG(("updater.exe was launched and run successfully!"));
michael@0 426 LogFlush();
michael@0 427
michael@0 428 // Don't attempt to update the service when the update is being staged.
michael@0 429 if (!IsUpdateBeingStaged(argc, argv)) {
michael@0 430 // We might not execute code after StartServiceUpdate because
michael@0 431 // the service installer will stop the service if it is running.
michael@0 432 StartServiceUpdate(installDir);
michael@0 433 }
michael@0 434 } else {
michael@0 435 result = FALSE;
michael@0 436 LOG_WARN(("Error running update process. Updating update.status (%d)",
michael@0 437 GetLastError()));
michael@0 438 LogFlush();
michael@0 439
michael@0 440 // If the update process was started, then updater.exe is responsible for
michael@0 441 // setting the failure code. If it could not be started then we do the
michael@0 442 // work. We set an error instead of directly setting status pending
michael@0 443 // so that the app.update.service.errors pref can be updated when
michael@0 444 // the callback app restarts.
michael@0 445 if (!updateProcessWasStarted) {
michael@0 446 if (!WriteStatusFailure(argv[1],
michael@0 447 SERVICE_UPDATER_COULD_NOT_BE_STARTED)) {
michael@0 448 LOG_WARN(("Could not write update.status service update failure. (%d)",
michael@0 449 GetLastError()));
michael@0 450 }
michael@0 451 }
michael@0 452 }
michael@0 453 } else {
michael@0 454 result = FALSE;
michael@0 455 LOG_WARN(("Could not start process due to certificate check error on "
michael@0 456 "updater.exe. Updating update.status. (%d)", GetLastError()));
michael@0 457
michael@0 458 // When there is a certificate check error on the updater.exe application,
michael@0 459 // we want to write out the error.
michael@0 460 if (!WriteStatusFailure(argv[1],
michael@0 461 SERVICE_UPDATER_SIGN_ERROR)) {
michael@0 462 LOG_WARN(("Could not write pending state to update.status. (%d)",
michael@0 463 GetLastError()));
michael@0 464 }
michael@0 465 }
michael@0 466
michael@0 467 return result;
michael@0 468 }
michael@0 469
michael@0 470 /**
michael@0 471 * Obtains the updater path alongside a subdir of the service binary.
michael@0 472 * The purpose of this function is to return a path that is likely high
michael@0 473 * integrity and therefore more safe to execute code from.
michael@0 474 *
michael@0 475 * @param serviceUpdaterPath Out parameter for the path where the updater
michael@0 476 * should be copied to.
michael@0 477 * @return TRUE if a file path was obtained.
michael@0 478 */
michael@0 479 BOOL
michael@0 480 GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1])
michael@0 481 {
michael@0 482 if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) {
michael@0 483 LOG_WARN(("Could not obtain module filename when attempting to "
michael@0 484 "use a secure updater path. (%d)", GetLastError()));
michael@0 485 return FALSE;
michael@0 486 }
michael@0 487
michael@0 488 if (!PathRemoveFileSpecW(serviceUpdaterPath)) {
michael@0 489 LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
michael@0 490 "updater path. (%d)", GetLastError()));
michael@0 491 return FALSE;
michael@0 492 }
michael@0 493
michael@0 494 if (!PathAppendSafe(serviceUpdaterPath, L"update")) {
michael@0 495 LOG_WARN(("Couldn't append file spec when attempting to use a secure "
michael@0 496 "updater path. (%d)", GetLastError()));
michael@0 497 return FALSE;
michael@0 498 }
michael@0 499
michael@0 500 CreateDirectoryW(serviceUpdaterPath, nullptr);
michael@0 501
michael@0 502 if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) {
michael@0 503 LOG_WARN(("Couldn't append file spec when attempting to use a secure "
michael@0 504 "updater path. (%d)", GetLastError()));
michael@0 505 return FALSE;
michael@0 506 }
michael@0 507
michael@0 508 return TRUE;
michael@0 509 }
michael@0 510
michael@0 511 /**
michael@0 512 * Deletes the passed in updater path and the associated updater.ini file.
michael@0 513 *
michael@0 514 * @param serviceUpdaterPath The path to delete.
michael@0 515 * @return TRUE if a file was deleted.
michael@0 516 */
michael@0 517 BOOL
michael@0 518 DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1])
michael@0 519 {
michael@0 520 BOOL result = FALSE;
michael@0 521 if (serviceUpdaterPath[0]) {
michael@0 522 result = DeleteFileW(serviceUpdaterPath);
michael@0 523 if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
michael@0 524 GetLastError() != ERROR_FILE_NOT_FOUND) {
michael@0 525 LOG_WARN(("Could not delete service updater path: '%ls'.",
michael@0 526 serviceUpdaterPath));
michael@0 527 }
michael@0 528
michael@0 529 WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' };
michael@0 530 if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath,
michael@0 531 L"updater.ini")) {
michael@0 532 result = DeleteFileW(updaterINIPath);
michael@0 533 if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
michael@0 534 GetLastError() != ERROR_FILE_NOT_FOUND) {
michael@0 535 LOG_WARN(("Could not delete service updater INI path: '%ls'.",
michael@0 536 updaterINIPath));
michael@0 537 }
michael@0 538 }
michael@0 539 }
michael@0 540 return result;
michael@0 541 }
michael@0 542
michael@0 543 /**
michael@0 544 * Executes a service command.
michael@0 545 *
michael@0 546 * @param argc The number of arguments in argv
michael@0 547 * @param argv The service command line arguments, argv[0] and argv[1]
michael@0 548 * and automatically included by Windows. argv[2] is the
michael@0 549 * service command.
michael@0 550 *
michael@0 551 * @return FALSE if there was an error executing the service command.
michael@0 552 */
michael@0 553 BOOL
michael@0 554 ExecuteServiceCommand(int argc, LPWSTR *argv)
michael@0 555 {
michael@0 556 if (argc < 3) {
michael@0 557 LOG_WARN(("Not enough command line arguments to execute a service command"));
michael@0 558 return FALSE;
michael@0 559 }
michael@0 560
michael@0 561 // The tests work by making sure the log has changed, so we put a
michael@0 562 // unique ID in the log.
michael@0 563 RPC_WSTR guidString = RPC_WSTR(L"");
michael@0 564 GUID guid;
michael@0 565 HRESULT hr = CoCreateGuid(&guid);
michael@0 566 if (SUCCEEDED(hr)) {
michael@0 567 UuidToString(&guid, &guidString);
michael@0 568 }
michael@0 569 LOG(("Executing service command %ls, ID: %ls",
michael@0 570 argv[2], reinterpret_cast<LPCWSTR>(guidString)));
michael@0 571 RpcStringFree(&guidString);
michael@0 572
michael@0 573 BOOL result = FALSE;
michael@0 574 if (!lstrcmpi(argv[2], L"software-update")) {
michael@0 575
michael@0 576 // Use the passed in command line arguments for the update, except for the
michael@0 577 // path to updater.exe. We copy updater.exe to a the directory of the
michael@0 578 // MozillaMaintenance service so that a low integrity process cannot
michael@0 579 // replace the updater.exe at any point and use that for the update.
michael@0 580 // It also makes DLL injection attacks harder.
michael@0 581 LPWSTR oldUpdaterPath = argv[3];
michael@0 582 WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
michael@0 583 result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging
michael@0 584 if (result) {
michael@0 585 LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
michael@0 586 oldUpdaterPath, secureUpdaterPath));
michael@0 587 DeleteSecureUpdater(secureUpdaterPath);
michael@0 588 result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE);
michael@0 589 }
michael@0 590
michael@0 591 if (!result) {
michael@0 592 LOG_WARN(("Could not copy path to secure location. (%d)",
michael@0 593 GetLastError()));
michael@0 594 if (argc > 4 && !WriteStatusFailure(argv[4],
michael@0 595 SERVICE_COULD_NOT_COPY_UPDATER)) {
michael@0 596 LOG_WARN(("Could not write update.status could not copy updater error"));
michael@0 597 }
michael@0 598 } else {
michael@0 599
michael@0 600 // We obtained the path and copied it successfully, update the path to
michael@0 601 // use for the service update.
michael@0 602 argv[3] = secureUpdaterPath;
michael@0 603
michael@0 604 WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
michael@0 605 WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
michael@0 606 if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath,
michael@0 607 L"updater.ini") &&
michael@0 608 PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath,
michael@0 609 L"updater.ini")) {
michael@0 610 // This is non fatal if it fails there is no real harm
michael@0 611 if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) {
michael@0 612 LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
michael@0 613 oldUpdaterINIPath, secureUpdaterINIPath, GetLastError()));
michael@0 614 }
michael@0 615 }
michael@0 616
michael@0 617 result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
michael@0 618 DeleteSecureUpdater(secureUpdaterPath);
michael@0 619 }
michael@0 620
michael@0 621 // We might not reach here if the service install succeeded
michael@0 622 // because the service self updates itself and the service
michael@0 623 // installer will stop the service.
michael@0 624 LOG(("Service command %ls complete.", argv[2]));
michael@0 625 } else {
michael@0 626 LOG_WARN(("Service command not recognized: %ls.", argv[2]));
michael@0 627 // result is already set to FALSE
michael@0 628 }
michael@0 629
michael@0 630 LOG(("service command %ls complete with result: %ls.",
michael@0 631 argv[1], (result ? L"Success" : L"Failure")));
michael@0 632 return TRUE;
michael@0 633 }

mercurial