[POWRPROF] Fix how & when the global registry semaphore is initialized (#8387)

CORE-19104

This fixes failure to create or open the `PowerProfileRegistrySemaphore`,
when it has been first created by a Process1 using powrprof from a "User1",
and while this process stays running, the semaphore is attempted to be
re-created (or opened) by another Process2 using powrprof from a different
"User2".

For example, this happens when "User1" is either the LocalSystem account
with the Process1 being Winlogon.exe (via the msgina.dll -> powrprof.dll
dependency), or, "Administrator" with the explorer.exe process when opening
the "Shutdown" dialog (via the shell32.dll -> runtime-loading of msgina.dll
-> powrprof.dll dependency),
AND,
"User2" being a non-administrator user and Process2 being explorer.exe,
again when opening the "Shutdown" dialog. In this situation, msgina.dll
fails to be loaded, because powrprof.dll fails in its `DllMain()` routine
-- see log excerpt below --, and the shell falls back to a minimal shutdown
confirmation dialog.

```
err:(dll\win32\powrprof\powrprof.c:1420) Couldn't create Semaphore: 5
(dll\ntdll\ldr\ldrinit.c:879) LDR: DLL_PROCESS_ATTACH for dll "powrprof.dll" (InitRoutine: 7ADC6DB0) failed
(dll\win32\kernel32\client\loader.c:386) LoadLibraryExW(msgina.dll) failing with status c0000142
err:(dll\win32\powrprof\powrprof.c:1420) Couldn't create Semaphore: 5
(dll\ntdll\ldr\ldrinit.c:879) LDR: DLL_PROCESS_ATTACH for dll "powrprof.dll" (InitRoutine: 7ADC6DB0) failed
(dll\win32\kernel32\client\loader.c:386) LoadLibraryExW(msgina.dll) failing with status c0000142
(dll\ntdll\ldr\ldrutils.c:2340) Image explorer.exe has no exports, but were trying to get procedure (null). BaseAddress asked 0x00400000, got entry BA 0x00400000
err:(win32ss\user\user32\windows\messagebox.c:1048) MessageBox: L"Do you want to shutdown?"
```
This commit is contained in:
Hermès Bélusca-Maïto
2025-09-14 18:21:00 +02:00
parent 918bc859a9
commit ecf718e48d

View File

@@ -44,17 +44,241 @@ static const WCHAR szCurrentPowerPolicies[] =
static const WCHAR szPolicies[] = L"Policies";
static const WCHAR szName[] = L"Name";
static const WCHAR szDescription[] = L"Description";
static const WCHAR szSemaphoreName[] = L"PowerProfileRegistrySemaphore";
static const WCHAR szDiskMax[] = L"DiskSpindownMax";
static const WCHAR szDiskMin[] = L"DiskSpindownMin";
static const WCHAR szLastID[] = L"LastID";
UINT g_LastID = (UINT)-1;
static UINT g_LastID = (UINT)-1;
static HANDLE PPRegSemaphore = NULL;
/**
* @brief
* Creates a security descriptor for the registry semaphore.
*
* @param[out] PowrProfSd
* A pointer to an allocated security descriptor for the semaphore.
*
* @return
* Returns TRUE if the function succeeds, otherwise FALSE is returned.
*
* @remarks
* Authenticated users are only given a subset of specific rights for
* the semaphore access, local system and administrators have full power.
**/
static BOOLEAN
CreatePwrProfSemaphoreSecurity(
_Out_ PSECURITY_DESCRIPTOR *PowrProfSd)
{
BOOLEAN Success = FALSE;
PSID AuthenticatedUsersSid = NULL, SystemSid = NULL, AdminsSid = NULL;
PACL Dacl = NULL;
PSECURITY_DESCRIPTOR RelSd = NULL;
ULONG DaclSize, RelSDSize = 0;
SECURITY_DESCRIPTOR AbsSd;
static SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
if (!AllocateAndInitializeSid(&NtAuthority,
1,
SECURITY_AUTHENTICATED_USER_RID,
0, 0, 0, 0, 0, 0, 0,
&AuthenticatedUsersSid))
{
return FALSE;
}
if (!AllocateAndInitializeSid(&NtAuthority,
1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&SystemSid))
{
goto Quit;
}
if (!AllocateAndInitializeSid(&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdminsSid))
{
goto Quit;
}
if (!InitializeSecurityDescriptor(&AbsSd, SECURITY_DESCRIPTOR_REVISION))
goto Quit;
/* Compute the size needed for the DACL and allocate it */
DaclSize = sizeof(ACL) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(AuthenticatedUsersSid) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(SystemSid) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(AdminsSid);
Dacl = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, DaclSize);
if (!Dacl || !InitializeAcl(Dacl, DaclSize, ACL_REVISION))
goto Quit;
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SYNCHRONIZE | READ_CONTROL | SEMAPHORE_MODIFY_STATE | SEMAPHORE_QUERY_STATE,
AuthenticatedUsersSid))
{
goto Quit;
}
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SEMAPHORE_ALL_ACCESS,
SystemSid))
{
goto Quit;
}
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SEMAPHORE_ALL_ACCESS,
AdminsSid))
{
goto Quit;
}
if (!SetSecurityDescriptorDacl(&AbsSd, TRUE, Dacl, FALSE))
goto Quit;
if (!SetSecurityDescriptorOwner(&AbsSd, AdminsSid, FALSE))
goto Quit;
if (!SetSecurityDescriptorGroup(&AbsSd, SystemSid, FALSE))
goto Quit;
/* Retrieve the size needed for the relative SD */
if (MakeSelfRelativeSD(&AbsSd, NULL, &RelSDSize) ||
(GetLastError() != ERROR_INSUFFICIENT_BUFFER))
{
goto Quit;
}
/* Build the relative SD */
RelSd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, RelSDSize);
if (!RelSd || !MakeSelfRelativeSD(&AbsSd, RelSd, &RelSDSize))
{
if (RelSd)
HeapFree(GetProcessHeap(), 0, RelSd);
goto Quit;
}
*PowrProfSd = RelSd;
Success = TRUE;
Quit:
if (Dacl)
HeapFree(GetProcessHeap(), 0, Dacl);
if (AdminsSid)
FreeSid(AdminsSid);
if (SystemSid)
FreeSid(SystemSid);
if (AuthenticatedUsersSid)
FreeSid(AuthenticatedUsersSid);
return Success;
}
/**
* @brief
* Creates or opens a registry-synchronizing semaphore, with access for
* SYNCHRONIZE | READ_CONTROL | SEMAPHORE_MODIFY_STATE | SEMAPHORE_QUERY_STATE
* by everyone.
*
* @note
* Because this semaphore aims at synchronizing registry accesses for both
* the global HKEY_LOCAL_MACHINE and the per-user HKEY_CURRENT_USER, this
* Windows <= 2003 - compatible mechanism isn't fully reliable. On Win7+,
* synchronization is ensured with the help of the "User-mode Power Service"
* ("Power", umpo.dll), using RPC calls.
* https://redplait.blogspot.com/2010/11/umpodll-rpc-interfaces.html
**/
static BOOLEAN
CreatePwrProfSemaphore(VOID)
{
SECURITY_ATTRIBUTES SecAttrs;
PSECURITY_DESCRIPTOR Sd;
HANDLE hSemaphore;
DWORD dwError;
while (TRUE)
{
/* Return early if we have already obtained the semaphore handle */
if (PPRegSemaphore)
return TRUE;
/*
* Suppose first that the semaphore already exists and try only to open it
* with the necessary access rights. The semaphore may have been created
* by another instance from a different user (e.g. powrprof.dll loaded by
* a LocalSystem service) and attempting to create-or-open it instead would
* fail with ERROR_ACCESS_DENIED.
*/
hSemaphore = OpenSemaphoreW(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE | SEMAPHORE_QUERY_STATE,
FALSE, L"PowerProfileRegistrySemaphore");
if (hSemaphore)
break;
dwError = GetLastError();
if (dwError == ERROR_ACCESS_DENIED)
{
ERR("Couldn't open POWRPROF semaphore: %ld\n", dwError);
return FALSE;
}
/* The semaphore may not exist. Create it so that all logged-in users
* can access it with the necessary but limited access rights. */
if (!CreatePwrProfSemaphoreSecurity(&Sd))
{
ERR("Couldn't create POWRPROF semaphore security descriptor\n");
return FALSE;
}
// TODO: Consider using no SD (or no security attributes i.e. falling back to defaults?) if creating the SD failed.
SecAttrs.nLength = sizeof(SecAttrs);
SecAttrs.lpSecurityDescriptor = Sd;
SecAttrs.bInheritHandle = FALSE;
hSemaphore = CreateSemaphoreW(&SecAttrs, 1, 1, L"PowerProfileRegistrySemaphore");
dwError = GetLastError();
HeapFree(GetProcessHeap(), 0, Sd);
if (hSemaphore)
break;
/* If a process from a different user created the semaphore in the
* meantime, we failed with ERROR_ACCESS_DENIED. Then, retry again
* by only opening the semaphore this time. Otherwise, bail out. */
if (dwError != ERROR_ACCESS_DENIED)
{
ERR("Couldn't create POWRPROF semaphore: %ld\n", dwError);
return FALSE;
}
}
/* Store the handle; if another thread already succeeded, close the handle */
if (InterlockedCompareExchangePointer((PVOID*)&PPRegSemaphore, hSemaphore, NULL) != NULL)
CloseHandle(hSemaphore);
return TRUE;
}
static BOOLEAN
AcquirePwrProfSemaphore(VOID)
{
if (!PPRegSemaphore && !CreatePwrProfSemaphore())
return FALSE;
return (WaitForSingleObject(PPRegSemaphore, INFINITE) == WAIT_OBJECT_0);
}
BOOLEAN WINAPI WritePwrPolicy(PUINT puiID, PPOWER_POLICY pPowerPolicy);
HANDLE PPRegSemaphore = NULL;
NTSTATUS WINAPI
CallNtPowerInformation(POWER_INFORMATION_LEVEL InformationLevel,
PVOID lpInputBuffer,
@@ -393,7 +617,8 @@ GetPwrDiskSpindownRange(PUINT RangeMax, PUINT RangeMin)
return FALSE;
}
WaitForSingleObject(PPRegSemaphore, INFINITE);
if (!AcquirePwrProfSemaphore())
return FALSE;
Ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, szPowerCfgSubKey, 0, KEY_READ, &hKey);
if (Ret != ERROR_SUCCESS)
@@ -585,7 +810,6 @@ DWORD WINAPI PowerWriteACValueIndex(HKEY key, const GUID *scheme, const GUID *su
return ERROR_SUCCESS;
}
#ifdef __REACTOS__
DWORD WINAPI PowerWriteDCValueIndex(
HKEY key,
const GUID *scheme,
@@ -628,7 +852,6 @@ PowerReadACValue(HKEY RootPowerKey, const GUID *Scheme, const GUID *SubGroup, co
FIXME("(%p,%s,%s,%s,%p,%p,%p) stub!\n", RootPowerKey, debugstr_guid(Scheme), debugstr_guid(SubGroup), debugstr_guid(PowerSettings), Type, Buffer, BufferSize);
return ERROR_CALL_NOT_IMPLEMENTED;
}
#endif
BOOLEAN WINAPI
ReadGlobalPwrPolicy(PGLOBAL_POWER_POLICY pGlobalPowerPolicy)
@@ -939,172 +1162,6 @@ CheckPowerActionPolicy(PPOWER_ACTION_POLICY pPAP, SYSTEM_POWER_CAPABILITIES Powe
};
}
/**
* @brief
* Creates a security descriptor for the power
* management registry semaphore.
*
* @param[out] PowrProfSd
* A pointer to an allocated security descriptor
* for the semaphore.
*
* @return
* Returns TRUE if the function succeeds, otherwise
* FALSE is returned.
*
* @remarks
* Authenticated users are only given a subset of specific
* rights for the semaphore access, local system and admins
* have full power.
*/
static BOOLEAN
CreatePowrProfSemaphoreSecurity(_Out_ PSECURITY_DESCRIPTOR *PowrProfSd)
{
BOOLEAN Success = FALSE;
PACL Dacl;
ULONG DaclSize, RelSDSize = 0;
PSID AuthenticatedUsersSid = NULL, SystemSid = NULL, AdminsSid = NULL;
SECURITY_DESCRIPTOR AbsSd;
PSECURITY_DESCRIPTOR RelSd = NULL;
static SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
if (!AllocateAndInitializeSid(&NtAuthority,
1,
SECURITY_AUTHENTICATED_USER_RID,
0, 0, 0, 0, 0, 0, 0,
&AuthenticatedUsersSid))
{
return FALSE;
}
if (!AllocateAndInitializeSid(&NtAuthority,
1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&SystemSid))
{
goto Quit;
}
if (!AllocateAndInitializeSid(&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdminsSid))
{
goto Quit;
}
if (!InitializeSecurityDescriptor(&AbsSd, SECURITY_DESCRIPTOR_REVISION))
{
goto Quit;
}
DaclSize = sizeof(ACL) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(AuthenticatedUsersSid) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(SystemSid) +
sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(AdminsSid);
Dacl = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, DaclSize);
if (!Dacl)
{
goto Quit;
}
if (!InitializeAcl(Dacl, DaclSize, ACL_REVISION))
{
goto Quit;
}
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SYNCHRONIZE | STANDARD_RIGHTS_READ | 0x3,
AuthenticatedUsersSid))
{
goto Quit;
}
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SEMAPHORE_ALL_ACCESS,
SystemSid))
{
goto Quit;
}
if (!AddAccessAllowedAce(Dacl,
ACL_REVISION,
SEMAPHORE_ALL_ACCESS,
AdminsSid))
{
goto Quit;
}
if (!SetSecurityDescriptorDacl(&AbsSd, TRUE, Dacl, FALSE))
{
goto Quit;
}
if (!SetSecurityDescriptorOwner(&AbsSd, AdminsSid, FALSE))
{
goto Quit;
}
if (!SetSecurityDescriptorGroup(&AbsSd, SystemSid, FALSE))
{
goto Quit;
}
if (!MakeSelfRelativeSD(&AbsSd, NULL, &RelSDSize) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
RelSd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, RelSDSize);
if (RelSd == NULL)
{
goto Quit;
}
if (!MakeSelfRelativeSD(&AbsSd, RelSd, &RelSDSize))
{
goto Quit;
}
}
*PowrProfSd = RelSd;
Success = TRUE;
Quit:
if (AuthenticatedUsersSid)
{
FreeSid(AuthenticatedUsersSid);
}
if (SystemSid)
{
FreeSid(SystemSid);
}
if (AdminsSid)
{
FreeSid(AdminsSid);
}
if (Dacl)
{
HeapFree(GetProcessHeap(), 0, Dacl);
}
if (!Success)
{
if (RelSd)
{
HeapFree(GetProcessHeap(), 0, RelSd);
}
}
return Success;
}
static VOID
FixSystemPowerState(PSYSTEM_POWER_STATE Psps, SYSTEM_POWER_CAPABILITIES PowerCaps)
{
@@ -1377,8 +1434,6 @@ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HKEY hKey;
LONG Err;
SECURITY_ATTRIBUTES SecAttrs;
PSECURITY_DESCRIPTOR Sd;
DisableThreadLibraryCalls(hinstDLL);
@@ -1405,26 +1460,10 @@ DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
RegCloseKey(hKey);
}
if (!CreatePowrProfSemaphoreSecurity(&Sd))
{
ERR("Couldn't create POWRPROF semaphore security descriptor!\n");
return FALSE;
}
SecAttrs.nLength = sizeof(SECURITY_ATTRIBUTES);
SecAttrs.lpSecurityDescriptor = Sd;
SecAttrs.bInheritHandle = FALSE;
PPRegSemaphore = CreateSemaphoreW(&SecAttrs, 1, 1, szSemaphoreName);
HeapFree(GetProcessHeap(), 0, Sd);
if (PPRegSemaphore == NULL)
{
ERR("Couldn't create Semaphore: %d\n", GetLastError());
return FALSE;
}
break;
}
case DLL_PROCESS_DETACH:
if (lpvReserved) break;
CloseHandle(PPRegSemaphore);
break;
}