1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #pragma comment(lib, "wtsapi32.lib")
13 #pragma comment(lib, "userenv.lib")
14 #pragma comment(lib, "shlwapi.lib")
15 #pragma comment(lib, "ole32.lib")
16 #pragma comment(lib, "rpcrt4.lib")
18 #include "workmonitor.hxx"
19 #include "serviceinstall.hxx"
20 #include "servicebase.hxx"
21 #include "registrycertificates.hxx"
22 #include "uachelper.h"
23 #include "updatehelper.h"
25 #include "windowsHelper.hxx"
27 // Wait 15 minutes for an update operation to run at most.
28 // Updates usually take less than a minute so this seems like a
29 // significantly large and safe amount of time to wait.
30 static const int TIME_TO_WAIT_ON_UPDATER
= 15 * 60 * 1000;
31 wchar_t* MakeCommandLine(int argc
, wchar_t **argv
);
32 BOOL
WriteStatusFailure(LPCWSTR updateDirPath
, int errorCode
);
33 BOOL
PathGetSiblingFilePath(LPWSTR destinationBuffer
, LPCWSTR siblingFilePath
,
37 * Read the update.status file and sets isApplying to true if
38 * the status is set to applying.
40 * @param updateDirPath The directory where update.status is stored
41 * @param isApplying Out parameter for specifying if the status
42 * is set to applying or not.
43 * @return TRUE if the information was filled.
46 IsStatusApplying(LPCWSTR updateDirPath
, BOOL
&isApplying
)
49 WCHAR updateStatusFilePath
[MAX_PATH
+ 1] = {L
'\0'};
50 wcsncpy(updateStatusFilePath
, updateDirPath
, MAX_PATH
);
51 if (!PathAppendSafe(updateStatusFilePath
, L
"update.status"))
53 LOG_WARN(("Could not append path for update.status file"));
57 AutoHandle
statusFile(CreateFileW(updateStatusFilePath
, GENERIC_READ
,
61 nullptr, OPEN_EXISTING
, 0, nullptr));
63 if (statusFile
== INVALID_HANDLE_VALUE
)
65 LOG_WARN(("Could not open update.status file"));
71 if (!ReadFile(statusFile
.get(), buf
, sizeof(buf
), &read
, nullptr))
73 LOG_WARN(("Could not read from update.status file"));
77 LOG(("updater.exe returned status: %s", buf
));
79 const char kApplying
[] = "applying";
80 isApplying
= strncmp(buf
, kApplying
,
81 sizeof(kApplying
) - 1) == 0;
86 * Determines whether we're staging an update.
88 * @param argc The argc value normally sent to updater.exe
89 * @param argv The argv value normally sent to updater.exe
90 * @return boolean True if we're staging an update
93 IsUpdateBeingStaged(int argc
, LPWSTR
*argv
)
95 // PID will be set to -1 if we're supposed to stage an update.
96 return argc
== 4 && !wcscmp(argv
[3], L
"-1") ||
97 argc
== 5 && !wcscmp(argv
[4], L
"-1");
101 * Determines whether the param only contains digits.
103 * @param str The string to check
104 * @param boolean True if the param only contains digits
111 if (!iswdigit(*str
++))
120 * Determines whether the command line contains just the directory to apply the
121 * update to (old command line) or if it contains the installation directory and
122 * the directory to apply the update to.
124 * @param argc The argc value normally sent to updater.exe
125 * @param argv The argv value normally sent to updater.exe
126 * @param boolean True if the command line contains just the directory to apply
130 IsOldCommandline(int argc
, LPWSTR
*argv
)
132 return argc
== 4 && !wcscmp(argv
[3], L
"-1") ||
133 argc
>= 4 && (wcsstr(argv
[3], L
"/replace") || IsDigits(argv
[3]));
137 * Gets the installation directory from the arguments passed to updater.exe.
139 * @param argcTmp The argc value normally sent to updater.exe
140 * @param argvTmp The argv value normally sent to updater.exe
141 * @param aResultDir Buffer to hold the installation directory.
144 GetInstallationDir(int argcTmp
, LPWSTR
*argvTmp
, WCHAR aResultDir
[MAX_PATH
+ 1])
147 if (IsOldCommandline(argcTmp
, argvTmp
))
157 wcsncpy(aResultDir
, argvTmp
[2], MAX_PATH
);
158 WCHAR
* backSlash
= wcsrchr(aResultDir
, L
'\\');
159 // Make sure that the path does not include trailing backslashes
160 if (backSlash
&& (backSlash
[1] == L
'\0'))
165 // The new command line's argv[2] is always the installation directory.
168 bool backgroundUpdate
= IsUpdateBeingStaged(argcTmp
, argvTmp
);
169 bool replaceRequest
= (argcTmp
>= 4 && wcsstr(argvTmp
[3], L
"/replace"));
170 if (backgroundUpdate
|| replaceRequest
)
172 return PathRemoveFileSpecW(aResultDir
);
179 * Runs an update process as the service using the SYSTEM account.
181 * @param argc The number of arguments in argv
182 * @param argv The arguments normally passed to updater.exe
183 * argv[0] must be the path to updater.exe
184 * @param processStarted Set to TRUE if the process was started.
185 * @return TRUE if the update process was run had a return code of 0.
188 StartUpdateProcess(int argc
,
191 BOOL
&processStarted
)
193 LOG(("Starting update process as the service in session 0."));
194 STARTUPINFO si
= {0};
195 si
.cb
= sizeof(STARTUPINFO
);
196 si
.lpDesktop
= L
"winsta0\\Default";
197 PROCESS_INFORMATION pi
= {0};
199 // The updater command line is of the form:
200 // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
201 LPWSTR cmdLine
= MakeCommandLine(argc
, argv
);
204 if (IsOldCommandline(argc
, argv
))
209 // If we're about to start the update process from session 0,
210 // then we should not show a GUI. This only really needs to be done
211 // on Vista and higher, but it's better to keep everything consistent
212 // across all OS if it's of no harm.
215 // Setting the desktop to blank will ensure no GUI is displayed
217 si
.dwFlags
|= STARTF_USESHOWWINDOW
;
218 si
.wShowWindow
= SW_HIDE
;
221 // We move the updater.ini file out of the way because we will handle
222 // executing PostUpdate through the service. We handle PostUpdate from
223 // the service because there are some per user things that happen that
224 // can't run in session 0 which we run updater.exe in.
225 // Once we are done running updater.exe we rename updater.ini back so
226 // that if there were any errors the next updater.exe will run correctly.
227 WCHAR updaterINI
[MAX_PATH
+ 1];
228 WCHAR updaterINITemp
[MAX_PATH
+ 1];
229 BOOL selfHandlePostUpdate
= FALSE
;
230 // We use the updater.ini from the same directory as the updater.exe
231 // because of background updates.
232 if (PathGetSiblingFilePath(updaterINI
, argv
[0], L
"updater.ini") &&
233 PathGetSiblingFilePath(updaterINITemp
, argv
[0], L
"updater.tmp"))
235 selfHandlePostUpdate
= MoveFileExW(updaterINI
, updaterINITemp
,
236 MOVEFILE_REPLACE_EXISTING
);
239 // Add an env var for USING_SERVICE so the updater.exe can
240 // do anything special that it needs to do for service updates.
241 // Search in updater.cpp for more info on USING_SERVICE.
242 putenv(const_cast<char*>("USING_SERVICE=1"));
243 LOG(("Starting service with cmdline: %ls", cmdLine
));
244 processStarted
= CreateProcessW(argv
[0], cmdLine
,
245 nullptr, nullptr, FALSE
,
246 CREATE_DEFAULT_ERROR_MODE
,
249 // Empty value on putenv is how you remove an env variable in Windows
250 putenv(const_cast<char*>("USING_SERVICE="));
252 BOOL updateWasSuccessful
= FALSE
;
255 // Wait for the updater process to finish
256 LOG(("Process was started... waiting on result."));
257 DWORD waitRes
= WaitForSingleObject(pi
.hProcess
, TIME_TO_WAIT_ON_UPDATER
);
258 if (WAIT_TIMEOUT
== waitRes
)
260 // We waited a long period of time for updater.exe and it never finished
262 TerminateProcess(pi
.hProcess
, 1);
266 // Check the return code of updater.exe to make sure we get 0
268 if (GetExitCodeProcess(pi
.hProcess
, &returnCode
))
270 LOG(("Process finished with return code %d.", returnCode
));
271 // updater returns 0 if successful.
272 updateWasSuccessful
= (returnCode
== 0);
276 LOG_WARN(("Process finished but could not obtain return code."));
279 CloseHandle(pi
.hProcess
);
280 CloseHandle(pi
.hThread
);
282 // Check just in case updater.exe didn't change the status from
283 // applying. If this is the case we report an error.
284 BOOL isApplying
= FALSE
;
285 if (IsStatusApplying(argv
[1], isApplying
) && isApplying
)
287 if (updateWasSuccessful
)
289 LOG(("update.status is still applying even know update "
290 " was successful."));
291 if (!WriteStatusFailure(argv
[1],
292 SERVICE_STILL_APPLYING_ON_SUCCESS
))
294 LOG_WARN(("Could not write update.status still applying on"
297 // Since we still had applying we know updater.exe didn't do its
299 updateWasSuccessful
= FALSE
;
303 LOG_WARN(("update.status is still applying and update was not successful."));
304 if (!WriteStatusFailure(argv
[1],
305 SERVICE_STILL_APPLYING_ON_FAILURE
))
307 LOG_WARN(("Could not write update.status still applying on"
315 DWORD lastError
= GetLastError();
316 LOG_WARN(("Could not create process as current user, "
317 "updaterPath: %ls; cmdLine: %ls. (%d)",
318 argv
[0], cmdLine
, lastError
));
321 // Now that we're done with the update, restore back the updater.ini file
322 // We use it ourselves, and also we want it back in case we had any type
323 // of error so that the normal update process can use it.
324 if (selfHandlePostUpdate
)
326 MoveFileExW(updaterINITemp
, updaterINI
, MOVEFILE_REPLACE_EXISTING
);
328 // Only run the PostUpdate if the update was successful
329 if (updateWasSuccessful
&& argc
> index
)
331 LPCWSTR updateInfoDir
= argv
[1];
332 bool stagingUpdate
= IsUpdateBeingStaged(argc
, argv
);
334 // Launch the PostProcess with admin access in session 0. This is
335 // actually launching the post update process but it takes in the
336 // callback app path to figure out where to apply to.
337 // The PostUpdate process with user only access will be done inside
338 // the unelevated updater.exe after the update process is complete
339 // from the service. We don't know here which session to start
340 // the user PostUpdate process from.
341 // Note that we don't need to do this if we're just staging the
342 // update in the background, as the PostUpdate step runs when
343 // performing the replacing in that case.
346 LOG(("Launching post update process as the service in session 0."));
347 if (!LaunchWinPostProcess(installDir
, updateInfoDir
, true, nullptr))
349 LOG_WARN(("The post update process could not be launched."
350 " installDir: %ls, updateInfoDir: %ls",
351 installDir
, updateInfoDir
));
358 return updateWasSuccessful
;
362 * Processes a software update command
364 * @param argc The number of arguments in argv
365 * @param argv The arguments normally passed to updater.exe
366 * argv[0] must be the path to updater.exe
367 * @return TRUE if the update was successful.
370 ProcessSoftwareUpdateCommand(DWORD argc
, LPWSTR
*argv
)
375 LOG_WARN(("Not enough command line parameters specified. "
376 "Updating update.status."));
378 // We can only update update.status if argv[1] exists. argv[1] is
379 // the directory where the update.status file exists.
381 !WriteStatusFailure(argv
[1],
382 SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS
))
384 LOG_WARN(("Could not write update.status service update failure. (%d)",
390 WCHAR installDir
[MAX_PATH
+ 1] = {L
'\0'};
391 if (!GetInstallationDir(argc
, argv
, installDir
))
393 LOG_WARN(("Could not get the installation directory"));
394 if (!WriteStatusFailure(argv
[1],
395 SERVICE_INSTALLDIR_ERROR
))
397 LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
402 // Make sure the path to the updater to use for the update is local.
403 // We do this check to make sure that file locking is available for
404 // race condition security checks.
405 BOOL isLocal
= FALSE
;
406 if (!IsLocalFile(argv
[0], isLocal
) || !isLocal
)
408 LOG_WARN(("Filesystem in path %ls is not supported (%d)",
409 argv
[0], GetLastError()));
410 if (!WriteStatusFailure(argv
[1],
411 SERVICE_UPDATER_NOT_FIXED_DRIVE
))
413 LOG_WARN(("Could not write update.status service update failure. (%d)",
419 AutoHandle
noWriteLock(CreateFileW(argv
[0], GENERIC_READ
, FILE_SHARE_READ
,
420 nullptr, OPEN_EXISTING
, 0, nullptr));
421 if (noWriteLock
== INVALID_HANDLE_VALUE
)
423 LOG_WARN(("Could not set no write sharing access on file. (%d)",
425 if (!WriteStatusFailure(argv
[1],
426 SERVICE_COULD_NOT_LOCK_UPDATER
))
428 LOG_WARN(("Could not write update.status service update failure. (%d)",
434 // Verify that the updater.exe that we are executing is the same
435 // as the one in the installation directory which we are updating.
436 // The installation dir that we are installing to is installDir.
437 WCHAR installDirUpdater
[MAX_PATH
+ 1] = { L
'\0' };
438 wcsncpy(installDirUpdater
, installDir
, MAX_PATH
);
439 if (!PathAppendSafe(installDirUpdater
, L
"updater.exe"))
441 LOG_WARN(("Install directory updater could not be determined."));
445 BOOL updaterIsCorrect
= FALSE
;
446 if (result
&& !VerifySameFiles(argv
[0], installDirUpdater
,
449 LOG_WARN(("Error checking if the updaters are the same.\n"
450 "Path 1: %ls\nPath 2: %ls", argv
[0], installDirUpdater
));
454 if (result
&& !updaterIsCorrect
)
456 LOG_WARN(("The updaters do not match, updater will not run.\n"
457 "Path 1: %ls\nPath 2: %ls", argv
[0], installDirUpdater
));
463 LOG(("updater.exe was compared successfully to the installation directory"
468 if (!WriteStatusFailure(argv
[1],
469 SERVICE_UPDATER_COMPARE_ERROR
))
471 LOG_WARN(("Could not write update.status updater compare failure."));
476 // Check to make sure the updater.exe module has the unique updater identity.
477 // This is a security measure to make sure that the signed executable that
478 // we will run is actually an updater.
479 HMODULE updaterModule
= LoadLibraryEx(argv
[0], nullptr,
480 LOAD_LIBRARY_AS_DATAFILE
);
483 LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError()));
488 char updaterIdentity
[64];
489 if (!LoadStringA(updaterModule
, IDS_UPDATER_IDENTITY
,
490 updaterIdentity
, sizeof(updaterIdentity
)))
492 LOG_WARN(("The updater.exe application does not contain the Mozilla"
493 " updater identity."));
497 if (strcmp(updaterIdentity
, UPDATER_IDENTITY_STRING
))
499 LOG_WARN(("The updater.exe identity string is not valid."));
502 FreeLibrary(updaterModule
);
507 LOG(("The updater.exe application contains the Mozilla"
508 " updater identity."));
512 if (!WriteStatusFailure(argv
[1],
513 SERVICE_UPDATER_IDENTITY_ERROR
))
515 LOG_WARN(("Could not write update.status no updater identity."));
520 // Check for updater.exe sign problems
521 BOOL updaterSignProblem
= FALSE
;
522 #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
523 updaterSignProblem
= !DoesBinaryMatchAllowedCertificates(installDir
,
527 // Only proceed with the update if we have no signing problems
528 if (!updaterSignProblem
)
530 BOOL updateProcessWasStarted
= FALSE
;
531 if (StartUpdateProcess(argc
, argv
, installDir
,
532 updateProcessWasStarted
))
534 LOG(("updater.exe was launched and run successfully!"));
537 // Don't attempt to update the service when the update is being staged.
538 if (!IsUpdateBeingStaged(argc
, argv
))
540 // We might not execute code after StartServiceUpdate because
541 // the service installer will stop the service if it is running.
542 StartServiceUpdate(installDir
);
548 LOG_WARN(("Error running update process. Updating update.status (%d)",
552 // If the update process was started, then updater.exe is responsible for
553 // setting the failure code. If it could not be started then we do the
554 // work. We set an error instead of directly setting status pending
555 // so that the app.update.service.errors pref can be updated when
556 // the callback app restarts.
557 if (!updateProcessWasStarted
)
559 if (!WriteStatusFailure(argv
[1],
560 SERVICE_UPDATER_COULD_NOT_BE_STARTED
))
562 LOG_WARN(("Could not write update.status service update failure. (%d)",
571 LOG_WARN(("Could not start process due to certificate check error on "
572 "updater.exe. Updating update.status. (%d)", GetLastError()));
574 // When there is a certificate check error on the updater.exe application,
575 // we want to write out the error.
576 if (!WriteStatusFailure(argv
[1],
577 SERVICE_UPDATER_SIGN_ERROR
))
579 LOG_WARN(("Could not write pending state to update.status. (%d)",
588 * Obtains the updater path alongside a subdir of the service binary.
589 * The purpose of this function is to return a path that is likely high
590 * integrity and therefore more safe to execute code from.
592 * @param serviceUpdaterPath Out parameter for the path where the updater
593 * should be copied to.
594 * @return TRUE if a file path was obtained.
597 GetSecureUpdaterPath(WCHAR serviceUpdaterPath
[MAX_PATH
+ 1])
599 if (!GetModuleFileNameW(nullptr, serviceUpdaterPath
, MAX_PATH
))
601 LOG_WARN(("Could not obtain module filename when attempting to "
602 "use a secure updater path. (%d)", GetLastError()));
606 if (!PathRemoveFileSpecW(serviceUpdaterPath
))
608 LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
609 "updater path. (%d)", GetLastError()));
613 if (!PathAppendSafe(serviceUpdaterPath
, L
"update"))
615 LOG_WARN(("Couldn't append file spec when attempting to use a secure "
616 "updater path. (%d)", GetLastError()));
620 CreateDirectoryW(serviceUpdaterPath
, nullptr);
622 if (!PathAppendSafe(serviceUpdaterPath
, L
"updater.exe"))
624 LOG_WARN(("Couldn't append file spec when attempting to use a secure "
625 "updater path. (%d)", GetLastError()));
633 * Deletes the passed in updater path and the associated updater.ini file.
635 * @param serviceUpdaterPath The path to delete.
636 * @return TRUE if a file was deleted.
639 DeleteSecureUpdater(WCHAR serviceUpdaterPath
[MAX_PATH
+ 1])
642 if (serviceUpdaterPath
[0])
644 result
= DeleteFileW(serviceUpdaterPath
);
645 if (!result
&& GetLastError() != ERROR_PATH_NOT_FOUND
&&
646 GetLastError() != ERROR_FILE_NOT_FOUND
)
648 LOG_WARN(("Could not delete service updater path: '%ls'.",
649 serviceUpdaterPath
));
652 WCHAR updaterINIPath
[MAX_PATH
+ 1] = { L
'\0' };
653 if (PathGetSiblingFilePath(updaterINIPath
, serviceUpdaterPath
,
656 result
= DeleteFileW(updaterINIPath
);
657 if (!result
&& GetLastError() != ERROR_PATH_NOT_FOUND
&&
658 GetLastError() != ERROR_FILE_NOT_FOUND
)
660 LOG_WARN(("Could not delete service updater INI path: '%ls'.",
669 * Executes a service command.
671 * @param argc The number of arguments in argv
672 * @param argv The service command line arguments, argv[0] and argv[1]
673 * and automatically included by Windows. argv[2] is the
676 * @return FALSE if there was an error executing the service command.
679 ExecuteServiceCommand(int argc
, LPWSTR
*argv
)
683 LOG_WARN(("Not enough command line arguments to execute a service command"));
687 // The tests work by making sure the log has changed, so we put a
688 // unique ID in the log.
689 RPC_WSTR guidString
= RPC_WSTR(L
"");
691 HRESULT hr
= CoCreateGuid(&guid
);
694 UuidToString(&guid
, &guidString
);
696 LOG(("Executing service command %ls, ID: %ls",
697 argv
[2], reinterpret_cast<LPCWSTR
>(guidString
)));
698 RpcStringFree(&guidString
);
701 if (!lstrcmpi(argv
[2], L
"software-update"))
704 // Use the passed in command line arguments for the update, except for the
705 // path to updater.exe. We copy updater.exe to the directory of the
706 // MozillaMaintenance service so that a low integrity process cannot
707 // replace the updater.exe at any point and use that for the update.
708 // It also makes DLL injection attacks harder.
709 LPWSTR oldUpdaterPath
= argv
[3];
710 WCHAR secureUpdaterPath
[MAX_PATH
+ 1] = { L
'\0' };
711 result
= GetSecureUpdaterPath(secureUpdaterPath
); // Does its own logging
714 LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
715 oldUpdaterPath
, secureUpdaterPath
));
716 DeleteSecureUpdater(secureUpdaterPath
);
717 result
= CopyFileW(oldUpdaterPath
, secureUpdaterPath
, FALSE
);
722 LOG_WARN(("Could not copy path to secure location. (%d)",
724 if (argc
> 4 && !WriteStatusFailure(argv
[4],
725 SERVICE_COULD_NOT_COPY_UPDATER
))
727 LOG_WARN(("Could not write update.status could not copy updater error"));
733 // We obtained the path and copied it successfully, update the path to
734 // use for the service update.
735 argv
[3] = secureUpdaterPath
;
737 WCHAR oldUpdaterINIPath
[MAX_PATH
+ 1] = { L
'\0' };
738 WCHAR secureUpdaterINIPath
[MAX_PATH
+ 1] = { L
'\0' };
739 if (PathGetSiblingFilePath(secureUpdaterINIPath
, secureUpdaterPath
,
741 PathGetSiblingFilePath(oldUpdaterINIPath
, oldUpdaterPath
,
744 // This is non fatal if it fails there is no real harm
745 if (!CopyFileW(oldUpdaterINIPath
, secureUpdaterINIPath
, FALSE
))
747 LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
748 oldUpdaterINIPath
, secureUpdaterINIPath
, GetLastError()));
752 result
= ProcessSoftwareUpdateCommand(argc
- 3, argv
+ 3);
753 DeleteSecureUpdater(secureUpdaterPath
);
756 // We might not reach here if the service install succeeded
757 // because the service self updates itself and the service
758 // installer will stop the service.
759 LOG(("Service command %ls complete.", argv
[2]));
763 LOG_WARN(("Service command not recognized: %ls.", argv
[2]));
764 // result is already set to FALSE
767 LOG(("service command %ls complete with result: %ls.",
768 argv
[1], (result
? L
"Success" : L
"Failure")));