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 #include <aclapi.h>
7 #include <stdlib.h>
8 #include <shlwapi.h>
9 
10 // Used for DNLEN and UNLEN
11 #include <lm.h>
12 
13 #include <nsWindowsHelpers.h>
14 #include "mozilla/UniquePtr.h"
15 
16 #include "serviceinstall.h"
17 #include "servicebase.h"
18 #include "updatehelper.h"
19 #include "shellapi.h"
20 #include "readstrings.h"
21 #include "updatererrors.h"
22 #include "commonupdatedir.h"
23 
24 #pragma comment(lib, "version.lib")
25 
26 // This uninstall key is defined originally in maintenanceservice_installer.nsi
27 #define MAINT_UNINSTALL_KEY                                                    \
28   L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MozillaMaintenan" \
29   L"ceService"
30 
UpdateUninstallerVersionString(LPWSTR versionString)31 static BOOL UpdateUninstallerVersionString(LPWSTR versionString) {
32   HKEY uninstallKey;
33   if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, MAINT_UNINSTALL_KEY, 0,
34                     KEY_WRITE | KEY_WOW64_32KEY,
35                     &uninstallKey) != ERROR_SUCCESS) {
36     return FALSE;
37   }
38 
39   LONG rv = RegSetValueExW(uninstallKey, L"DisplayVersion", 0, REG_SZ,
40                            reinterpret_cast<const BYTE*>(versionString),
41                            (wcslen(versionString) + 1) * sizeof(WCHAR));
42   RegCloseKey(uninstallKey);
43   return rv == ERROR_SUCCESS;
44 }
45 
46 /**
47  * A wrapper function to read strings for the maintenance service.
48  *
49  * @param path    The path of the ini file to read from
50  * @param results The maintenance service strings that were read
51  * @return OK on success
52  */
ReadMaintenanceServiceStrings(LPCWSTR path,MaintenanceServiceStringTable * results)53 static int ReadMaintenanceServiceStrings(
54     LPCWSTR path, MaintenanceServiceStringTable* results) {
55   // Read in the maintenance service description string if specified.
56   const unsigned int kNumStrings = 1;
57   const char* kServiceKeys = "MozillaMaintenanceDescription\0";
58   mozilla::UniquePtr<char[]> serviceString;
59   int result = ReadStrings(path, kServiceKeys, kNumStrings, &serviceString);
60   if (result != OK) {
61     results->serviceDescription = mozilla::MakeUnique<char[]>(1);
62     results->serviceDescription.get()[0] = '\0';
63   }
64   results->serviceDescription.swap(serviceString);
65   return result;
66 }
67 
68 /**
69  * Obtains the version number from the specified PE file's version information
70  * Version Format: A.B.C.D (Example 10.0.0.300)
71  *
72  * @param  path The path of the file to check the version on
73  * @param  A    The first part of the version number
74  * @param  B    The second part of the version number
75  * @param  C    The third part of the version number
76  * @param  D    The fourth part of the version number
77  * @return TRUE if successful
78  */
GetVersionNumberFromPath(LPWSTR path,DWORD & A,DWORD & B,DWORD & C,DWORD & D)79 static BOOL GetVersionNumberFromPath(LPWSTR path, DWORD& A, DWORD& B, DWORD& C,
80                                      DWORD& D) {
81   DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
82   mozilla::UniquePtr<char[]> fileVersionInfo(new char[fileVersionInfoSize]);
83   if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
84                            fileVersionInfo.get())) {
85     LOG_WARN(
86         ("Could not obtain file info of old service.  (%d)", GetLastError()));
87     return FALSE;
88   }
89 
90   VS_FIXEDFILEINFO* fixedFileInfo =
91       reinterpret_cast<VS_FIXEDFILEINFO*>(fileVersionInfo.get());
92   UINT size;
93   if (!VerQueryValueW(fileVersionInfo.get(), L"\\",
94                       reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) {
95     LOG_WARN(("Could not query file version info of old service.  (%d)",
96               GetLastError()));
97     return FALSE;
98   }
99 
100   A = HIWORD(fixedFileInfo->dwFileVersionMS);
101   B = LOWORD(fixedFileInfo->dwFileVersionMS);
102   C = HIWORD(fixedFileInfo->dwFileVersionLS);
103   D = LOWORD(fixedFileInfo->dwFileVersionLS);
104   return TRUE;
105 }
106 
107 /**
108  * Updates the service description with what is stored in updater.ini
109  * at the same path as the currently executing module binary.
110  *
111  * @param serviceHandle A handle to an opened service with
112  *                      SERVICE_CHANGE_CONFIG access right
113  * @param TRUE on succcess.
114  */
UpdateServiceDescription(SC_HANDLE serviceHandle)115 BOOL UpdateServiceDescription(SC_HANDLE serviceHandle) {
116   WCHAR updaterINIPath[MAX_PATH + 1];
117   if (!GetModuleFileNameW(nullptr, updaterINIPath,
118                           sizeof(updaterINIPath) / sizeof(updaterINIPath[0]))) {
119     LOG_WARN(
120         ("Could not obtain module filename when attempting to "
121          "modify service description.  (%d)",
122          GetLastError()));
123     return FALSE;
124   }
125 
126   if (!PathRemoveFileSpecW(updaterINIPath)) {
127     LOG_WARN(
128         ("Could not remove file spec when attempting to "
129          "modify service description.  (%d)",
130          GetLastError()));
131     return FALSE;
132   }
133 
134   if (!PathAppendSafe(updaterINIPath, L"updater.ini")) {
135     LOG_WARN(
136         ("Could not append updater.ini filename when attempting to "
137          "modify service description.  (%d)",
138          GetLastError()));
139     return FALSE;
140   }
141 
142   if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) {
143     LOG_WARN(
144         ("updater.ini file does not exist, will not modify "
145          "service description.  (%d)",
146          GetLastError()));
147     return FALSE;
148   }
149 
150   MaintenanceServiceStringTable serviceStrings;
151   int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
152   if (rv != OK || !strlen(serviceStrings.serviceDescription.get())) {
153     LOG_WARN(
154         ("updater.ini file does not contain a maintenance "
155          "service description."));
156     return FALSE;
157   }
158 
159   int bufferSize = MultiByteToWideChar(
160       CP_UTF8, 0, serviceStrings.serviceDescription.get(), -1, nullptr, 0);
161   mozilla::UniquePtr<WCHAR[]> serviceDescription =
162       mozilla::MakeUnique<WCHAR[]>(bufferSize);
163   if (!MultiByteToWideChar(CP_UTF8, 0, serviceStrings.serviceDescription.get(),
164                            -1, serviceDescription.get(), bufferSize)) {
165     LOG_WARN(("Could not convert description to wide string format.  (%d)",
166               GetLastError()));
167     return FALSE;
168   }
169 
170   SERVICE_DESCRIPTIONW descriptionConfig;
171   descriptionConfig.lpDescription = serviceDescription.get();
172   if (!ChangeServiceConfig2W(serviceHandle, SERVICE_CONFIG_DESCRIPTION,
173                              &descriptionConfig)) {
174     LOG_WARN(("Could not change service config.  (%d)", GetLastError()));
175     return FALSE;
176   }
177 
178   LOG(("The service description was updated successfully."));
179   return TRUE;
180 }
181 
182 /**
183  * Determines if the MozillaMaintenance service path needs to be updated
184  * and fixes it if it is wrong.
185  *
186  * @param service             A handle to the service to fix.
187  * @param currentServicePath  The current (possibly wrong) path that is used.
188  * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed.
189  * @return TRUE if the service path is now correct.
190  */
FixServicePath(SC_HANDLE service,LPCWSTR currentServicePath,BOOL & servicePathWasWrong)191 BOOL FixServicePath(SC_HANDLE service, LPCWSTR currentServicePath,
192                     BOOL& servicePathWasWrong) {
193   // When we originally upgraded the MozillaMaintenance service we
194   // would uninstall the service on each upgrade.  This had an
195   // intermittent error which could cause the service to use the file
196   // maintenanceservice_tmp.exe as the install path.  Only a small number
197   // of Nightly users would be affected by this, but we check for this
198   // state here and fix the user if they are affected.
199   //
200   // We also fix the path in the case of the path not being quoted.
201   size_t currentServicePathLen = wcslen(currentServicePath);
202   bool doesServiceHaveCorrectPath =
203       currentServicePathLen > 2 &&
204       !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") &&
205       currentServicePath[0] == L'\"' &&
206       currentServicePath[currentServicePathLen - 1] == L'\"';
207 
208   if (doesServiceHaveCorrectPath) {
209     LOG(("The MozillaMaintenance service path is correct."));
210     servicePathWasWrong = FALSE;
211     return TRUE;
212   }
213   // This is a recoverable situation so not logging as a warning
214   LOG(("The MozillaMaintenance path is NOT correct. It was: %ls",
215        currentServicePath));
216 
217   servicePathWasWrong = TRUE;
218   WCHAR fixedPath[MAX_PATH + 1] = {L'\0'};
219   wcsncpy(fixedPath, currentServicePath, MAX_PATH);
220   PathUnquoteSpacesW(fixedPath);
221   if (!PathRemoveFileSpecW(fixedPath)) {
222     LOG_WARN(("Couldn't remove file spec.  (%d)", GetLastError()));
223     return FALSE;
224   }
225   if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) {
226     LOG_WARN(("Couldn't append file spec.  (%d)", GetLastError()));
227     return FALSE;
228   }
229   PathQuoteSpacesW(fixedPath);
230 
231   if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
232                             SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr,
233                             nullptr, nullptr, nullptr, nullptr)) {
234     LOG_WARN(("Could not fix service path.  (%d)", GetLastError()));
235     return FALSE;
236   }
237 
238   LOG(("Fixed service path to: %ls.", fixedPath));
239   return TRUE;
240 }
241 
242 /**
243  * Installs or upgrades the SVC_NAME service.
244  * If an existing service is already installed, we replace it with the
245  * currently running process.
246  *
247  * @param  action The action to perform.
248  * @return TRUE if the service was installed/upgraded
249  */
SvcInstall(SvcInstallAction action)250 BOOL SvcInstall(SvcInstallAction action) {
251   // Get a handle to the local computer SCM database with full access rights.
252   nsAutoServiceHandle schSCManager(
253       OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
254   if (!schSCManager) {
255     LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
256     return FALSE;
257   }
258 
259   WCHAR newServiceBinaryPath[MAX_PATH + 1];
260   if (!GetModuleFileNameW(
261           nullptr, newServiceBinaryPath,
262           sizeof(newServiceBinaryPath) / sizeof(newServiceBinaryPath[0]))) {
263     LOG_WARN(
264         ("Could not obtain module filename when attempting to "
265          "install service.  (%d)",
266          GetLastError()));
267     return FALSE;
268   }
269 
270   // Check if we already have the service installed.
271   nsAutoServiceHandle schService(
272       OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS));
273   DWORD lastError = GetLastError();
274   if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) {
275     // The service exists but we couldn't open it
276     LOG_WARN(("Could not open service.  (%d)", GetLastError()));
277     return FALSE;
278   }
279 
280   if (schService) {
281     // The service exists but it may not have the correct permissions.
282     // This could happen if the permissions were not set correctly originally
283     // or have been changed after the installation.  This will reset the
284     // permissions back to allow limited user accounts.
285     if (!SetUserAccessServiceDACL(schService)) {
286       LOG_WARN(
287           ("Could not reset security ACE on service handle. It might not be "
288            "possible to start the service. This error should never "
289            "happen.  (%d)",
290            GetLastError()));
291     }
292 
293     // The service exists and we opened it
294     DWORD bytesNeeded;
295     if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) &&
296         GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
297       LOG_WARN(
298           ("Could not determine buffer size for query service config.  (%d)",
299            GetLastError()));
300       return FALSE;
301     }
302 
303     // Get the service config information, in particular we want the binary
304     // path of the service.
305     mozilla::UniquePtr<char[]> serviceConfigBuffer(new char[bytesNeeded]);
306     if (!QueryServiceConfigW(
307             schService,
308             reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
309             bytesNeeded, &bytesNeeded)) {
310       LOG_WARN(("Could open service but could not query service config.  (%d)",
311                 GetLastError()));
312       return FALSE;
313     }
314     QUERY_SERVICE_CONFIGW& serviceConfig =
315         *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
316 
317     // Check if we need to fix the service path
318     BOOL servicePathWasWrong;
319     static BOOL alreadyCheckedFixServicePath = FALSE;
320     if (!alreadyCheckedFixServicePath) {
321       if (!FixServicePath(schService, serviceConfig.lpBinaryPathName,
322                           servicePathWasWrong)) {
323         LOG_WARN(("Could not fix service path. This should never happen.  (%d)",
324                   GetLastError()));
325         // True is returned because the service is pointing to
326         // maintenanceservice_tmp.exe so it actually was upgraded to the
327         // newest installed service.
328         return TRUE;
329       } else if (servicePathWasWrong) {
330         // Now that the path is fixed we should re-attempt the install.
331         // This current process' image path is maintenanceservice_tmp.exe.
332         // The service used to point to maintenanceservice_tmp.exe.
333         // The service was just fixed to point to maintenanceservice.exe.
334         // Re-attempting an install from scratch will work as normal.
335         alreadyCheckedFixServicePath = TRUE;
336         LOG(("Restarting install action: %d", action));
337         return SvcInstall(action);
338       }
339     }
340 
341     // Ensure the service path is not quoted. We own this memory and know it to
342     // be large enough for the quoted path, so it is large enough for the
343     // unquoted path.  This function cannot fail.
344     PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
345 
346     // Obtain the existing maintenanceservice file's version number and
347     // the new file's version number.  Versions are in the format of
348     // A.B.C.D.
349     DWORD existingA, existingB, existingC, existingD;
350     DWORD newA, newB, newC, newD;
351     BOOL obtainedExistingVersionInfo =
352         GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, existingA,
353                                  existingB, existingC, existingD);
354     if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, newB, newC,
355                                   newD)) {
356       LOG_WARN(("Could not obtain version number from new path"));
357       return FALSE;
358     }
359 
360     // Check if we need to replace the old binary with the new one
361     // If we couldn't get the old version info then we assume we should
362     // replace it.
363     if (ForceInstallSvc == action || !obtainedExistingVersionInfo ||
364         (existingA < newA) || (existingA == newA && existingB < newB) ||
365         (existingA == newA && existingB == newB && existingC < newC) ||
366         (existingA == newA && existingB == newB && existingC == newC &&
367          existingD < newD)) {
368       // We have a newer updater, so update the description from the INI file.
369       UpdateServiceDescription(schService);
370 
371       schService.reset();
372       if (!StopService()) {
373         return FALSE;
374       }
375 
376       if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) {
377         LOG(
378             ("File is already in the correct location, no action needed for "
379              "upgrade.  The path is: \"%ls\"",
380              newServiceBinaryPath));
381         return TRUE;
382       }
383 
384       BOOL result = TRUE;
385 
386       // Attempt to copy the new binary over top the existing binary.
387       // If there is an error we try to move it out of the way and then
388       // copy it in.  First try the safest / easiest way to overwrite the file.
389       if (!CopyFileW(newServiceBinaryPath, serviceConfig.lpBinaryPathName,
390                      FALSE)) {
391         LOG_WARN(
392             ("Could not overwrite old service binary file. "
393              "This should never happen, but if it does the next "
394              "upgrade will fix it, the service is not a critical "
395              "component that needs to be installed for upgrades "
396              "to work.  (%d)",
397              GetLastError()));
398 
399         // We rename the last 3 filename chars in an unsafe way.  Manually
400         // verify there are more than 3 chars for safe failure in MoveFileExW.
401         const size_t len = wcslen(serviceConfig.lpBinaryPathName);
402         if (len > 3) {
403           // Calculate the temp file path that we're moving the file to. This
404           // is the same as the proper service path but with a .old extension.
405           LPWSTR oldServiceBinaryTempPath = new WCHAR[len + 1];
406           memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof(WCHAR));
407           wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName,
408                   len);
409           // Rename the last 3 chars to 'old'
410           wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3);
411 
412           // Move the current (old) service file to the temp path.
413           if (MoveFileExW(serviceConfig.lpBinaryPathName,
414                           oldServiceBinaryTempPath,
415                           MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
416             // The old binary is moved out of the way, copy in the new one.
417             if (!CopyFileW(newServiceBinaryPath, serviceConfig.lpBinaryPathName,
418                            FALSE)) {
419               // It is best to leave the old service binary in this condition.
420               LOG_WARN(
421                   ("The new service binary could not be copied in."
422                    " The service will not be upgraded."));
423               result = FALSE;
424             } else {
425               LOG(
426                   ("The new service binary was copied in by first moving the"
427                    " old one out of the way."));
428             }
429 
430             // Attempt to get rid of the old service temp path.
431             if (DeleteFileW(oldServiceBinaryTempPath)) {
432               LOG(("The old temp service path was deleted: %ls.",
433                    oldServiceBinaryTempPath));
434             } else {
435               // The old temp path could not be removed.  It will be removed
436               // the next time the user can't copy the binary in or on
437               // uninstall.
438               LOG_WARN(("The old temp service path was not deleted."));
439             }
440           } else {
441             // It is best to leave the old service binary in this condition.
442             LOG_WARN(
443                 ("Could not move old service file out of the way from:"
444                  " \"%ls\" to \"%ls\". Service will not be upgraded.  (%d)",
445                  serviceConfig.lpBinaryPathName, oldServiceBinaryTempPath,
446                  GetLastError()));
447             result = FALSE;
448           }
449           delete[] oldServiceBinaryTempPath;
450         } else {
451           // It is best to leave the old service binary in this condition.
452           LOG_WARN(
453               ("Service binary path was less than 3, service will"
454                " not be updated.  This should never happen."));
455           result = FALSE;
456         }
457       } else {
458         WCHAR versionStr[128] = {L'\0'};
459         swprintf(versionStr, 128, L"%d.%d.%d.%d", newA, newB, newC, newD);
460         if (!UpdateUninstallerVersionString(versionStr)) {
461           LOG(("The uninstaller version string could not be updated."));
462         }
463         LOG(("The new service binary was copied in."));
464       }
465 
466       // We made a copy of ourselves to the existing location.
467       // The tmp file (the process of which we are executing right now) will be
468       // left over.  Attempt to delete the file on the next reboot.
469       if (MoveFileExW(newServiceBinaryPath, nullptr,
470                       MOVEFILE_DELAY_UNTIL_REBOOT)) {
471         LOG(("Deleting the old file path on the next reboot: %ls.",
472              newServiceBinaryPath));
473       } else {
474         LOG_WARN(("Call to delete the old file path failed: %ls.",
475                   newServiceBinaryPath));
476       }
477 
478       return result;
479     }
480 
481     // We don't need to copy ourselves to the existing location.
482     // The tmp file (the process of which we are executing right now) will be
483     // left over.  Attempt to delete the file on the next reboot.
484     MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
485 
486     // nothing to do, we already have a newer service installed
487     return TRUE;
488   }
489 
490   // If the service does not exist and we are upgrading, don't install it.
491   if (UpgradeSvc == action) {
492     // The service does not exist and we are upgrading, so don't install it
493     return TRUE;
494   }
495 
496   // Quote the path only if it contains spaces.
497   PathQuoteSpacesW(newServiceBinaryPath);
498   // The service does not already exist so create the service as on demand
499   schService.own(CreateServiceW(
500       schSCManager, SVC_NAME, SVC_DISPLAY_NAME, SERVICE_ALL_ACCESS,
501       SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
502       newServiceBinaryPath, nullptr, nullptr, nullptr, nullptr, nullptr));
503   if (!schService) {
504     LOG_WARN(
505         ("Could not create Windows service. "
506          "This error should never happen since a service install "
507          "should only be called when elevated.  (%d)",
508          GetLastError()));
509     return FALSE;
510   }
511 
512   if (!SetUserAccessServiceDACL(schService)) {
513     LOG_WARN(
514         ("Could not set security ACE on service handle, the service will not "
515          "be able to be started from unelevated processes. "
516          "This error should never happen.  (%d)",
517          GetLastError()));
518   }
519 
520   UpdateServiceDescription(schService);
521 
522   return TRUE;
523 }
524 
525 /**
526  * Stops the Maintenance service.
527  *
528  * @return TRUE if successful.
529  */
StopService()530 BOOL StopService() {
531   // Get a handle to the local computer SCM database with full access rights.
532   nsAutoServiceHandle schSCManager(
533       OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
534   if (!schSCManager) {
535     LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
536     return FALSE;
537   }
538 
539   // Open the service
540   nsAutoServiceHandle schService(
541       OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS));
542   if (!schService) {
543     LOG_WARN(("Could not open service.  (%d)", GetLastError()));
544     return FALSE;
545   }
546 
547   LOG(("Sending stop request..."));
548   SERVICE_STATUS status;
549   SetLastError(ERROR_SUCCESS);
550   if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) &&
551       GetLastError() != ERROR_SERVICE_NOT_ACTIVE) {
552     LOG_WARN(("Error sending stop request.  (%d)", GetLastError()));
553   }
554 
555   schSCManager.reset();
556   schService.reset();
557 
558   LOG(("Waiting for service stop..."));
559   DWORD lastState = WaitForServiceStop(SVC_NAME, 30);
560 
561   // The service can be in a stopped state but the exe still in use
562   // so make sure the process is really gone before proceeding
563   WaitForProcessExit(L"maintenanceservice.exe", 30);
564   LOG(("Done waiting for service stop, last service state: %d", lastState));
565 
566   return lastState == SERVICE_STOPPED;
567 }
568 
569 /**
570  * Uninstalls the Maintenance service.
571  *
572  * @return TRUE if successful.
573  */
SvcUninstall()574 BOOL SvcUninstall() {
575   // Get a handle to the local computer SCM database with full access rights.
576   nsAutoServiceHandle schSCManager(
577       OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS));
578   if (!schSCManager) {
579     LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
580     return FALSE;
581   }
582 
583   // Open the service
584   nsAutoServiceHandle schService(
585       OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS));
586   if (!schService) {
587     LOG_WARN(("Could not open service.  (%d)", GetLastError()));
588     return FALSE;
589   }
590 
591   // Stop the service so it deletes faster and so the uninstaller
592   // can actually delete its EXE.
593   DWORD totalWaitTime = 0;
594   SERVICE_STATUS status;
595   static const int maxWaitTime = 1000 * 60;  // Never wait more than a minute
596   if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
597     do {
598       Sleep(status.dwWaitHint);
599       totalWaitTime += (status.dwWaitHint + 10);
600       if (status.dwCurrentState == SERVICE_STOPPED) {
601         break;
602       } else if (totalWaitTime > maxWaitTime) {
603         break;
604       }
605     } while (QueryServiceStatus(schService, &status));
606   }
607 
608   // Delete the service or mark it for deletion
609   BOOL deleted = DeleteService(schService);
610   if (!deleted) {
611     deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE);
612   }
613 
614   return deleted;
615 }
616 
617 /**
618  * Sets the access control list for user access for the specified service.
619  *
620  * @param  hService The service to set the access control list on
621  * @return TRUE if successful
622  */
SetUserAccessServiceDACL(SC_HANDLE hService)623 BOOL SetUserAccessServiceDACL(SC_HANDLE hService) {
624   PACL pNewAcl = nullptr;
625   PSECURITY_DESCRIPTOR psd = nullptr;
626   DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd);
627   if (pNewAcl) {
628     LocalFree((HLOCAL)pNewAcl);
629   }
630   if (psd) {
631     LocalFree((LPVOID)psd);
632   }
633   return ERROR_SUCCESS == lastError;
634 }
635 
636 /**
637  * Sets the access control list for user access for the specified service.
638  *
639  * @param  hService  The service to set the access control list on
640  * @param  pNewAcl   The out param ACL which should be freed by caller
641  * @param  psd       out param security descriptor, should be freed by caller
642  * @return ERROR_SUCCESS if successful
643  */
644 DWORD
SetUserAccessServiceDACL(SC_HANDLE hService,PACL & pNewAcl,PSECURITY_DESCRIPTOR psd)645 SetUserAccessServiceDACL(SC_HANDLE hService, PACL& pNewAcl,
646                          PSECURITY_DESCRIPTOR psd) {
647   // Get the current security descriptor needed size
648   DWORD needed = 0;
649   if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &psd, 0,
650                                   &needed)) {
651     if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
652       LOG_WARN(("Could not query service object security size.  (%d)",
653                 GetLastError()));
654       return GetLastError();
655     }
656 
657     DWORD size = needed;
658     psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
659     if (!psd) {
660       LOG_WARN(
661           ("Could not allocate security descriptor.  (%d)", GetLastError()));
662       return ERROR_INSUFFICIENT_BUFFER;
663     }
664 
665     // Get the actual security descriptor now
666     if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, psd,
667                                     size, &needed)) {
668       LOG_WARN(
669           ("Could not allocate security descriptor.  (%d)", GetLastError()));
670       return GetLastError();
671     }
672   }
673 
674   // Get the current DACL from the security descriptor.
675   PACL pacl = nullptr;
676   BOOL bDaclPresent = FALSE;
677   BOOL bDaclDefaulted = FALSE;
678   if (!GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, &bDaclDefaulted)) {
679     LOG_WARN(("Could not obtain DACL.  (%d)", GetLastError()));
680     return GetLastError();
681   }
682 
683   PSID sidBuiltinUsers;
684   DWORD SIDSize = SECURITY_MAX_SID_SIZE;
685   sidBuiltinUsers = LocalAlloc(LMEM_FIXED, SIDSize);
686   if (!sidBuiltinUsers) {
687     LOG_WARN(("Could not allocate SID memory.  (%d)", GetLastError()));
688     return GetLastError();
689   }
690   UniqueSidPtr uniqueSidBuiltinUsers(sidBuiltinUsers);
691 
692   if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sidBuiltinUsers,
693                           &SIDSize)) {
694     DWORD lastError = GetLastError();
695     LOG_WARN(("Could not create BI\\Users SID.  (%d)", lastError));
696     return lastError;
697   }
698 
699   PSID sidInteractive;
700   SIDSize = SECURITY_MAX_SID_SIZE;
701   sidInteractive = LocalAlloc(LMEM_FIXED, SIDSize);
702   if (!sidInteractive) {
703     LOG_WARN(("Could not allocate SID memory.  (%d)", GetLastError()));
704     return GetLastError();
705   }
706   UniqueSidPtr uniqueSidInteractive(sidInteractive);
707 
708   if (!CreateWellKnownSid(WinInteractiveSid, nullptr, sidInteractive,
709                           &SIDSize)) {
710     DWORD lastError = GetLastError();
711     LOG_WARN(("Could not create Interactive SID.  (%d)", lastError));
712     return lastError;
713   }
714 
715   const size_t eaCount = 2;
716   EXPLICIT_ACCESS ea[eaCount];
717   ZeroMemory(ea, sizeof(ea));
718   ea[0].grfAccessMode = REVOKE_ACCESS;
719   ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
720   ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
721   ea[0].Trustee.ptstrName = static_cast<LPWSTR>(sidBuiltinUsers);
722   ea[1].grfAccessPermissions = SERVICE_START | SERVICE_STOP | GENERIC_READ;
723   ea[1].grfAccessMode = SET_ACCESS;
724   ea[1].grfInheritance = NO_INHERITANCE;
725   ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
726   ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
727   ea[1].Trustee.ptstrName = static_cast<LPWSTR>(sidInteractive);
728 
729   DWORD lastError = SetEntriesInAclW(eaCount, ea, pacl, &pNewAcl);
730   if (ERROR_SUCCESS != lastError) {
731     LOG_WARN(("Could not set entries in ACL.  (%d)", lastError));
732     return lastError;
733   }
734 
735   // Initialize a new security descriptor.
736   SECURITY_DESCRIPTOR sd;
737   if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
738     LOG_WARN(
739         ("Could not initialize security descriptor.  (%d)", GetLastError()));
740     return GetLastError();
741   }
742 
743   // Set the new DACL in the security descriptor.
744   if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) {
745     LOG_WARN(("Could not set security descriptor DACL.  (%d)", GetLastError()));
746     return GetLastError();
747   }
748 
749   // Set the new security descriptor for the service object.
750   if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) {
751     LOG_WARN(("Could not set object security.  (%d)", GetLastError()));
752     return GetLastError();
753   }
754 
755   // Woohoo, raise the roof
756   LOG(("User access was set successfully on the service."));
757   return ERROR_SUCCESS;
758 }
759