reactos/ntoskrnl/config/cmse.c
George Bișoc 697a52aa33
[NTOS:CM] Do not acquire the lock twice when the Object Manager calls CmpSecurityMethod
Whenever a security request is invoked into a key object, such as when requesting
information from its security descriptor, the Object Manager will execute
the CmpSecurityMethod method to do the job.

The problem is that CmpSecurityMethod is not aware if the key control block
of the key body already has a lock acquired which means the function will attempt
to acquire a lock again, leading to a deadlock. This happens if the same
calling thread locks the KCB but it also wants to acquire security information
with ObCheckObjectAccess in CmpDoOpen.

Windows has a hack in CmpSecurityMethod where the passed KCB pointer is ORed
with a bitfield mask to avoid locking in all cases. This is ugly because it negates
every thread to acquire a lock if at least one has it.
2023-10-01 20:06:02 +02:00

356 lines
11 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/config/cmse.c
* PURPOSE: Configuration Manager - Security Subsystem Interface
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include "ntoskrnl.h"
#define NDEBUG
#include "debug.h"
/* GLOBALS *******************************************************************/
/* FUNCTIONS *****************************************************************/
PSECURITY_DESCRIPTOR
NTAPI
CmpHiveRootSecurityDescriptor(VOID)
{
NTSTATUS Status;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PACL Acl, AclCopy;
PSID Sid[4];
SID_IDENTIFIER_AUTHORITY WorldAuthority = {SECURITY_WORLD_SID_AUTHORITY};
SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
ULONG AceLength, AclLength, SidLength;
PACE_HEADER AceHeader;
ULONG i;
PAGED_CODE();
/* Phase 1: Allocate SIDs */
SidLength = RtlLengthRequiredSid(1);
Sid[0] = ExAllocatePoolWithTag(PagedPool, SidLength, TAG_CMSD);
Sid[1] = ExAllocatePoolWithTag(PagedPool, SidLength, TAG_CMSD);
Sid[2] = ExAllocatePoolWithTag(PagedPool, SidLength, TAG_CMSD);
SidLength = RtlLengthRequiredSid(2);
Sid[3] = ExAllocatePoolWithTag(PagedPool, SidLength, TAG_CMSD);
/* Make sure all SIDs were allocated */
if (!(Sid[0]) || !(Sid[1]) || !(Sid[2]) || !(Sid[3]))
{
/* Bugcheck */
KeBugCheckEx(REGISTRY_ERROR, 11, 1, 0, 0);
}
/* Phase 2: Initialize all SIDs */
Status = RtlInitializeSid(Sid[0], &WorldAuthority, 1);
Status |= RtlInitializeSid(Sid[1], &NtAuthority, 1);
Status |= RtlInitializeSid(Sid[2], &NtAuthority, 1);
Status |= RtlInitializeSid(Sid[3], &NtAuthority, 2);
if (!NT_SUCCESS(Status)) KeBugCheckEx(REGISTRY_ERROR, 11, 2, 0, 0);
/* Phase 2: Setup SID Sub Authorities */
*RtlSubAuthoritySid(Sid[0], 0) = SECURITY_WORLD_RID;
*RtlSubAuthoritySid(Sid[1], 0) = SECURITY_RESTRICTED_CODE_RID;
*RtlSubAuthoritySid(Sid[2], 0) = SECURITY_LOCAL_SYSTEM_RID;
*RtlSubAuthoritySid(Sid[3], 0) = SECURITY_BUILTIN_DOMAIN_RID;
*RtlSubAuthoritySid(Sid[3], 1) = DOMAIN_ALIAS_RID_ADMINS;
/* Make sure all SIDs are valid */
ASSERT(RtlValidSid(Sid[0]));
ASSERT(RtlValidSid(Sid[1]));
ASSERT(RtlValidSid(Sid[2]));
ASSERT(RtlValidSid(Sid[3]));
/* Phase 3: Calculate ACL Length */
AclLength = sizeof(ACL);
for (i = 0; i < 4; i++)
{
/* This is what MSDN says to do */
AceLength = FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart);
AceLength += SeLengthSid(Sid[i]);
AclLength += AceLength;
}
/* Phase 3: Allocate the ACL */
Acl = ExAllocatePoolWithTag(PagedPool, AclLength, TAG_CMSD);
if (!Acl) KeBugCheckEx(REGISTRY_ERROR, 11, 3, 0, 0);
/* Phase 4: Create the ACL */
Status = RtlCreateAcl(Acl, AclLength, ACL_REVISION);
if (!NT_SUCCESS(Status)) KeBugCheckEx(REGISTRY_ERROR, 11, 4, Status, 0);
/* Phase 5: Build the ACL */
Status = RtlAddAccessAllowedAce(Acl, ACL_REVISION, KEY_ALL_ACCESS, Sid[2]);
Status |= RtlAddAccessAllowedAce(Acl, ACL_REVISION, KEY_ALL_ACCESS, Sid[3]);
Status |= RtlAddAccessAllowedAce(Acl, ACL_REVISION, KEY_READ, Sid[0]);
Status |= RtlAddAccessAllowedAce(Acl, ACL_REVISION, KEY_READ, Sid[1]);
if (!NT_SUCCESS(Status)) KeBugCheckEx(REGISTRY_ERROR, 11, 5, Status, 0);
/* Phase 5: Make the ACEs inheritable */
Status = RtlGetAce(Acl, 0, (PVOID*)&AceHeader);
ASSERT(NT_SUCCESS(Status));
AceHeader->AceFlags |= CONTAINER_INHERIT_ACE;
Status = RtlGetAce(Acl, 1, (PVOID*)&AceHeader);
ASSERT(NT_SUCCESS(Status));
AceHeader->AceFlags |= CONTAINER_INHERIT_ACE;
Status = RtlGetAce(Acl, 2, (PVOID*)&AceHeader);
ASSERT(NT_SUCCESS(Status));
AceHeader->AceFlags |= CONTAINER_INHERIT_ACE;
Status = RtlGetAce(Acl, 3, (PVOID*)&AceHeader);
ASSERT(NT_SUCCESS(Status));
AceHeader->AceFlags |= CONTAINER_INHERIT_ACE;
/* Phase 6: Allocate the security descriptor and make space for the ACL */
SecurityDescriptor = ExAllocatePoolWithTag(PagedPool,
sizeof(SECURITY_DESCRIPTOR) +
AclLength,
TAG_CMSD);
if (!SecurityDescriptor) KeBugCheckEx(REGISTRY_ERROR, 11, 6, 0, 0);
/* Phase 6: Make a copy of the ACL */
AclCopy = (PACL)((PISECURITY_DESCRIPTOR)SecurityDescriptor + 1);
RtlCopyMemory(AclCopy, Acl, AclLength);
/* Phase 7: Create the security descriptor */
Status = RtlCreateSecurityDescriptor(SecurityDescriptor,
SECURITY_DESCRIPTOR_REVISION);
if (!NT_SUCCESS(Status)) KeBugCheckEx(REGISTRY_ERROR, 11, 7, Status, 0);
/* Phase 8: Set the ACL as a DACL */
Status = RtlSetDaclSecurityDescriptor(SecurityDescriptor,
TRUE,
AclCopy,
FALSE);
if (!NT_SUCCESS(Status)) KeBugCheckEx(REGISTRY_ERROR, 11, 8, Status, 0);
/* Free the SIDs and original ACL */
for (i = 0; i < 4; i++) ExFreePoolWithTag(Sid[i], TAG_CMSD);
ExFreePoolWithTag(Acl, TAG_CMSD);
/* Return the security descriptor */
return SecurityDescriptor;
}
NTSTATUS
CmpQuerySecurityDescriptor(IN PCM_KEY_CONTROL_BLOCK Kcb,
IN SECURITY_INFORMATION SecurityInformation,
OUT PSECURITY_DESCRIPTOR SecurityDescriptor,
IN OUT PULONG BufferLength)
{
PISECURITY_DESCRIPTOR_RELATIVE RelSd;
ULONG SidSize;
ULONG AclSize;
ULONG SdSize;
NTSTATUS Status;
SECURITY_DESCRIPTOR_CONTROL Control = 0;
ULONG Owner = 0;
ULONG Group = 0;
ULONG Dacl = 0;
DBG_UNREFERENCED_PARAMETER(Kcb);
DPRINT("CmpQuerySecurityDescriptor()\n");
if (SecurityInformation == 0)
{
return STATUS_ACCESS_DENIED;
}
SidSize = RtlLengthSid(SeWorldSid);
RelSd = SecurityDescriptor;
SdSize = sizeof(*RelSd);
if (SecurityInformation & OWNER_SECURITY_INFORMATION)
{
Owner = SdSize;
SdSize += SidSize;
}
if (SecurityInformation & GROUP_SECURITY_INFORMATION)
{
Group = SdSize;
SdSize += SidSize;
}
if (SecurityInformation & DACL_SECURITY_INFORMATION)
{
Control |= SE_DACL_PRESENT;
Dacl = SdSize;
AclSize = sizeof(ACL) + sizeof(ACE) + SidSize;
SdSize += AclSize;
}
if (SecurityInformation & SACL_SECURITY_INFORMATION)
{
Control |= SE_SACL_PRESENT;
}
if (*BufferLength < SdSize)
{
*BufferLength = SdSize;
return STATUS_BUFFER_TOO_SMALL;
}
*BufferLength = SdSize;
Status = RtlCreateSecurityDescriptorRelative(RelSd,
SECURITY_DESCRIPTOR_REVISION);
if (!NT_SUCCESS(Status))
return Status;
RelSd->Control |= Control;
RelSd->Owner = Owner;
RelSd->Group = Group;
RelSd->Dacl = Dacl;
if (Owner)
RtlCopyMemory((PUCHAR)RelSd + Owner,
SeWorldSid,
SidSize);
if (Group)
RtlCopyMemory((PUCHAR)RelSd + Group,
SeWorldSid,
SidSize);
if (Dacl)
{
Status = RtlCreateAcl((PACL)((PUCHAR)RelSd + Dacl),
AclSize,
ACL_REVISION);
if (NT_SUCCESS(Status))
{
Status = RtlAddAccessAllowedAce((PACL)((PUCHAR)RelSd + Dacl),
ACL_REVISION,
GENERIC_ALL,
SeWorldSid);
}
}
ASSERT(Status == STATUS_SUCCESS);
return Status;
}
NTSTATUS
CmpSetSecurityDescriptor(IN PCM_KEY_CONTROL_BLOCK Kcb,
IN PSECURITY_INFORMATION SecurityInformation,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN POOL_TYPE PoolType,
IN PGENERIC_MAPPING GenericMapping)
{
DPRINT("CmpSetSecurityDescriptor()\n");
return STATUS_SUCCESS;
}
NTSTATUS
CmpAssignSecurityDescriptor(IN PCM_KEY_CONTROL_BLOCK Kcb,
IN PSECURITY_DESCRIPTOR SecurityDescriptor)
{
DPRINT("CmpAssignSecurityDescriptor(%p %p)\n",
Kcb, SecurityDescriptor);
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
CmpSecurityMethod(IN PVOID ObjectBody,
IN SECURITY_OPERATION_CODE OperationCode,
IN PSECURITY_INFORMATION SecurityInformation,
IN OUT PSECURITY_DESCRIPTOR SecurityDescriptor,
IN OUT PULONG BufferLength,
IN OUT PSECURITY_DESCRIPTOR *OldSecurityDescriptor,
IN POOL_TYPE PoolType,
IN PGENERIC_MAPPING GenericMapping)
{
PCM_KEY_CONTROL_BLOCK Kcb;
NTSTATUS Status = STATUS_SUCCESS;
DBG_UNREFERENCED_PARAMETER(OldSecurityDescriptor);
DBG_UNREFERENCED_PARAMETER(GenericMapping);
Kcb = ((PCM_KEY_BODY)ObjectBody)->KeyControlBlock;
/* Acquire the hive lock */
CmpLockRegistry();
/* Acquire the KCB lock */
if (OperationCode == QuerySecurityDescriptor)
{
/* Avoid recursive locking if somebody already holds it */
if (!((PCM_KEY_BODY)ObjectBody)->KcbLocked)
{
CmpAcquireKcbLockShared(Kcb);
}
}
else
{
ASSERT(!((PCM_KEY_BODY)ObjectBody)->KcbLocked);
CmpAcquireKcbLockExclusive(Kcb);
}
/* Don't touch deleted keys */
if (Kcb->Delete)
{
/* Release the KCB lock */
CmpReleaseKcbLock(Kcb);
/* Release the hive lock */
CmpUnlockRegistry();
return STATUS_KEY_DELETED;
}
switch (OperationCode)
{
case SetSecurityDescriptor:
DPRINT("Set security descriptor\n");
ASSERT((PoolType == PagedPool) || (PoolType == NonPagedPool));
Status = CmpSetSecurityDescriptor(Kcb,
SecurityInformation,
SecurityDescriptor,
PoolType,
GenericMapping);
break;
case QuerySecurityDescriptor:
DPRINT("Query security descriptor\n");
Status = CmpQuerySecurityDescriptor(Kcb,
*SecurityInformation,
SecurityDescriptor,
BufferLength);
break;
case DeleteSecurityDescriptor:
DPRINT("Delete security descriptor\n");
/* HACK */
break;
case AssignSecurityDescriptor:
DPRINT("Assign security descriptor\n");
Status = CmpAssignSecurityDescriptor(Kcb,
SecurityDescriptor);
break;
default:
KeBugCheckEx(SECURITY_SYSTEM, 0, STATUS_INVALID_PARAMETER, 0, 0);
}
/*
* Release the KCB lock, but only if we locked it ourselves and
* nobody else was locking it by themselves.
*/
if (!((PCM_KEY_BODY)ObjectBody)->KcbLocked)
{
CmpReleaseKcbLock(Kcb);
}
/* Release the hive lock */
CmpUnlockRegistry();
return Status;
}