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/. */
4
5 #include <windows.h>
6
7 // Needed for CreateToolhelp32Snapshot
8 #include <tlhelp32.h>
9
10 #include <stdio.h>
11 #include <direct.h>
12 #include "shlobj.h"
13
14 // Needed for PathAppendW
15 #include <shlwapi.h>
16
17 #include "updatehelper.h"
18 #include "updateutils_win.h"
19
20 #ifdef MOZ_MAINTENANCE_SERVICE
21 # include "mozilla/UniquePtr.h"
22 # include "pathhash.h"
23 # include "registrycertificates.h"
24 # include "uachelper.h"
25
26 using mozilla::MakeUnique;
27 using mozilla::UniquePtr;
28 #endif
29
30 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
31 LPCWSTR newFileName);
32
33 /**
34 * Obtains the path of a file in the same directory as the specified file.
35 *
36 * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
37 * @param siblingFilePath The path of another file in the same directory
38 * @param newFileName The filename of another file in the same directory
39 * @return TRUE if successful
40 */
PathGetSiblingFilePath(LPWSTR destinationBuffer,LPCWSTR siblingFilePath,LPCWSTR newFileName)41 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
42 LPCWSTR newFileName) {
43 if (wcslen(siblingFilePath) > MAX_PATH) {
44 return FALSE;
45 }
46
47 wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH + 1);
48 if (!PathRemoveFileSpecW(destinationBuffer)) {
49 return FALSE;
50 }
51
52 return PathAppendSafe(destinationBuffer, newFileName);
53 }
54
55 /**
56 * Obtains the path of the secure directory used to write the status and log
57 * files for updates applied with an elevated updater or an updater that is
58 * launched using the maintenance service.
59 *
60 * Example
61 * Destination buffer value:
62 * C:\Program Files (x86)\Mozilla Maintenance Service\UpdateLogs
63 *
64 * @param outBuf
65 * A buffer of size MAX_PATH + 1 to store the result.
66 * @return TRUE if successful
67 */
GetSecureOutputDirectoryPath(LPWSTR outBuf)68 BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) {
69 PWSTR progFilesX86;
70 if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE,
71 nullptr, &progFilesX86))) {
72 return FALSE;
73 }
74 if (wcslen(progFilesX86) > MAX_PATH) {
75 CoTaskMemFree(progFilesX86);
76 return FALSE;
77 }
78 wcsncpy(outBuf, progFilesX86, MAX_PATH + 1);
79 CoTaskMemFree(progFilesX86);
80
81 if (!PathAppendSafe(outBuf, L"Mozilla Maintenance Service")) {
82 return FALSE;
83 }
84
85 // Create the Maintenance Service directory in case it doesn't exist.
86 if (!CreateDirectoryW(outBuf, nullptr) &&
87 GetLastError() != ERROR_ALREADY_EXISTS) {
88 return FALSE;
89 }
90
91 if (!PathAppendSafe(outBuf, L"UpdateLogs")) {
92 return FALSE;
93 }
94
95 // Create the secure update output directory in case it doesn't exist.
96 if (!CreateDirectoryW(outBuf, nullptr) &&
97 GetLastError() != ERROR_ALREADY_EXISTS) {
98 return FALSE;
99 }
100
101 return TRUE;
102 }
103
104 /**
105 * Obtains the name of the update output file using the update patch directory
106 * path and file extension (must include the '.' separator) passed to this
107 * function.
108 *
109 * Example
110 * Patch directory path parameter:
111 * C:\ProgramData\Mozilla\updates\0123456789ABCDEF\updates\0
112 * File extension parameter:
113 * .status
114 * Destination buffer value:
115 * 0123456789ABCDEF.status
116 *
117 * @param patchDirPath
118 * The path to the update patch directory.
119 * @param fileExt
120 * The file extension for the file including the '.' separator.
121 * @param outBuf
122 * A buffer of size MAX_PATH + 1 to store the result.
123 * @return TRUE if successful
124 */
GetSecureOutputFileName(LPCWSTR patchDirPath,LPCWSTR fileExt,LPWSTR outBuf)125 BOOL GetSecureOutputFileName(LPCWSTR patchDirPath, LPCWSTR fileExt,
126 LPWSTR outBuf) {
127 size_t fullPathLen = wcslen(patchDirPath);
128 if (fullPathLen > MAX_PATH) {
129 return FALSE;
130 }
131
132 size_t relPathLen = wcslen(PATCH_DIR_PATH);
133 if (relPathLen > fullPathLen) {
134 return FALSE;
135 }
136
137 // The patch directory path must end with updates\0 for updates applied with
138 // an elevated updater or an updater that is launched using the maintenance
139 // service.
140 if (_wcsnicmp(patchDirPath + fullPathLen - relPathLen, PATCH_DIR_PATH,
141 relPathLen) != 0) {
142 return FALSE;
143 }
144
145 wcsncpy(outBuf, patchDirPath, MAX_PATH + 1);
146 if (!PathRemoveFileSpecW(outBuf)) {
147 return FALSE;
148 }
149
150 if (!PathRemoveFileSpecW(outBuf)) {
151 return FALSE;
152 }
153
154 PathStripPathW(outBuf);
155
156 size_t outBufLen = wcslen(outBuf);
157 size_t fileExtLen = wcslen(fileExt);
158 if (outBufLen + fileExtLen > MAX_PATH) {
159 return FALSE;
160 }
161
162 wcsncat(outBuf, fileExt, fileExtLen);
163
164 return TRUE;
165 }
166
167 /**
168 * Obtains the full path of the secure update output file using the update patch
169 * directory path and file extension (must include the '.' separator) passed to
170 * this function.
171 *
172 * Example
173 * Patch directory path parameter:
174 * C:\ProgramData\Mozilla\updates\0123456789ABCDEF\updates\0
175 * File extension parameter:
176 * .status
177 * Destination buffer value:
178 * C:\Program Files (x86)\Mozilla Maintenance
179 * Service\UpdateLogs\0123456789ABCDEF.status
180 *
181 * @param patchDirPath
182 * The path to the update patch directory.
183 * @param fileExt
184 * The file extension for the file including the '.' separator.
185 * @param outBuf
186 * A buffer of size MAX_PATH + 1 to store the result.
187 * @return TRUE if successful
188 */
GetSecureOutputFilePath(LPCWSTR patchDirPath,LPCWSTR fileExt,LPWSTR outBuf)189 BOOL GetSecureOutputFilePath(LPCWSTR patchDirPath, LPCWSTR fileExt,
190 LPWSTR outBuf) {
191 if (!GetSecureOutputDirectoryPath(outBuf)) {
192 return FALSE;
193 }
194
195 WCHAR statusFileName[MAX_PATH + 1] = {L'\0'};
196 if (!GetSecureOutputFileName(patchDirPath, fileExt, statusFileName)) {
197 return FALSE;
198 }
199
200 return PathAppendSafe(outBuf, statusFileName);
201 }
202
203 /**
204 * Writes a UUID to the ID file in the secure output directory. This is used by
205 * the unelevated updater to determine whether an existing update status file in
206 * the secure output directory has been updated.
207 *
208 * @param patchDirPath
209 * The path to the update patch directory.
210 * @return TRUE if successful
211 */
WriteSecureIDFile(LPCWSTR patchDirPath)212 BOOL WriteSecureIDFile(LPCWSTR patchDirPath) {
213 WCHAR uuidString[MAX_PATH + 1] = {L'\0'};
214 if (!GetUUIDString(uuidString)) {
215 return FALSE;
216 }
217
218 WCHAR idFilePath[MAX_PATH + 1] = {L'\0'};
219 if (!GetSecureOutputFilePath(patchDirPath, L".id", idFilePath)) {
220 return FALSE;
221 }
222
223 FILE* idFile = _wfopen(idFilePath, L"wb+");
224 if (idFile == nullptr) {
225 return FALSE;
226 }
227
228 if (fprintf(idFile, "%ls\n", uuidString) == -1) {
229 fclose(idFile);
230 return FALSE;
231 }
232
233 fclose(idFile);
234
235 return TRUE;
236 }
237
238 /**
239 * Removes the update status and log files from the secure output directory.
240 *
241 * @param patchDirPath
242 * The path to the update patch directory.
243 */
RemoveSecureOutputFiles(LPCWSTR patchDirPath)244 void RemoveSecureOutputFiles(LPCWSTR patchDirPath) {
245 WCHAR filePath[MAX_PATH + 1] = {L'\0'};
246 if (GetSecureOutputFilePath(patchDirPath, L".id", filePath)) {
247 (void)_wremove(filePath);
248 }
249 if (GetSecureOutputFilePath(patchDirPath, L".status", filePath)) {
250 (void)_wremove(filePath);
251 }
252 if (GetSecureOutputFilePath(patchDirPath, L".log", filePath)) {
253 (void)_wremove(filePath);
254 }
255 }
256
257 #ifdef MOZ_MAINTENANCE_SERVICE
258 /**
259 * Starts the upgrade process for update of the service if it is
260 * already installed.
261 *
262 * @param installDir the installation directory where
263 * maintenanceservice_installer.exe is located.
264 * @return TRUE if successful
265 */
StartServiceUpdate(LPCWSTR installDir)266 BOOL StartServiceUpdate(LPCWSTR installDir) {
267 // Get a handle to the local computer SCM database
268 SC_HANDLE manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
269 if (!manager) {
270 return FALSE;
271 }
272
273 // Open the service
274 SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, SERVICE_ALL_ACCESS);
275 if (!svc) {
276 CloseServiceHandle(manager);
277 return FALSE;
278 }
279
280 // If we reach here, then the service is installed, so
281 // proceed with upgrading it.
282
283 CloseServiceHandle(manager);
284
285 // The service exists and we opened it, get the config bytes needed
286 DWORD bytesNeeded;
287 if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
288 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
289 CloseServiceHandle(svc);
290 return FALSE;
291 }
292
293 // Get the service config information, in particular we want the binary
294 // path of the service.
295 UniquePtr<char[]> serviceConfigBuffer = MakeUnique<char[]>(bytesNeeded);
296 if (!QueryServiceConfigW(
297 svc,
298 reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
299 bytesNeeded, &bytesNeeded)) {
300 CloseServiceHandle(svc);
301 return FALSE;
302 }
303
304 CloseServiceHandle(svc);
305
306 QUERY_SERVICE_CONFIGW& serviceConfig =
307 *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
308
309 PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
310
311 // Obtain the temp path of the maintenance service binary
312 WCHAR tmpService[MAX_PATH + 1] = {L'\0'};
313 if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName,
314 L"maintenanceservice_tmp.exe")) {
315 return FALSE;
316 }
317
318 if (wcslen(installDir) > MAX_PATH) {
319 return FALSE;
320 }
321
322 // Get the new maintenance service path from the install dir
323 WCHAR newMaintServicePath[MAX_PATH + 1] = {L'\0'};
324 wcsncpy(newMaintServicePath, installDir, MAX_PATH);
325 PathAppendSafe(newMaintServicePath, L"maintenanceservice.exe");
326
327 // Copy the temp file in alongside the maintenace service.
328 // This is a requirement for maintenance service upgrades.
329 if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) {
330 return FALSE;
331 }
332
333 // Check that the copied file's certificate matches the expected name and
334 // issuer stored in the registry for this installation and that the
335 // certificate is trusted by the system's certificate store.
336 if (!DoesBinaryMatchAllowedCertificates(installDir, tmpService)) {
337 DeleteFileW(tmpService);
338 return FALSE;
339 }
340
341 // Start the upgrade comparison process
342 STARTUPINFOW si = {0};
343 si.cb = sizeof(STARTUPINFOW);
344 // No particular desktop because no UI
345 si.lpDesktop = const_cast<LPWSTR>(L""); // -Wwritable-strings
346 PROCESS_INFORMATION pi = {0};
347 WCHAR cmdLine[64] = {'\0'};
348 wcsncpy(cmdLine, L"dummyparam.exe upgrade",
349 sizeof(cmdLine) / sizeof(cmdLine[0]) - 1);
350 BOOL svcUpdateProcessStarted =
351 CreateProcessW(tmpService, cmdLine, nullptr, nullptr, FALSE, 0, nullptr,
352 installDir, &si, &pi);
353 if (svcUpdateProcessStarted) {
354 CloseHandle(pi.hProcess);
355 CloseHandle(pi.hThread);
356 }
357 return svcUpdateProcessStarted;
358 }
359
360 /**
361 * Executes a maintenance service command
362 *
363 * @param argc The total number of arguments in argv
364 * @param argv An array of null terminated strings to pass to the service,
365 * @return ERROR_SUCCESS if the service command was started.
366 * Less than 16000, a windows system error code from StartServiceW
367 * More than 20000, 20000 + the last state of the service constant if
368 * the last state is something other than stopped.
369 * 17001 if the SCM could not be opened
370 * 17002 if the service could not be opened
371 */
372 DWORD
StartServiceCommand(int argc,LPCWSTR * argv)373 StartServiceCommand(int argc, LPCWSTR* argv) {
374 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
375 if (lastState != SERVICE_STOPPED) {
376 return 20000 + lastState;
377 }
378
379 // Get a handle to the SCM database.
380 SC_HANDLE serviceManager = OpenSCManager(
381 nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
382 if (!serviceManager) {
383 return 17001;
384 }
385
386 // Get a handle to the service.
387 SC_HANDLE service = OpenServiceW(serviceManager, SVC_NAME, SERVICE_START);
388 if (!service) {
389 CloseServiceHandle(serviceManager);
390 return 17002;
391 }
392
393 // Wait at most 5 seconds trying to start the service in case of errors
394 // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
395 const DWORD maxWaitMS = 5000;
396 DWORD currentWaitMS = 0;
397 DWORD lastError = ERROR_SUCCESS;
398 while (currentWaitMS < maxWaitMS) {
399 BOOL result = StartServiceW(service, argc, argv);
400 if (result) {
401 lastError = ERROR_SUCCESS;
402 break;
403 } else {
404 lastError = GetLastError();
405 }
406 Sleep(100);
407 currentWaitMS += 100;
408 }
409 CloseServiceHandle(service);
410 CloseServiceHandle(serviceManager);
411 return lastError;
412 }
413
414 /**
415 * Launch a service initiated action for a software update with the
416 * specified arguments.
417 *
418 * @param argc The total number of arguments in argv
419 * @param argv An array of null terminated strings to pass to the exePath,
420 * argv[0] must be the path to the updater.exe
421 * @return ERROR_SUCCESS if successful
422 */
423 DWORD
LaunchServiceSoftwareUpdateCommand(int argc,LPCWSTR * argv)424 LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) {
425 // The service command is the same as the updater.exe command line except
426 // it has 4 extra args:
427 // 0) The name of the service, automatically added by Windows
428 // 1) "MozillaMaintenance" (I think this is redundant with 0)
429 // 2) The command being executed, which is "software-update"
430 // 3) The path to updater.exe (from argv[0])
431 LPCWSTR* updaterServiceArgv = new LPCWSTR[argc + 2];
432 updaterServiceArgv[0] = L"MozillaMaintenance";
433 updaterServiceArgv[1] = L"software-update";
434
435 for (int i = 0; i < argc; ++i) {
436 updaterServiceArgv[i + 2] = argv[i];
437 }
438
439 // Execute the service command by starting the service with
440 // the passed in arguments.
441 DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
442 delete[] updaterServiceArgv;
443 return ret;
444 }
445
446 /**
447 * Writes a specific failure code for the update status to a file in the secure
448 * output directory. The status file's name without the '.' separator and
449 * extension is the same as the update directory name.
450 *
451 * @param patchDirPath
452 * The path of the update patch directory.
453 * @param errorCode
454 * Error code to set
455 * @return TRUE if successful
456 */
WriteStatusFailure(LPCWSTR patchDirPath,int errorCode)457 BOOL WriteStatusFailure(LPCWSTR patchDirPath, int errorCode) {
458 WCHAR statusFilePath[MAX_PATH + 1] = {L'\0'};
459 if (!GetSecureOutputFilePath(patchDirPath, L".status", statusFilePath)) {
460 return FALSE;
461 }
462
463 HANDLE hStatusFile = CreateFileW(statusFilePath, GENERIC_WRITE, 0, nullptr,
464 CREATE_ALWAYS, 0, nullptr);
465 if (hStatusFile == INVALID_HANDLE_VALUE) {
466 return FALSE;
467 }
468
469 char failure[32];
470 sprintf(failure, "failed: %d", errorCode);
471 DWORD toWrite = strlen(failure);
472 DWORD wrote;
473 BOOL ok = WriteFile(hStatusFile, failure, toWrite, &wrote, nullptr);
474 CloseHandle(hStatusFile);
475
476 if (!ok || wrote != toWrite) {
477 return FALSE;
478 }
479
480 return TRUE;
481 }
482
483 /**
484 * Waits for a service to enter a stopped state.
485 * This function does not stop the service, it just blocks until the service
486 * is stopped.
487 *
488 * @param serviceName The service to wait for.
489 * @param maxWaitSeconds The maximum number of seconds to wait
490 * @return state of the service after a timeout or when stopped.
491 * A value of 255 is returned for an error. Typical values are:
492 * SERVICE_STOPPED 0x00000001
493 * SERVICE_START_PENDING 0x00000002
494 * SERVICE_STOP_PENDING 0x00000003
495 * SERVICE_RUNNING 0x00000004
496 * SERVICE_CONTINUE_PENDING 0x00000005
497 * SERVICE_PAUSE_PENDING 0x00000006
498 * SERVICE_PAUSED 0x00000007
499 * last status not set 0x000000CF
500 * Could no query status 0x000000DF
501 * Could not open service, access denied 0x000000EB
502 * Could not open service, invalid handle 0x000000EC
503 * Could not open service, invalid name 0x000000ED
504 * Could not open service, does not exist 0x000000EE
505 * Could not open service, other error 0x000000EF
506 * Could not open SCM, access denied 0x000000FD
507 * Could not open SCM, database does not exist 0x000000FE;
508 * Could not open SCM, other error 0x000000FF;
509 * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
510 * in case Windows comes out with other service stats higher than 7, they
511 * would likely call it 8 and above. JS code that uses this in TestAUSHelper
512 * only handles values up to 255 so that's why we don't use GetLastError
513 * directly.
514 */
515 DWORD
WaitForServiceStop(LPCWSTR serviceName,DWORD maxWaitSeconds)516 WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) {
517 // 0x000000CF is defined above to be not set
518 DWORD lastServiceState = 0x000000CF;
519
520 // Get a handle to the SCM database.
521 SC_HANDLE serviceManager = OpenSCManager(
522 nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
523 if (!serviceManager) {
524 DWORD lastError = GetLastError();
525 switch (lastError) {
526 case ERROR_ACCESS_DENIED:
527 return 0x000000FD;
528 case ERROR_DATABASE_DOES_NOT_EXIST:
529 return 0x000000FE;
530 default:
531 return 0x000000FF;
532 }
533 }
534
535 // Get a handle to the service.
536 SC_HANDLE service =
537 OpenServiceW(serviceManager, serviceName, SERVICE_QUERY_STATUS);
538 if (!service) {
539 DWORD lastError = GetLastError();
540 CloseServiceHandle(serviceManager);
541 switch (lastError) {
542 case ERROR_ACCESS_DENIED:
543 return 0x000000EB;
544 case ERROR_INVALID_HANDLE:
545 return 0x000000EC;
546 case ERROR_INVALID_NAME:
547 return 0x000000ED;
548 case ERROR_SERVICE_DOES_NOT_EXIST:
549 return 0x000000EE;
550 default:
551 return 0x000000EF;
552 }
553 }
554
555 DWORD currentWaitMS = 0;
556 SERVICE_STATUS_PROCESS ssp;
557 ssp.dwCurrentState = lastServiceState;
558 while (currentWaitMS < maxWaitSeconds * 1000) {
559 DWORD bytesNeeded;
560 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
561 sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
562 DWORD lastError = GetLastError();
563 switch (lastError) {
564 case ERROR_INVALID_HANDLE:
565 ssp.dwCurrentState = 0x000000D9;
566 break;
567 case ERROR_ACCESS_DENIED:
568 ssp.dwCurrentState = 0x000000DA;
569 break;
570 case ERROR_INSUFFICIENT_BUFFER:
571 ssp.dwCurrentState = 0x000000DB;
572 break;
573 case ERROR_INVALID_PARAMETER:
574 ssp.dwCurrentState = 0x000000DC;
575 break;
576 case ERROR_INVALID_LEVEL:
577 ssp.dwCurrentState = 0x000000DD;
578 break;
579 case ERROR_SHUTDOWN_IN_PROGRESS:
580 ssp.dwCurrentState = 0x000000DE;
581 break;
582 // These 3 errors can occur when the service is not yet stopped but
583 // it is stopping.
584 case ERROR_INVALID_SERVICE_CONTROL:
585 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
586 case ERROR_SERVICE_NOT_ACTIVE:
587 currentWaitMS += 50;
588 Sleep(50);
589 continue;
590 default:
591 ssp.dwCurrentState = 0x000000DF;
592 }
593
594 // We couldn't query the status so just break out
595 break;
596 }
597
598 // The service is already in use.
599 if (ssp.dwCurrentState == SERVICE_STOPPED) {
600 break;
601 }
602 currentWaitMS += 50;
603 Sleep(50);
604 }
605
606 lastServiceState = ssp.dwCurrentState;
607 CloseServiceHandle(service);
608 CloseServiceHandle(serviceManager);
609 return lastServiceState;
610 }
611 #endif
612
613 /**
614 * Determines if there is at least one process running for the specified
615 * application. A match will be found across any session for any user.
616 *
617 * @param process The process to check for existance
618 * @return ERROR_NOT_FOUND if the process was not found
619 * ERROR_SUCCESS if the process was found and there were no errors
620 * Other Win32 system error code for other errors
621 **/
622 DWORD
IsProcessRunning(LPCWSTR filename)623 IsProcessRunning(LPCWSTR filename) {
624 // Take a snapshot of all processes in the system.
625 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
626 if (INVALID_HANDLE_VALUE == snapshot) {
627 return GetLastError();
628 }
629
630 PROCESSENTRY32W processEntry;
631 processEntry.dwSize = sizeof(PROCESSENTRY32W);
632 if (!Process32FirstW(snapshot, &processEntry)) {
633 DWORD lastError = GetLastError();
634 CloseHandle(snapshot);
635 return lastError;
636 }
637
638 do {
639 if (wcsicmp(filename, processEntry.szExeFile) == 0) {
640 CloseHandle(snapshot);
641 return ERROR_SUCCESS;
642 }
643 } while (Process32NextW(snapshot, &processEntry));
644 CloseHandle(snapshot);
645 return ERROR_NOT_FOUND;
646 }
647
648 /**
649 * Waits for the specified application to exit.
650 *
651 * @param filename The application to wait for.
652 * @param maxSeconds The maximum amount of seconds to wait for all
653 * instances of the application to exit.
654 * @return ERROR_SUCCESS if no instances of the application exist
655 * WAIT_TIMEOUT if the process is still running after maxSeconds.
656 * Any other Win32 system error code.
657 */
658 DWORD
WaitForProcessExit(LPCWSTR filename,DWORD maxSeconds)659 WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) {
660 DWORD applicationRunningError = WAIT_TIMEOUT;
661 for (DWORD i = 0; i < maxSeconds; i++) {
662 DWORD applicationRunningError = IsProcessRunning(filename);
663 if (ERROR_NOT_FOUND == applicationRunningError) {
664 return ERROR_SUCCESS;
665 }
666 Sleep(1000);
667 }
668
669 if (ERROR_SUCCESS == applicationRunningError) {
670 return WAIT_TIMEOUT;
671 }
672
673 return applicationRunningError;
674 }
675
676 #ifdef MOZ_MAINTENANCE_SERVICE
677 /**
678 * Determines if the fallback key exists or not
679 *
680 * @return TRUE if the fallback key exists and there was no error checking
681 */
DoesFallbackKeyExist()682 BOOL DoesFallbackKeyExist() {
683 HKEY testOnlyFallbackKey;
684 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEST_ONLY_FALLBACK_KEY_PATH, 0,
685 KEY_READ | KEY_WOW64_64KEY,
686 &testOnlyFallbackKey) != ERROR_SUCCESS) {
687 return FALSE;
688 }
689
690 RegCloseKey(testOnlyFallbackKey);
691 return TRUE;
692 }
693
694 /**
695 * Determines if the file system for the specified file handle is local
696 * @param file path to check the filesystem type for, must be at most MAX_PATH
697 * @param isLocal out parameter which will hold TRUE if the drive is local
698 * @return TRUE if the call succeeded
699 */
IsLocalFile(LPCWSTR file,BOOL & isLocal)700 BOOL IsLocalFile(LPCWSTR file, BOOL& isLocal) {
701 WCHAR rootPath[MAX_PATH + 1] = {L'\0'};
702 if (wcslen(file) > MAX_PATH) {
703 return FALSE;
704 }
705
706 wcsncpy(rootPath, file, MAX_PATH);
707 PathStripToRootW(rootPath);
708 isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
709 return TRUE;
710 }
711
712 /**
713 * Determines the DWORD value of a registry key value
714 *
715 * @param key The base key to where the value name exists
716 * @param valueName The name of the value
717 * @param retValue Out parameter which will hold the value
718 * @return TRUE on success
719 */
GetDWORDValue(HKEY key,LPCWSTR valueName,DWORD & retValue)720 static BOOL GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD& retValue) {
721 DWORD regDWORDValueSize = sizeof(DWORD);
722 LONG retCode =
723 RegQueryValueExW(key, valueName, 0, nullptr,
724 reinterpret_cast<LPBYTE>(&retValue), ®DWORDValueSize);
725 return ERROR_SUCCESS == retCode;
726 }
727
728 /**
729 * Determines if the the system's elevation type allows
730 * unprmopted elevation.
731 *
732 * @param isUnpromptedElevation Out parameter which specifies if unprompted
733 * elevation is allowed.
734 * @return TRUE if the user can actually elevate and the value was obtained
735 * successfully.
736 */
IsUnpromptedElevation(BOOL & isUnpromptedElevation)737 BOOL IsUnpromptedElevation(BOOL& isUnpromptedElevation) {
738 if (!UACHelper::CanUserElevate()) {
739 return FALSE;
740 }
741
742 LPCWSTR UACBaseRegKey =
743 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
744 HKEY baseKey;
745 LONG retCode =
746 RegOpenKeyExW(HKEY_LOCAL_MACHINE, UACBaseRegKey, 0, KEY_READ, &baseKey);
747 if (retCode != ERROR_SUCCESS) {
748 return FALSE;
749 }
750
751 DWORD consent, secureDesktop;
752 BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", consent);
753 success = success &&
754 GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
755
756 RegCloseKey(baseKey);
757 if (success) {
758 isUnpromptedElevation = !consent && !secureDesktop;
759 }
760
761 return success;
762 }
763 #endif
764