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), &regDWORDValueSize);
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