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