diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/maintenanceservice/serviceinstall.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/maintenanceservice/serviceinstall.cpp')
-rw-r--r-- | toolkit/components/maintenanceservice/serviceinstall.cpp | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp new file mode 100644 index 0000000000..c59a74b94a --- /dev/null +++ b/toolkit/components/maintenanceservice/serviceinstall.cpp @@ -0,0 +1,760 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <aclapi.h> +#include <stdlib.h> +#include <shlwapi.h> + +// Used for DNLEN and UNLEN +#include <lm.h> + +#include <nsWindowsHelpers.h> +#include "mozilla/UniquePtr.h" + +#include "serviceinstall.h" +#include "servicebase.h" +#include "updatehelper.h" +#include "shellapi.h" +#include "readstrings.h" +#include "updatererrors.h" +#include "commonupdatedir.h" + +#pragma comment(lib, "version.lib") + +// This uninstall key is defined originally in maintenanceservice_installer.nsi +#define MAINT_UNINSTALL_KEY \ + L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MozillaMaintenan" \ + L"ceService" + +static BOOL UpdateUninstallerVersionString(LPWSTR versionString) { + HKEY uninstallKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, MAINT_UNINSTALL_KEY, 0, + KEY_WRITE | KEY_WOW64_32KEY, + &uninstallKey) != ERROR_SUCCESS) { + return FALSE; + } + + LONG rv = RegSetValueExW(uninstallKey, L"DisplayVersion", 0, REG_SZ, + reinterpret_cast<const BYTE*>(versionString), + (wcslen(versionString) + 1) * sizeof(WCHAR)); + RegCloseKey(uninstallKey); + return rv == ERROR_SUCCESS; +} + +/** + * A wrapper function to read strings for the maintenance service. + * + * @param path The path of the ini file to read from + * @param results The maintenance service strings that were read + * @return OK on success + */ +static int ReadMaintenanceServiceStrings( + LPCWSTR path, MaintenanceServiceStringTable* results) { + // Read in the maintenance service description string if specified. + const unsigned int kNumStrings = 1; + const char* kServiceKeys = "MozillaMaintenanceDescription\0"; + mozilla::UniquePtr<char[]> serviceString; + int result = ReadStrings(path, kServiceKeys, kNumStrings, &serviceString); + if (result != OK) { + results->serviceDescription = mozilla::MakeUnique<char[]>(1); + results->serviceDescription.get()[0] = '\0'; + } + results->serviceDescription.swap(serviceString); + return result; +} + +/** + * Obtains the version number from the specified PE file's version information + * Version Format: A.B.C.D (Example 10.0.0.300) + * + * @param path The path of the file to check the version on + * @param A The first part of the version number + * @param B The second part of the version number + * @param C The third part of the version number + * @param D The fourth part of the version number + * @return TRUE if successful + */ +static BOOL GetVersionNumberFromPath(LPWSTR path, DWORD& A, DWORD& B, DWORD& C, + DWORD& D) { + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0); + mozilla::UniquePtr<char[]> fileVersionInfo(new char[fileVersionInfoSize]); + if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize, + fileVersionInfo.get())) { + LOG_WARN( + ("Could not obtain file info of old service. (%lu)", GetLastError())); + return FALSE; + } + + VS_FIXEDFILEINFO* fixedFileInfo = + reinterpret_cast<VS_FIXEDFILEINFO*>(fileVersionInfo.get()); + UINT size; + if (!VerQueryValueW(fileVersionInfo.get(), L"\\", + reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) { + LOG_WARN(("Could not query file version info of old service. (%lu)", + GetLastError())); + return FALSE; + } + + A = HIWORD(fixedFileInfo->dwFileVersionMS); + B = LOWORD(fixedFileInfo->dwFileVersionMS); + C = HIWORD(fixedFileInfo->dwFileVersionLS); + D = LOWORD(fixedFileInfo->dwFileVersionLS); + return TRUE; +} + +/** + * Updates the service description with what is stored in updater.ini + * at the same path as the currently executing module binary. + * + * @param serviceHandle A handle to an opened service with + * SERVICE_CHANGE_CONFIG access right + * @param TRUE on succcess. + */ +BOOL UpdateServiceDescription(SC_HANDLE serviceHandle) { + WCHAR updaterINIPath[MAX_PATH + 1]; + if (!GetModuleFileNameW(nullptr, updaterINIPath, + sizeof(updaterINIPath) / sizeof(updaterINIPath[0]))) { + LOG_WARN( + ("Could not obtain module filename when attempting to " + "modify service description. (%lu)", + GetLastError())); + return FALSE; + } + + if (!PathRemoveFileSpecW(updaterINIPath)) { + LOG_WARN( + ("Could not remove file spec when attempting to " + "modify service description. (%lu)", + GetLastError())); + return FALSE; + } + + if (!PathAppendSafe(updaterINIPath, L"updater.ini")) { + LOG_WARN( + ("Could not append updater.ini filename when attempting to " + "modify service description. (%lu)", + GetLastError())); + return FALSE; + } + + if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) { + LOG_WARN( + ("updater.ini file does not exist, will not modify " + "service description. (%lu)", + GetLastError())); + return FALSE; + } + + MaintenanceServiceStringTable serviceStrings; + int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings); + if (rv != OK || !strlen(serviceStrings.serviceDescription.get())) { + LOG_WARN( + ("updater.ini file does not contain a maintenance " + "service description.")); + return FALSE; + } + + int bufferSize = MultiByteToWideChar( + CP_UTF8, 0, serviceStrings.serviceDescription.get(), -1, nullptr, 0); + mozilla::UniquePtr<WCHAR[]> serviceDescription = + mozilla::MakeUnique<WCHAR[]>(bufferSize); + if (!MultiByteToWideChar(CP_UTF8, 0, serviceStrings.serviceDescription.get(), + -1, serviceDescription.get(), bufferSize)) { + LOG_WARN(("Could not convert description to wide string format. (%lu)", + GetLastError())); + return FALSE; + } + + SERVICE_DESCRIPTIONW descriptionConfig; + descriptionConfig.lpDescription = serviceDescription.get(); + if (!ChangeServiceConfig2W(serviceHandle, SERVICE_CONFIG_DESCRIPTION, + &descriptionConfig)) { + LOG_WARN(("Could not change service config. (%lu)", GetLastError())); + return FALSE; + } + + LOG(("The service description was updated successfully.")); + return TRUE; +} + +/** + * Determines if the MozillaMaintenance service path needs to be updated + * and fixes it if it is wrong. + * + * @param service A handle to the service to fix. + * @param currentServicePath The current (possibly wrong) path that is used. + * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed. + * @return TRUE if the service path is now correct. + */ +BOOL FixServicePath(SC_HANDLE service, LPCWSTR currentServicePath, + BOOL& servicePathWasWrong) { + // When we originally upgraded the MozillaMaintenance service we + // would uninstall the service on each upgrade. This had an + // intermittent error which could cause the service to use the file + // maintenanceservice_tmp.exe as the install path. Only a small number + // of Nightly users would be affected by this, but we check for this + // state here and fix the user if they are affected. + // + // We also fix the path in the case of the path not being quoted. + size_t currentServicePathLen = wcslen(currentServicePath); + bool doesServiceHaveCorrectPath = + currentServicePathLen > 2 && + !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") && + currentServicePath[0] == L'\"' && + currentServicePath[currentServicePathLen - 1] == L'\"'; + + if (doesServiceHaveCorrectPath) { + LOG(("The MozillaMaintenance service path is correct.")); + servicePathWasWrong = FALSE; + return TRUE; + } + // This is a recoverable situation so not logging as a warning + LOG(("The MozillaMaintenance path is NOT correct. It was: %ls", + currentServicePath)); + + servicePathWasWrong = TRUE; + WCHAR fixedPath[MAX_PATH + 1] = {L'\0'}; + wcsncpy(fixedPath, currentServicePath, MAX_PATH); + PathUnquoteSpacesW(fixedPath); + if (!PathRemoveFileSpecW(fixedPath)) { + LOG_WARN(("Couldn't remove file spec. (%lu)", GetLastError())); + return FALSE; + } + if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) { + LOG_WARN(("Couldn't append file spec. (%lu)", GetLastError())); + return FALSE; + } + PathQuoteSpacesW(fixedPath); + + if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr)) { + LOG_WARN(("Could not fix service path. (%lu)", GetLastError())); + return FALSE; + } + + LOG(("Fixed service path to: %ls.", fixedPath)); + return TRUE; +} + +/** + * Installs or upgrades the SVC_NAME service. + * If an existing service is already installed, we replace it with the + * currently running process. + * + * @param action The action to perform. + * @return TRUE if the service was installed/upgraded + */ +BOOL SvcInstall(SvcInstallAction action) { + // Get a handle to the local computer SCM database with full access rights. + nsAutoServiceHandle schSCManager( + OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG_WARN(("Could not open service manager. (%lu)", GetLastError())); + return FALSE; + } + + WCHAR newServiceBinaryPath[MAX_PATH + 1]; + if (!GetModuleFileNameW( + nullptr, newServiceBinaryPath, + sizeof(newServiceBinaryPath) / sizeof(newServiceBinaryPath[0]))) { + LOG_WARN( + ("Could not obtain module filename when attempting to " + "install service. (%lu)", + GetLastError())); + return FALSE; + } + + // Check if we already have the service installed. + nsAutoServiceHandle schService( + OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS)); + DWORD lastError = GetLastError(); + if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) { + // The service exists but we couldn't open it + LOG_WARN(("Could not open service. (%lu)", GetLastError())); + return FALSE; + } + + if (schService) { + // The service exists but it may not have the correct permissions. + // This could happen if the permissions were not set correctly originally + // or have been changed after the installation. This will reset the + // permissions back to allow limited user accounts. + if (!SetUserAccessServiceDACL(schService)) { + LOG_WARN( + ("Could not reset security ACE on service handle. It might not be " + "possible to start the service. This error should never " + "happen. (%lu)", + GetLastError())); + } + + // The service exists and we opened it + DWORD bytesNeeded; + if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + LOG_WARN( + ("Could not determine buffer size for query service config. (%lu)", + GetLastError())); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + mozilla::UniquePtr<char[]> serviceConfigBuffer(new char[bytesNeeded]); + if (!QueryServiceConfigW( + schService, + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) { + LOG_WARN(("Could open service but could not query service config. (%lu)", + GetLastError())); + return FALSE; + } + QUERY_SERVICE_CONFIGW& serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + // Check if we need to fix the service path + BOOL servicePathWasWrong; + static BOOL alreadyCheckedFixServicePath = FALSE; + if (!alreadyCheckedFixServicePath) { + if (!FixServicePath(schService, serviceConfig.lpBinaryPathName, + servicePathWasWrong)) { + LOG_WARN( + ("Could not fix service path. This should never happen. (%lu)", + GetLastError())); + // True is returned because the service is pointing to + // maintenanceservice_tmp.exe so it actually was upgraded to the + // newest installed service. + return TRUE; + } else if (servicePathWasWrong) { + // Now that the path is fixed we should re-attempt the install. + // This current process' image path is maintenanceservice_tmp.exe. + // The service used to point to maintenanceservice_tmp.exe. + // The service was just fixed to point to maintenanceservice.exe. + // Re-attempting an install from scratch will work as normal. + alreadyCheckedFixServicePath = TRUE; + LOG(("Restarting install action: %d", action)); + return SvcInstall(action); + } + } + + // Ensure the service path is not quoted. We own this memory and know it to + // be large enough for the quoted path, so it is large enough for the + // unquoted path. This function cannot fail. + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the existing maintenanceservice file's version number and + // the new file's version number. Versions are in the format of + // A.B.C.D. + DWORD existingA, existingB, existingC, existingD; + DWORD newA, newB, newC, newD; + BOOL obtainedExistingVersionInfo = + GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, existingA, + existingB, existingC, existingD); + if (!GetVersionNumberFromPath(newServiceBinaryPath, newA, newB, newC, + newD)) { + LOG_WARN(("Could not obtain version number from new path")); + return FALSE; + } + + // Check if we need to replace the old binary with the new one + // If we couldn't get the old version info then we assume we should + // replace it. + if (ForceInstallSvc == action || !obtainedExistingVersionInfo || + (existingA < newA) || (existingA == newA && existingB < newB) || + (existingA == newA && existingB == newB && existingC < newC) || + (existingA == newA && existingB == newB && existingC == newC && + existingD < newD)) { + // We have a newer updater, so update the description from the INI file. + UpdateServiceDescription(schService); + + schService.reset(); + if (!StopService()) { + return FALSE; + } + + if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) { + LOG( + ("File is already in the correct location, no action needed for " + "upgrade. The path is: \"%ls\"", + newServiceBinaryPath)); + return TRUE; + } + + BOOL result = TRUE; + + // Attempt to copy the new binary over top the existing binary. + // If there is an error we try to move it out of the way and then + // copy it in. First try the safest / easiest way to overwrite the file. + if (!CopyFileW(newServiceBinaryPath, serviceConfig.lpBinaryPathName, + FALSE)) { + LOG_WARN( + ("Could not overwrite old service binary file. " + "This should never happen, but if it does the next " + "upgrade will fix it, the service is not a critical " + "component that needs to be installed for upgrades " + "to work. (%lu)", + GetLastError())); + + // We rename the last 3 filename chars in an unsafe way. Manually + // verify there are more than 3 chars for safe failure in MoveFileExW. + const size_t len = wcslen(serviceConfig.lpBinaryPathName); + if (len > 3) { + // Calculate the temp file path that we're moving the file to. This + // is the same as the proper service path but with a .old extension. + LPWSTR oldServiceBinaryTempPath = new WCHAR[len + 1]; + memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof(WCHAR)); + wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, + len); + // Rename the last 3 chars to 'old' + wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3); + + // Move the current (old) service file to the temp path. + if (MoveFileExW(serviceConfig.lpBinaryPathName, + oldServiceBinaryTempPath, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + // The old binary is moved out of the way, copy in the new one. + if (!CopyFileW(newServiceBinaryPath, serviceConfig.lpBinaryPathName, + FALSE)) { + // It is best to leave the old service binary in this condition. + LOG_WARN( + ("The new service binary could not be copied in." + " The service will not be upgraded.")); + result = FALSE; + } else { + LOG( + ("The new service binary was copied in by first moving the" + " old one out of the way.")); + } + + // Attempt to get rid of the old service temp path. + if (DeleteFileW(oldServiceBinaryTempPath)) { + LOG(("The old temp service path was deleted: %ls.", + oldServiceBinaryTempPath)); + } else { + // The old temp path could not be removed. It will be removed + // the next time the user can't copy the binary in or on + // uninstall. + LOG_WARN(("The old temp service path was not deleted.")); + } + } else { + // It is best to leave the old service binary in this condition. + LOG_WARN( + ("Could not move old service file out of the way from:" + " \"%ls\" to \"%ls\". Service will not be upgraded. (%lu)", + serviceConfig.lpBinaryPathName, oldServiceBinaryTempPath, + GetLastError())); + result = FALSE; + } + delete[] oldServiceBinaryTempPath; + } else { + // It is best to leave the old service binary in this condition. + LOG_WARN( + ("Service binary path was less than 3, service will" + " not be updated. This should never happen.")); + result = FALSE; + } + } else { + WCHAR versionStr[128] = {L'\0'}; + swprintf(versionStr, 128, L"%d.%d.%d.%d", newA, newB, newC, newD); + if (!UpdateUninstallerVersionString(versionStr)) { + LOG(("The uninstaller version string could not be updated.")); + } + LOG(("The new service binary was copied in.")); + } + + // We made a copy of ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + if (MoveFileExW(newServiceBinaryPath, nullptr, + MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("Deleting the old file path on the next reboot: %ls.", + newServiceBinaryPath)); + } else { + LOG_WARN(("Call to delete the old file path failed: %ls.", + newServiceBinaryPath)); + } + + return result; + } + + // We don't need to copy ourselves to the existing location. + // The tmp file (the process of which we are executing right now) will be + // left over. Attempt to delete the file on the next reboot. + MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); + + // nothing to do, we already have a newer service installed + return TRUE; + } + + // If the service does not exist and we are upgrading, don't install it. + if (UpgradeSvc == action) { + // The service does not exist and we are upgrading, so don't install it + return TRUE; + } + + // Quote the path only if it contains spaces. + PathQuoteSpacesW(newServiceBinaryPath); + // The service does not already exist so create the service as on demand + schService.own(CreateServiceW( + schSCManager, SVC_NAME, SVC_DISPLAY_NAME, SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + newServiceBinaryPath, nullptr, nullptr, nullptr, nullptr, nullptr)); + if (!schService) { + LOG_WARN( + ("Could not create Windows service. " + "This error should never happen since a service install " + "should only be called when elevated. (%lu)", + GetLastError())); + return FALSE; + } + + if (!SetUserAccessServiceDACL(schService)) { + LOG_WARN( + ("Could not set security ACE on service handle, the service will not " + "be able to be started from unelevated processes. " + "This error should never happen. (%lu)", + GetLastError())); + } + + UpdateServiceDescription(schService); + + return TRUE; +} + +/** + * Stops the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL StopService() { + // Get a handle to the local computer SCM database with full access rights. + nsAutoServiceHandle schSCManager( + OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG_WARN(("Could not open service manager. (%lu)", GetLastError())); + return FALSE; + } + + // Open the service + nsAutoServiceHandle schService( + OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS)); + if (!schService) { + LOG_WARN(("Could not open service. (%lu)", GetLastError())); + return FALSE; + } + + LOG(("Sending stop request...")); + SERVICE_STATUS status; + SetLastError(ERROR_SUCCESS); + if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) && + GetLastError() != ERROR_SERVICE_NOT_ACTIVE) { + LOG_WARN(("Error sending stop request. (%lu)", GetLastError())); + } + + schSCManager.reset(); + schService.reset(); + + LOG(("Waiting for service stop...")); + DWORD lastState = WaitForServiceStop(SVC_NAME, 30); + + // The service can be in a stopped state but the exe still in use + // so make sure the process is really gone before proceeding + WaitForProcessExit(L"maintenanceservice.exe", 30); + LOG(("Done waiting for service stop, last service state: %lu", lastState)); + + return lastState == SERVICE_STOPPED; +} + +/** + * Uninstalls the Maintenance service. + * + * @return TRUE if successful. + */ +BOOL SvcUninstall() { + // Get a handle to the local computer SCM database with full access rights. + nsAutoServiceHandle schSCManager( + OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS)); + if (!schSCManager) { + LOG_WARN(("Could not open service manager. (%lu)", GetLastError())); + return FALSE; + } + + // Open the service + nsAutoServiceHandle schService( + OpenServiceW(schSCManager, SVC_NAME, SERVICE_ALL_ACCESS)); + if (!schService) { + LOG_WARN(("Could not open service. (%lu)", GetLastError())); + return FALSE; + } + + // Stop the service so it deletes faster and so the uninstaller + // can actually delete its EXE. + DWORD totalWaitTime = 0; + SERVICE_STATUS status; + static const int maxWaitTime = 1000 * 60; // Never wait more than a minute + if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) { + do { + Sleep(status.dwWaitHint); + totalWaitTime += (status.dwWaitHint + 10); + if (status.dwCurrentState == SERVICE_STOPPED) { + break; + } else if (totalWaitTime > maxWaitTime) { + break; + } + } while (QueryServiceStatus(schService, &status)); + } + + // Delete the service or mark it for deletion + BOOL deleted = DeleteService(schService); + if (!deleted) { + deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE); + } + + return deleted; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @return TRUE if successful + */ +BOOL SetUserAccessServiceDACL(SC_HANDLE hService) { + PACL pNewAcl = nullptr; + PSECURITY_DESCRIPTOR psd = nullptr; + DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd); + if (pNewAcl) { + LocalFree((HLOCAL)pNewAcl); + } + if (psd) { + LocalFree((LPVOID)psd); + } + return ERROR_SUCCESS == lastError; +} + +/** + * Sets the access control list for user access for the specified service. + * + * @param hService The service to set the access control list on + * @param pNewAcl The out param ACL which should be freed by caller + * @param psd out param security descriptor, should be freed by caller + * @return ERROR_SUCCESS if successful + */ +DWORD +SetUserAccessServiceDACL(SC_HANDLE hService, PACL& pNewAcl, + PSECURITY_DESCRIPTOR psd) { + // Get the current security descriptor needed size + DWORD needed = 0; + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &psd, 0, + &needed)) { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + LOG_WARN(("Could not query service object security size. (%lu)", + GetLastError())); + return GetLastError(); + } + + DWORD size = needed; + psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size); + if (!psd) { + LOG_WARN( + ("Could not allocate security descriptor. (%lu)", GetLastError())); + return ERROR_INSUFFICIENT_BUFFER; + } + + // Get the actual security descriptor now + if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, psd, + size, &needed)) { + LOG_WARN( + ("Could not allocate security descriptor. (%lu)", GetLastError())); + return GetLastError(); + } + } + + // Get the current DACL from the security descriptor. + PACL pacl = nullptr; + BOOL bDaclPresent = FALSE; + BOOL bDaclDefaulted = FALSE; + if (!GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, &bDaclDefaulted)) { + LOG_WARN(("Could not obtain DACL. (%lu)", GetLastError())); + return GetLastError(); + } + + PSID sidBuiltinUsers; + DWORD SIDSize = SECURITY_MAX_SID_SIZE; + sidBuiltinUsers = LocalAlloc(LMEM_FIXED, SIDSize); + if (!sidBuiltinUsers) { + LOG_WARN(("Could not allocate SID memory. (%lu)", GetLastError())); + return GetLastError(); + } + UniqueSidPtr uniqueSidBuiltinUsers(sidBuiltinUsers); + + if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sidBuiltinUsers, + &SIDSize)) { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create BI\\Users SID. (%lu)", lastError)); + return lastError; + } + + PSID sidInteractive; + SIDSize = SECURITY_MAX_SID_SIZE; + sidInteractive = LocalAlloc(LMEM_FIXED, SIDSize); + if (!sidInteractive) { + LOG_WARN(("Could not allocate SID memory. (%lu)", GetLastError())); + return GetLastError(); + } + UniqueSidPtr uniqueSidInteractive(sidInteractive); + + if (!CreateWellKnownSid(WinInteractiveSid, nullptr, sidInteractive, + &SIDSize)) { + DWORD lastError = GetLastError(); + LOG_WARN(("Could not create Interactive SID. (%lu)", lastError)); + return lastError; + } + + const size_t eaCount = 2; + EXPLICIT_ACCESS ea[eaCount]; + ZeroMemory(ea, sizeof(ea)); + ea[0].grfAccessMode = REVOKE_ACCESS; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + ea[0].Trustee.ptstrName = static_cast<LPWSTR>(sidBuiltinUsers); + ea[1].grfAccessPermissions = SERVICE_START | SERVICE_STOP | GENERIC_READ; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + ea[1].Trustee.ptstrName = static_cast<LPWSTR>(sidInteractive); + + DWORD lastError = SetEntriesInAclW(eaCount, ea, pacl, &pNewAcl); + if (ERROR_SUCCESS != lastError) { + LOG_WARN(("Could not set entries in ACL. (%lu)", lastError)); + return lastError; + } + + // Initialize a new security descriptor. + SECURITY_DESCRIPTOR sd; + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + LOG_WARN( + ("Could not initialize security descriptor. (%lu)", GetLastError())); + return GetLastError(); + } + + // Set the new DACL in the security descriptor. + if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) { + LOG_WARN( + ("Could not set security descriptor DACL. (%lu)", GetLastError())); + return GetLastError(); + } + + // Set the new security descriptor for the service object. + if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) { + LOG_WARN(("Could not set object security. (%lu)", GetLastError())); + return GetLastError(); + } + + // Woohoo, raise the roof + LOG(("User access was set successfully on the service.")); + return ERROR_SUCCESS; +} |