reactos/ntoskrnl/se/accesschk.c
George Bișoc b284e82f47
[NTOS:SE] Do not allocate memory pool just for the access rights
Access check is an expensive operation, that is, whenever an access to an
object is performed an access check has to be done to ensure the access
can be allowed to the calling thread who attempts to access such object.

Currently SepAnalyzeAcesFromDacl allocates a block of pool memory for
access check rights, nagging the Memory Manager like a desperate naughty
creep. So instead initialize the access rights as a simple variable in
SepAccessCheck and pass it out as an address to SepAnalyzeAcesFromDacl so
that the function will fill it up with access rights. This helps with
performance, avoiding wasting a few bits of memory just to hold these
access rights.

In addition to that, add a few asserts and fix the copyright header on
both se.h and accesschk.c, to reflect the Coding Style rules.
2023-03-07 17:50:39 +01:00

1410 lines
48 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Security access check control implementation
* COPYRIGHT: Copyright 2014 Timo Kreuzer <timo.kreuzer@reactos.org>
* Copyright 2014 Eric Kohl
* Copyright 2022-2023 George Bișoc <george.bisoc@reactos.org>
*/
/* INCLUDES *******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* PRIVATE FUNCTIONS **********************************************************/
/**
* @brief
* Analyzes an access control entry that is present in a discretionary
* access control list (DACL) for access right masks of each entry with
* the purpose to judge whether the calling thread can be warranted
* access check to a certain object or not.
*
* @param[in] ActionType
* The type of analysis to be done against an access entry. This type
* influences how access rights are gathered. This can either be AccessCheckMaximum
* which means the algorithm will perform analysis against ACEs on behalf of the
* requestor that gave us the acknowledgement that he desires MAXIMUM_ALLOWED access
* right or AccessCheckRegular if the requestor wants a subset of access rights.
*
* @param[in] RemainingAccess
* The remaining access rights that have yet to be granted to the calling thread
* whomst requests access to a certain object. This parameter mustn't be 0 as
* the remaining rights are left to be addressed. This is the case if we have
* to address the remaining rights on a regular subset basis (the requestor
* didn't ask for MAXIMUM_ALLOWED). Otherwise this parameter can be 0.
*
* @param[in] Dacl
* The discretionary access control list to be given to this function. This DACL
* must have at least one ACE currently present in the list.
*
* @param[in] AccessToken
* A pointer to an access token, where an equality comparison check is performed if
* the security identifier (SID) from a ACE of a certain object is present in this
* token. This token represents the effective (calling thread) token of the caller.
*
* @param[in] PrimaryAccessToken
* A pointer to an access token, represented as an access token associated with the
* primary calling process. This token describes the primary security context of the
* main process.
*
* @param[in] IsTokenRestricted
* If this parameter is set to TRUE, the function considers the token pointed by
* AccessToken parameter argument as restricted. That is, the token has restricted
* SIDs therefore the function will act accordingly against that token by checking
* for restricted SIDs only when doing an equaility comparison check between the
* two identifiers.
*
* @param[in] PrincipalSelfSid
* A pointer to a security identifier that represents a principal. A principal
* identifies a user object which is associated with its own security descriptor.
*
* @param[in] GenericMapping
* A pointer to a generic mapping that is associated with the object in question
* being checked for access. If certain set of desired access rights have
* a generic access right, this parameter is needed to map generic rights.
*
* @param[in] ObjectTypeList
* A pointer to a list array of object types. If such array is provided to the
* function, the algorithm will perform a different approach by doing analysis
* against ACEs each sub-object of an object of primary level (level 0) or sub-objects
* of a sub-object of an object. If this parameter is NULL, the function will normally
* analyze the ACEs of a DACL of the target object itself.
*
* @param[in] ObjectTypeListLength
* The length of the object type list array, pointed by ObjectTypeList. This length in
* question represents the number of elements in such array. This parameter must be 0
* if no array list is provided.
*
* @param[in,out] AccessCheckRights
* A pointer to a structure that contains the access check rights. This function fills
* up this structure with remaining, granted and denied rights to the caller for
* access check. Henceforth, this parameter must not be NULL!
*/
VOID
SepAnalyzeAcesFromDacl(
_In_ ACCESS_CHECK_RIGHT_TYPE ActionType,
_In_ ACCESS_MASK RemainingAccess,
_In_ PACL Dacl,
_In_ PACCESS_TOKEN AccessToken,
_In_ PACCESS_TOKEN PrimaryAccessToken,
_In_ BOOLEAN IsTokenRestricted,
_In_opt_ PSID PrincipalSelfSid,
_In_ PGENERIC_MAPPING GenericMapping,
_In_opt_ POBJECT_TYPE_LIST ObjectTypeList,
_In_ ULONG ObjectTypeListLength,
_Inout_ PACCESS_CHECK_RIGHTS AccessCheckRights)
{
NTSTATUS Status;
PACE CurrentAce;
ULONG AceIndex;
PSID Sid;
ACCESS_MASK Access;
PAGED_CODE();
/* These parameters are really needed */
ASSERT(Dacl);
ASSERT(AccessToken);
/* TODO: To be removed once we support object type handling in Se */
DBG_UNREFERENCED_PARAMETER(ObjectTypeList);
DBG_UNREFERENCED_PARAMETER(ObjectTypeListLength);
/* TODO: To be removed once we support compound ACEs handling in Se */
DBG_UNREFERENCED_PARAMETER(PrimaryAccessToken);
/* Determine how we should analyze the ACEs */
switch (ActionType)
{
/*
* We got the acknowledgement the calling thread desires
* maximum rights (as according to MAXIMUM_ALLOWED access
* mask). Analyze the ACE of the given DACL.
*/
case AccessCheckMaximum:
{
/* Loop over the DACL to retrieve ACEs */
for (AceIndex = 0; AceIndex < Dacl->AceCount; AceIndex++)
{
/* Obtain a ACE now */
Status = RtlGetAce(Dacl, AceIndex, (PVOID*)&CurrentAce);
/* Getting this ACE is important, otherwise something is seriously wrong */
ASSERT(NT_SUCCESS(Status));
/*
* Now it's time to analyze it based upon the
* type of this ACE we're being given.
*/
if (!(CurrentAce->Header.AceFlags & INHERIT_ONLY_ACE))
{
if (CurrentAce->Header.AceType == ACCESS_DENIED_ACE_TYPE)
{
/* Get the SID from this ACE */
Sid = SepGetSidFromAce(ACCESS_DENIED_ACE_TYPE, CurrentAce);
ASSERT(Sid);
if (SepSidInTokenEx(AccessToken, PrincipalSelfSid, Sid, TRUE, IsTokenRestricted))
{
/* Get this access right from the ACE */
Access = CurrentAce->AccessMask;
/* Map this access right if it has a generic mask right */
if ((Access & GENERIC_ACCESS) && GenericMapping)
{
RtlMapGenericMask(&Access, GenericMapping);
}
/* Deny access rights that have not been granted yet */
AccessCheckRights->DeniedAccessRights |= (Access & ~AccessCheckRights->GrantedAccessRights);
DPRINT("SepAnalyzeAcesFromDacl(): DeniedAccessRights 0x%08lx\n", AccessCheckRights->DeniedAccessRights);
}
}
else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
{
/* Get the SID from this ACE */
Sid = SepGetSidFromAce(ACCESS_ALLOWED_ACE_TYPE, CurrentAce);
ASSERT(Sid);
if (SepSidInTokenEx(AccessToken, PrincipalSelfSid, Sid, FALSE, IsTokenRestricted))
{
/* Get this access right from the ACE */
Access = CurrentAce->AccessMask;
/* Map this access right if it has a generic mask right */
if ((Access & GENERIC_ACCESS) && GenericMapping)
{
RtlMapGenericMask(&Access, GenericMapping);
}
/* Grant access rights that have not been denied yet */
AccessCheckRights->GrantedAccessRights |= (Access & ~AccessCheckRights->DeniedAccessRights);
DPRINT("SepAnalyzeAcesFromDacl(): GrantedAccessRights 0x%08lx\n", AccessCheckRights->GrantedAccessRights);
}
}
else
{
DPRINT1("SepAnalyzeAcesFromDacl(): Unsupported ACE type 0x%lx\n", CurrentAce->Header.AceType);
}
}
}
/* We're done here */
break;
}
/*
* We got the acknowledgement the calling thread desires
* only a subset of rights therefore we have to act a little
* different here.
*/
case AccessCheckRegular:
{
/* Cache the remaining access rights to be addressed */
ASSERT(RemainingAccess != 0);
AccessCheckRights->RemainingAccessRights = RemainingAccess;
/* Loop over the DACL to retrieve ACEs */
for (AceIndex = 0; AceIndex < Dacl->AceCount; AceIndex++)
{
/* Obtain a ACE now */
Status = RtlGetAce(Dacl, AceIndex, (PVOID*)&CurrentAce);
/* Getting this ACE is important, otherwise something is seriously wrong */
ASSERT(NT_SUCCESS(Status));
/*
* Now it's time to analyze it based upon the
* type of this ACE we're being given.
*/
if (!(CurrentAce->Header.AceFlags & INHERIT_ONLY_ACE))
{
if (CurrentAce->Header.AceType == ACCESS_DENIED_ACE_TYPE)
{
/* Get the SID from this ACE */
Sid = SepGetSidFromAce(ACCESS_DENIED_ACE_TYPE, CurrentAce);
ASSERT(Sid);
if (SepSidInTokenEx(AccessToken, PrincipalSelfSid, Sid, TRUE, IsTokenRestricted))
{
/* Get this access right from the ACE */
Access = CurrentAce->AccessMask;
/* Map this access right if it has a generic mask right */
if ((Access & GENERIC_ACCESS) && GenericMapping)
{
RtlMapGenericMask(&Access, GenericMapping);
}
/*
* The caller requests a right that cannot be
* granted. Access is implicitly denied for
* the calling thread. Track this access right.
*/
if (AccessCheckRights->RemainingAccessRights & Access)
{
DPRINT("SepAnalyzeAcesFromDacl(): Refuted access 0x%08lx\n", Access);
AccessCheckRights->DeniedAccessRights |= Access;
break;
}
}
}
else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
{
/* Get the SID from this ACE */
Sid = SepGetSidFromAce(ACCESS_ALLOWED_ACE_TYPE, CurrentAce);
ASSERT(Sid);
if (SepSidInTokenEx(AccessToken, PrincipalSelfSid, Sid, FALSE, IsTokenRestricted))
{
/* Get this access right from the ACE */
Access = CurrentAce->AccessMask;
/* Map this access right if it has a generic mask right */
if ((Access & GENERIC_ACCESS) && GenericMapping)
{
RtlMapGenericMask(&Access, GenericMapping);
}
/* Remove the remaining rights */
DPRINT("SepAnalyzeAcesFromDacl(): RemainingAccessRights 0x%08lx Access 0x%08lx\n", AccessCheckRights->RemainingAccessRights, Access);
AccessCheckRights->RemainingAccessRights &= ~Access;
DPRINT("SepAnalyzeAcesFromDacl(): RemainingAccessRights 0x%08lx\n", AccessCheckRights->RemainingAccessRights);
/* Track the granted access right */
AccessCheckRights->GrantedAccessRights |= Access;
}
}
else
{
DPRINT1("SepAnalyzeAcesFromDacl(): Unsupported ACE type 0x%lx\n", CurrentAce->Header.AceType);
}
}
}
/* We're done here */
break;
}
/* We shouldn't reach here */
DEFAULT_UNREACHABLE;
}
}
/**
* @brief
* Private function that determines whether security access rights can be given
* to the calling thread in order to access an object depending on the security
* descriptor and other security context entities, such as an owner. This
* function is the heart and brain of the whole access check algorithm in
* the kernel.
*
* @param[in] ClientAccessToken
* A pointer to a client (thread) access token that requests access rights
* of an object or subset of multiple objects.
*
* @param[in] PrimaryAccessToken
* A pointer to a primary access token that describes the primary security
* context of the main calling process.
*
* @param[in] PrincipalSelfSid
* A pointer to a security identifier that represents a security principal,
* that is, a user object associated with its security descriptor.
*
* @param[in] DesiredAccess
* The access rights desired by the calling thread to acquire in order to
* access an object.
*
* @param[in] ObjectTypeList
* An array list of object types to be checked against for access. The function
* will act accordingly in this case by checking each sub-object of an object
* of primary level and such. If this parameter is NULL, the function will
* perform a normal access check against the target object itself.
*
* @param[in] ObjectTypeListLength
* The length of a object type list. Such length represents the number of
* elements in this list.
*
* @param[in] PreviouslyGrantedAccess
* The access rights previously acquired in the past. If this parameter is 0,
* it is deemed that the calling thread hasn't acquired any rights. Access checks
* are more tighten in this case.
*
* @param[in] GenericMapping
* A pointer to a generic mapping of access rights of the target object.
*
* @param[in] AccessMode
* The processor request level mode.
*
* @param[in] UseResultList
* If set to TRUE, the function will return a list of granted access rights
* of each sub-object as well as status code for each. If this parameter is
* set to FALSE, then the function will just return only the granted access
* rights and status code for single object that's been target for access
* checks.
*
* @param[out] Privileges
* A pointer to a definite set of privileges that have been audited
* whilst doing access check procedures. Such set of privileges are
* optionally returned to the caller. This can be set to NULL if
* the caller doesn't want to obtain a set of privileges.
*
* @param[out] GrantedAccessList
* A list of granted access rights returned to the caller. This list
* can comprehend multiple elements which represent the sub-objects
* that have been checked or a single element which is the target
* object itself.
*
* @param[out] AccessStatusList
* A list of access status codes returned to the caller. This list
* can comprehend multiple elements which represent the sub-objects
* that have been checked or a single element which is the target
* object itself.
*
* @return
* Returns TRUE if access onto the specific object is allowed, FALSE
* otherwise.
*/
BOOLEAN
NTAPI
SepAccessCheck(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_opt_ PACCESS_TOKEN ClientAccessToken,
_In_ PACCESS_TOKEN PrimaryAccessToken,
_In_opt_ PSID PrincipalSelfSid,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_TYPE_LIST ObjectTypeList,
_In_ ULONG ObjectTypeListLength,
_In_ ACCESS_MASK PreviouslyGrantedAccess,
_In_ PGENERIC_MAPPING GenericMapping,
_In_ KPROCESSOR_MODE AccessMode,
_In_ BOOLEAN UseResultList,
_Out_opt_ PPRIVILEGE_SET* Privileges,
_Out_ PACCESS_MASK GrantedAccessList,
_Out_ PNTSTATUS AccessStatusList)
{
ACCESS_MASK RemainingAccess;
ULONG ResultListLength;
ULONG ResultListIndex;
PACL Dacl;
BOOLEAN Present;
BOOLEAN Defaulted;
NTSTATUS Status;
PACCESS_TOKEN Token = NULL;
ACCESS_CHECK_RIGHTS AccessCheckRights = {0};
PAGED_CODE();
/* A security descriptor must be expected for access checks */
ASSERT(SecurityDescriptor);
/* Check for no access desired */
if (!DesiredAccess)
{
/* Check if we had no previous access */
if (!PreviouslyGrantedAccess)
{
/* Then there's nothing to give */
DPRINT1("SepAccessCheck(): The caller has no previously granted access gained!\n");
Status = STATUS_ACCESS_DENIED;
goto ReturnCommonStatus;
}
/* Return the previous access only */
Status = STATUS_SUCCESS;
*Privileges = NULL;
goto ReturnCommonStatus;
}
/* Map given accesses */
RtlMapGenericMask(&DesiredAccess, GenericMapping);
if (PreviouslyGrantedAccess)
RtlMapGenericMask(&PreviouslyGrantedAccess, GenericMapping);
/* Initialize remaining access rights */
RemainingAccess = DesiredAccess;
/*
* Obtain the token provided by the caller. Client (or also
* called impersonation or thread) token takes precedence over
* the primary token which is the token associated with the security
* context of the main calling process. This is because it is the
* client itself that requests access of an object or subset of
* multiple objects. Otherwise obtain the security context of the
* main process (the actual primary token).
*/
Token = ClientAccessToken ? ClientAccessToken : PrimaryAccessToken;
/*
* We should at least expect a primary token
* to be present if client token is not
* available.
*/
ASSERT(Token);
/*
* Check for ACCESS_SYSTEM_SECURITY and WRITE_OWNER access.
* Write down a set of privileges that have been checked
* if the caller wants it.
*/
Status = SePrivilegePolicyCheck(&RemainingAccess,
&PreviouslyGrantedAccess,
NULL,
Token,
Privileges,
AccessMode);
if (!NT_SUCCESS(Status))
{
goto ReturnCommonStatus;
}
/* Succeed if there are no more rights to grant */
if (RemainingAccess == 0)
{
Status = STATUS_SUCCESS;
goto ReturnCommonStatus;
}
/* Get the DACL */
Status = RtlGetDaclSecurityDescriptor(SecurityDescriptor,
&Present,
&Dacl,
&Defaulted);
if (!NT_SUCCESS(Status))
{
goto ReturnCommonStatus;
}
/* Grant desired access if the object is unprotected */
if (Present == FALSE || Dacl == NULL)
{
PreviouslyGrantedAccess |= RemainingAccess;
if (RemainingAccess & MAXIMUM_ALLOWED)
{
PreviouslyGrantedAccess &= ~MAXIMUM_ALLOWED;
PreviouslyGrantedAccess |= GenericMapping->GenericAll;
}
Status = STATUS_SUCCESS;
goto ReturnCommonStatus;
}
/* Deny access if the DACL is empty */
if (Dacl->AceCount == 0)
{
if (RemainingAccess == MAXIMUM_ALLOWED && PreviouslyGrantedAccess != 0)
{
Status = STATUS_SUCCESS;
}
else
{
DPRINT1("SepAccessCheck(): The DACL has no ACEs and the caller has no previously granted access!\n");
PreviouslyGrantedAccess = 0;
Status = STATUS_ACCESS_DENIED;
}
goto ReturnCommonStatus;
}
/* Determine the MAXIMUM_ALLOWED access rights according to the DACL */
if (DesiredAccess & MAXIMUM_ALLOWED)
{
/* Perform access checks against ACEs from this DACL */
SepAnalyzeAcesFromDacl(AccessCheckMaximum,
0,
Dacl,
Token,
PrimaryAccessToken,
FALSE,
PrincipalSelfSid,
GenericMapping,
ObjectTypeList,
ObjectTypeListLength,
&AccessCheckRights);
/*
* Perform further access checks if this token
* has restricted SIDs.
*/
if (SeTokenIsRestricted(Token))
{
SepAnalyzeAcesFromDacl(AccessCheckMaximum,
0,
Dacl,
Token,
PrimaryAccessToken,
TRUE,
PrincipalSelfSid,
GenericMapping,
ObjectTypeList,
ObjectTypeListLength,
&AccessCheckRights);
}
/* Fail if some rights have not been granted */
RemainingAccess &= ~(MAXIMUM_ALLOWED | AccessCheckRights.GrantedAccessRights);
if (RemainingAccess != 0)
{
DPRINT1("SepAccessCheck(): Failed to grant access rights. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n", RemainingAccess, DesiredAccess);
PreviouslyGrantedAccess = 0;
Status = STATUS_ACCESS_DENIED;
goto ReturnCommonStatus;
}
/* Set granted access right and access status */
PreviouslyGrantedAccess |= AccessCheckRights.GrantedAccessRights;
if (PreviouslyGrantedAccess != 0)
{
Status = STATUS_SUCCESS;
}
else
{
DPRINT1("SepAccessCheck(): Failed to grant access rights. PreviouslyGrantedAccess == 0 DesiredAccess = %08lx\n", DesiredAccess);
Status = STATUS_ACCESS_DENIED;
}
/* We have successfully granted all the rights */
goto ReturnCommonStatus;
}
/* Grant rights according to the DACL */
SepAnalyzeAcesFromDacl(AccessCheckRegular,
RemainingAccess,
Dacl,
Token,
PrimaryAccessToken,
FALSE,
PrincipalSelfSid,
GenericMapping,
ObjectTypeList,
ObjectTypeListLength,
&AccessCheckRights);
/* Fail if some rights have not been granted */
if (AccessCheckRights.RemainingAccessRights != 0)
{
DPRINT1("SepAccessCheck(): Failed to grant access rights. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n", AccessCheckRights.RemainingAccessRights, DesiredAccess);
PreviouslyGrantedAccess = 0;
Status = STATUS_ACCESS_DENIED;
goto ReturnCommonStatus;
}
/*
* Perform further access checks if this token
* has restricted SIDs.
*/
if (SeTokenIsRestricted(Token))
{
SepAnalyzeAcesFromDacl(AccessCheckRegular,
RemainingAccess,
Dacl,
Token,
PrimaryAccessToken,
TRUE,
PrincipalSelfSid,
GenericMapping,
ObjectTypeList,
ObjectTypeListLength,
&AccessCheckRights);
/* Fail if some rights have not been granted */
if (AccessCheckRights.RemainingAccessRights != 0)
{
DPRINT1("SepAccessCheck(): Failed to grant access rights. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n", AccessCheckRights.RemainingAccessRights, DesiredAccess);
PreviouslyGrantedAccess = 0;
Status = STATUS_ACCESS_DENIED;
goto ReturnCommonStatus;
}
}
/* Set granted access rights */
PreviouslyGrantedAccess |= DesiredAccess;
/* Fail if no rights have been granted */
if (PreviouslyGrantedAccess == 0)
{
DPRINT1("SepAccessCheck(): Failed to grant access rights. PreviouslyGrantedAccess == 0 DesiredAccess = %08lx\n", DesiredAccess);
Status = STATUS_ACCESS_DENIED;
goto ReturnCommonStatus;
}
/*
* If we're here then we granted all the desired
* access rights the caller wanted.
*/
Status = STATUS_SUCCESS;
ReturnCommonStatus:
ResultListLength = UseResultList ? ObjectTypeListLength : 1;
for (ResultListIndex = 0; ResultListIndex < ResultListLength; ResultListIndex++)
{
GrantedAccessList[ResultListIndex] = PreviouslyGrantedAccess;
AccessStatusList[ResultListIndex] = Status;
}
#if DBG
/* Dump security debug info on access denied case */
if (Status == STATUS_ACCESS_DENIED)
{
SepDumpSdDebugInfo(SecurityDescriptor);
SepDumpTokenDebugInfo(Token);
SepDumpAccessRightsStats(&AccessCheckRights);
}
#endif
return NT_SUCCESS(Status);
}
/**
* @brief
* Retrieves the main user from a security descriptor.
*
* @param[in] SecurityDescriptor
* A valid allocated security descriptor structure where the owner
* is to be retrieved.
*
* @return
* Returns a SID that represents the main user (owner).
*/
static PSID
SepGetSDOwner(
_In_ PSECURITY_DESCRIPTOR _SecurityDescriptor)
{
PISECURITY_DESCRIPTOR SecurityDescriptor = _SecurityDescriptor;
PSID Owner;
if (SecurityDescriptor->Control & SE_SELF_RELATIVE)
Owner = (PSID)((ULONG_PTR)SecurityDescriptor->Owner +
(ULONG_PTR)SecurityDescriptor);
else
Owner = (PSID)SecurityDescriptor->Owner;
return Owner;
}
/**
* @brief
* Retrieves the group from a security descriptor.
*
* @param[in] SecurityDescriptor
* A valid allocated security descriptor structure where the group
* is to be retrieved.
*
* @return
* Returns a SID that represents a group.
*/
static PSID
SepGetSDGroup(
_In_ PSECURITY_DESCRIPTOR _SecurityDescriptor)
{
PISECURITY_DESCRIPTOR SecurityDescriptor = _SecurityDescriptor;
PSID Group;
if (SecurityDescriptor->Control & SE_SELF_RELATIVE)
Group = (PSID)((ULONG_PTR)SecurityDescriptor->Group +
(ULONG_PTR)SecurityDescriptor);
else
Group = (PSID)SecurityDescriptor->Group;
return Group;
}
/**
* @brief
* Retrieves the length size of a set list of privileges structure.
*
* @param[in] PrivilegeSet
* A valid set of privileges.
*
* @return
* Returns the total length of a set of privileges.
*/
static
ULONG
SepGetPrivilegeSetLength(
_In_ PPRIVILEGE_SET PrivilegeSet)
{
if (PrivilegeSet == NULL)
return 0;
if (PrivilegeSet->PrivilegeCount == 0)
return (ULONG)(sizeof(PRIVILEGE_SET) - sizeof(LUID_AND_ATTRIBUTES));
return (ULONG)(sizeof(PRIVILEGE_SET) +
(PrivilegeSet->PrivilegeCount - 1) * sizeof(LUID_AND_ATTRIBUTES));
}
/* PUBLIC FUNCTIONS ***********************************************************/
/**
* @brief
* Determines whether security access rights can be given to an object
* depending on the security descriptor and other security context
* entities, such as an owner.
*
* @param[in] SecurityDescriptor
* Security descriptor of the object that is being accessed.
*
* @param[in] SubjectSecurityContext
* The captured subject security context.
*
* @param[in] SubjectContextLocked
* If set to TRUE, the caller acknowledges that the subject context
* has already been locked by the caller himself. If set to FALSE,
* the function locks the subject context.
*
* @param[in] DesiredAccess
* Access right bitmask that the calling thread wants to acquire.
*
* @param[in] PreviouslyGrantedAccess
* The access rights previously acquired in the past.
*
* @param[out] Privileges
* The returned set of privileges.
*
* @param[in] GenericMapping
* The generic mapping of access rights of an object type.
*
* @param[in] AccessMode
* The processor request level mode.
*
* @param[out] GrantedAccess
* A list of granted access rights.
*
* @param[out] AccessStatus
* The returned status code specifying why access cannot be made
* onto an object (if said access is denied in the first place).
*
* @return
* Returns TRUE if access onto the specific object is allowed, FALSE
* otherwise.
*/
BOOLEAN
NTAPI
SeAccessCheck(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_ PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext,
_In_ BOOLEAN SubjectContextLocked,
_In_ ACCESS_MASK DesiredAccess,
_In_ ACCESS_MASK PreviouslyGrantedAccess,
_Out_ PPRIVILEGE_SET* Privileges,
_In_ PGENERIC_MAPPING GenericMapping,
_In_ KPROCESSOR_MODE AccessMode,
_Out_ PACCESS_MASK GrantedAccess,
_Out_ PNTSTATUS AccessStatus)
{
BOOLEAN ret;
PAGED_CODE();
/* Check if this is kernel mode */
if (AccessMode == KernelMode)
{
/* Check if kernel wants everything */
if (DesiredAccess & MAXIMUM_ALLOWED)
{
/* Give it */
*GrantedAccess = GenericMapping->GenericAll;
*GrantedAccess |= (DesiredAccess &~ MAXIMUM_ALLOWED);
*GrantedAccess |= PreviouslyGrantedAccess;
}
else
{
/* Give the desired and previous access */
*GrantedAccess = DesiredAccess | PreviouslyGrantedAccess;
}
/* Success */
*AccessStatus = STATUS_SUCCESS;
return TRUE;
}
/* Check if we didn't get an SD */
if (!SecurityDescriptor)
{
/* Automatic failure */
*AccessStatus = STATUS_ACCESS_DENIED;
return FALSE;
}
/* Check for invalid impersonation */
if ((SubjectSecurityContext->ClientToken) &&
(SubjectSecurityContext->ImpersonationLevel < SecurityImpersonation))
{
*AccessStatus = STATUS_BAD_IMPERSONATION_LEVEL;
return FALSE;
}
/* Acquire the lock if needed */
if (!SubjectContextLocked)
SeLockSubjectContext(SubjectSecurityContext);
/* Check if the token is the owner and grant WRITE_DAC and READ_CONTROL rights */
if (DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED))
{
PACCESS_TOKEN Token = SubjectSecurityContext->ClientToken ?
SubjectSecurityContext->ClientToken : SubjectSecurityContext->PrimaryToken;
if (SepTokenIsOwner(Token,
SecurityDescriptor,
FALSE))
{
if (DesiredAccess & MAXIMUM_ALLOWED)
PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL);
else
PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL));
DesiredAccess &= ~(WRITE_DAC | READ_CONTROL);
}
}
if (DesiredAccess == 0)
{
*GrantedAccess = PreviouslyGrantedAccess;
if (PreviouslyGrantedAccess == 0)
{
DPRINT1("Request for zero access to an object. Denying.\n");
*AccessStatus = STATUS_ACCESS_DENIED;
ret = FALSE;
}
else
{
*AccessStatus = STATUS_SUCCESS;
ret = TRUE;
}
}
else
{
/* Call the internal function */
ret = SepAccessCheck(SecurityDescriptor,
SubjectSecurityContext->ClientToken,
SubjectSecurityContext->PrimaryToken,
NULL,
DesiredAccess,
NULL,
0,
PreviouslyGrantedAccess,
GenericMapping,
AccessMode,
FALSE,
Privileges,
GrantedAccess,
AccessStatus);
}
/* Release the lock if needed */
if (!SubjectContextLocked)
SeUnlockSubjectContext(SubjectSecurityContext);
return ret;
}
/**
* @brief
* Determines whether security access rights can be given to an object
* depending on the security descriptor. Unlike the regular access check
* procedure in the NT kernel, the fast traverse check is a faster way
* to quickly check if access can be made into an object.
*
* @param[in] SecurityDescriptor
* Security descriptor of the object that is being accessed.
*
* @param[in] AccessState
* An access state to determine if the access token in the current
* security context of the object is an restricted token.
*
* @param[in] DesiredAccess
* The access right bitmask where the calling thread wants to acquire.
*
* @param[in] AccessMode
* Process level request mode.
*
* @return
* Returns TRUE if access onto the specific object is allowed, FALSE
* otherwise.
*/
BOOLEAN
NTAPI
SeFastTraverseCheck(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_ PACCESS_STATE AccessState,
_In_ ACCESS_MASK DesiredAccess,
_In_ KPROCESSOR_MODE AccessMode)
{
PACL Dacl;
ULONG AceIndex;
PKNOWN_ACE Ace;
PAGED_CODE();
ASSERT(AccessMode != KernelMode);
if (SecurityDescriptor == NULL)
return FALSE;
/* Get DACL */
Dacl = SepGetDaclFromDescriptor(SecurityDescriptor);
/* If no DACL, grant access */
if (Dacl == NULL)
return TRUE;
/* No ACE -> Deny */
if (!Dacl->AceCount)
return FALSE;
/* Can't perform the check on restricted token */
if (AccessState->Flags & TOKEN_IS_RESTRICTED)
return FALSE;
/* Browse the ACEs */
for (AceIndex = 0, Ace = (PKNOWN_ACE)((ULONG_PTR)Dacl + sizeof(ACL));
AceIndex < Dacl->AceCount;
AceIndex++, Ace = (PKNOWN_ACE)((ULONG_PTR)Ace + Ace->Header.AceSize))
{
if (Ace->Header.AceFlags & INHERIT_ONLY_ACE)
continue;
/* If access-allowed ACE */
if (Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
{
/* Check if all accesses are granted */
if (!(Ace->Mask & DesiredAccess))
continue;
/* Check SID and grant access if matching */
if (RtlEqualSid(SeWorldSid, &(Ace->SidStart)))
return TRUE;
}
/* If access-denied ACE */
else if (Ace->Header.AceType == ACCESS_DENIED_ACE_TYPE)
{
/* Here, only check if it denies any access wanted and deny if so */
if (Ace->Mask & DesiredAccess)
return FALSE;
}
}
/* Faulty, deny */
return FALSE;
}
/* SYSTEM CALLS ***************************************************************/
/**
* @brief
* Determines whether security access rights can be given to an object
* depending on the security descriptor and a valid handle to an access
* token.
*
* @param[in] SecurityDescriptor
* Security descriptor of the object that is being accessed.
*
* @param[in] TokenHandle
* A handle to a token.
*
* @param[in] DesiredAccess
* The access right bitmask where the calling thread wants to acquire.
*
* @param[in] GenericMapping
* The generic mapping of access rights of an object type.
*
* @param[out] PrivilegeSet
* The returned set of privileges.
*
* @param[in,out] PrivilegeSetLength
* The total length size of a set of privileges.
*
* @param[out] GrantedAccess
* A list of granted access rights.
*
* @param[out] AccessStatus
* The returned status code specifying why access cannot be made
* onto an object (if said access is denied in the first place).
*
* @return
* Returns STATUS_SUCCESS if access check has been done without problems
* and that the object can be accessed. STATUS_GENERIC_NOT_MAPPED is returned
* if no generic access right is mapped. STATUS_NO_IMPERSONATION_TOKEN is returned
* if the token from the handle is not an impersonation token.
* STATUS_BAD_IMPERSONATION_LEVEL is returned if the token cannot be impersonated
* because the current security impersonation level doesn't permit so.
* STATUS_INVALID_SECURITY_DESCR is returned if the security descriptor given
* to the call is not a valid one. STATUS_BUFFER_TOO_SMALL is returned if
* the buffer to the captured privileges has a length that is less than the required
* size of the set of privileges. A failure NTSTATUS code is returned otherwise.
*/
NTSTATUS
NTAPI
NtAccessCheck(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_ HANDLE TokenHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PGENERIC_MAPPING GenericMapping,
_Out_opt_ PPRIVILEGE_SET PrivilegeSet,
_Inout_ PULONG PrivilegeSetLength,
_Out_ PACCESS_MASK GrantedAccess,
_Out_ PNTSTATUS AccessStatus)
{
PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = NULL;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
ACCESS_MASK PreviouslyGrantedAccess = 0;
PPRIVILEGE_SET Privileges = NULL;
ULONG CapturedPrivilegeSetLength, RequiredPrivilegeSetLength;
PTOKEN Token;
NTSTATUS Status;
PAGED_CODE();
/* Check if this is kernel mode */
if (PreviousMode == KernelMode)
{
/* Check if kernel wants everything */
if (DesiredAccess & MAXIMUM_ALLOWED)
{
/* Give it */
*GrantedAccess = GenericMapping->GenericAll;
*GrantedAccess |= (DesiredAccess &~ MAXIMUM_ALLOWED);
}
else
{
/* Just give the desired access */
*GrantedAccess = DesiredAccess;
}
/* Success */
*AccessStatus = STATUS_SUCCESS;
return STATUS_SUCCESS;
}
/* Protect probe in SEH */
_SEH2_TRY
{
/* Probe all pointers */
ProbeForRead(GenericMapping, sizeof(GENERIC_MAPPING), sizeof(ULONG));
ProbeForRead(PrivilegeSetLength, sizeof(ULONG), sizeof(ULONG));
ProbeForWrite(PrivilegeSet, *PrivilegeSetLength, sizeof(ULONG));
ProbeForWrite(GrantedAccess, sizeof(ACCESS_MASK), sizeof(ULONG));
ProbeForWrite(AccessStatus, sizeof(NTSTATUS), sizeof(ULONG));
/* Capture the privilege set length and the mapping */
CapturedPrivilegeSetLength = *PrivilegeSetLength;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
/* Check for unmapped access rights */
if (DesiredAccess & (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL))
return STATUS_GENERIC_NOT_MAPPED;
/* Reference the token */
Status = ObReferenceObjectByHandle(TokenHandle,
TOKEN_QUERY,
SeTokenObjectType,
PreviousMode,
(PVOID*)&Token,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT("Failed to reference token (Status %lx)\n", Status);
return Status;
}
/* Check token type */
if (Token->TokenType != TokenImpersonation)
{
DPRINT("No impersonation token\n");
ObDereferenceObject(Token);
return STATUS_NO_IMPERSONATION_TOKEN;
}
/* Check the impersonation level */
if (Token->ImpersonationLevel < SecurityIdentification)
{
DPRINT("Impersonation level < SecurityIdentification\n");
ObDereferenceObject(Token);
return STATUS_BAD_IMPERSONATION_LEVEL;
}
/* Check for ACCESS_SYSTEM_SECURITY and WRITE_OWNER access */
Status = SePrivilegePolicyCheck(&DesiredAccess,
&PreviouslyGrantedAccess,
NULL,
Token,
&Privileges,
PreviousMode);
if (!NT_SUCCESS(Status))
{
DPRINT("SePrivilegePolicyCheck failed (Status 0x%08lx)\n", Status);
ObDereferenceObject(Token);
*AccessStatus = Status;
*GrantedAccess = 0;
return STATUS_SUCCESS;
}
/* Check the size of the privilege set and return the privileges */
if (Privileges != NULL)
{
DPRINT("Privileges != NULL\n");
/* Calculate the required privilege set buffer size */
RequiredPrivilegeSetLength = SepGetPrivilegeSetLength(Privileges);
/* Fail if the privilege set buffer is too small */
if (CapturedPrivilegeSetLength < RequiredPrivilegeSetLength)
{
ObDereferenceObject(Token);
SeFreePrivileges(Privileges);
*PrivilegeSetLength = RequiredPrivilegeSetLength;
return STATUS_BUFFER_TOO_SMALL;
}
/* Copy the privilege set to the caller */
RtlCopyMemory(PrivilegeSet,
Privileges,
RequiredPrivilegeSetLength);
/* Free the local privilege set */
SeFreePrivileges(Privileges);
}
else
{
DPRINT("Privileges == NULL\n");
/* Fail if the privilege set buffer is too small */
if (CapturedPrivilegeSetLength < sizeof(PRIVILEGE_SET))
{
ObDereferenceObject(Token);
*PrivilegeSetLength = sizeof(PRIVILEGE_SET);
return STATUS_BUFFER_TOO_SMALL;
}
/* Initialize the privilege set */
PrivilegeSet->PrivilegeCount = 0;
PrivilegeSet->Control = 0;
}
/* Capture the security descriptor */
Status = SeCaptureSecurityDescriptor(SecurityDescriptor,
PreviousMode,
PagedPool,
FALSE,
&CapturedSecurityDescriptor);
if (!NT_SUCCESS(Status))
{
DPRINT("Failed to capture the Security Descriptor\n");
ObDereferenceObject(Token);
return Status;
}
/* Check the captured security descriptor */
if (CapturedSecurityDescriptor == NULL)
{
DPRINT("Security Descriptor is NULL\n");
ObDereferenceObject(Token);
return STATUS_INVALID_SECURITY_DESCR;
}
/* Check security descriptor for valid owner and group */
if (SepGetSDOwner(CapturedSecurityDescriptor) == NULL ||
SepGetSDGroup(CapturedSecurityDescriptor) == NULL)
{
DPRINT("Security Descriptor does not have a valid group or owner\n");
SeReleaseSecurityDescriptor(CapturedSecurityDescriptor,
PreviousMode,
FALSE);
ObDereferenceObject(Token);
return STATUS_INVALID_SECURITY_DESCR;
}
/* Set up the subject context, and lock it */
SeCaptureSubjectContext(&SubjectSecurityContext);
/* Lock the token */
SepAcquireTokenLockShared(Token);
/* Check if the token is the owner and grant WRITE_DAC and READ_CONTROL rights */
if (DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED))
{
if (SepTokenIsOwner(Token, CapturedSecurityDescriptor, FALSE))
{
if (DesiredAccess & MAXIMUM_ALLOWED)
PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL);
else
PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL));
DesiredAccess &= ~(WRITE_DAC | READ_CONTROL);
}
}
if (DesiredAccess == 0)
{
*GrantedAccess = PreviouslyGrantedAccess;
*AccessStatus = STATUS_SUCCESS;
}
else
{
/* Now perform the access check */
SepAccessCheck(CapturedSecurityDescriptor,
Token,
&SubjectSecurityContext.PrimaryToken,
NULL,
DesiredAccess,
NULL,
0,
PreviouslyGrantedAccess,
GenericMapping,
PreviousMode,
FALSE,
NULL,
GrantedAccess,
AccessStatus);
}
/* Release subject context and unlock the token */
SeReleaseSubjectContext(&SubjectSecurityContext);
SepReleaseTokenLock(Token);
/* Release the captured security descriptor */
SeReleaseSecurityDescriptor(CapturedSecurityDescriptor,
PreviousMode,
FALSE);
/* Dereference the token */
ObDereferenceObject(Token);
/* Check succeeded */
return STATUS_SUCCESS;
}
/**
* @brief
* Determines whether security access could be granted or not on
* an object by the requestor who wants such access through type.
*
* @param[in] SecurityDescriptor
* A security descriptor with information data for auditing.
*
* @param[in] PrincipalSelfSid
* A principal self user SID.
*
* @param[in] ClientToken
* A client access token.
*
* @param[in] DesiredAccess
* The desired access masks rights requested by the caller.
*
* @param[in] ObjectTypeList
* A list of object types.
*
* @param[in] ObjectTypeLength
* The length size of the list.
*
* @param[in] GenericMapping
* The generic mapping list of access masks rights.
*
* @param[in] PrivilegeSet
* An array set of privileges.
*
* @param[in,out] PrivilegeSetLength
* The length size of the array set of privileges.
*
* @param[out] GrantedAccess
* The returned granted access rights.
*
* @param[out] AccessStatus
* The returned NTSTATUS code indicating the final results
* of auditing.
*
* @return
* To be added...
*/
NTSTATUS
NTAPI
NtAccessCheckByType(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_ PSID PrincipalSelfSid,
_In_ HANDLE ClientToken,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_TYPE_LIST ObjectTypeList,
_In_ ULONG ObjectTypeLength,
_In_ PGENERIC_MAPPING GenericMapping,
_In_ PPRIVILEGE_SET PrivilegeSet,
_Inout_ PULONG PrivilegeSetLength,
_Out_ PACCESS_MASK GrantedAccess,
_Out_ PNTSTATUS AccessStatus)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
/**
* @brief
* Determines whether security access could be granted or not on
* an object by the requestor who wants such access through
* type list.
*
* @param[in] SecurityDescriptor
* A security descriptor with information data for auditing.
*
* @param[in] PrincipalSelfSid
* A principal self user SID.
*
* @param[in] ClientToken
* A client access token.
*
* @param[in] DesiredAccess
* The desired access masks rights requested by the caller.
*
* @param[in] ObjectTypeList
* A list of object types.
*
* @param[in] ObjectTypeLength
* The length size of the list.
*
* @param[in] GenericMapping
* The generic mapping list of access masks rights.
*
* @param[in] PrivilegeSet
* An array set of privileges.
*
* @param[in,out] PrivilegeSetLength
* The length size of the array set of privileges.
*
* @param[out] GrantedAccess
* The returned granted access rights.
*
* @param[out] AccessStatus
* The returned NTSTATUS code indicating the final results
* of auditing.
*
* @return
* To be added...
*/
NTSTATUS
NTAPI
NtAccessCheckByTypeResultList(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
_In_ PSID PrincipalSelfSid,
_In_ HANDLE ClientToken,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_TYPE_LIST ObjectTypeList,
_In_ ULONG ObjectTypeLength,
_In_ PGENERIC_MAPPING GenericMapping,
_In_ PPRIVILEGE_SET PrivilegeSet,
_Inout_ PULONG PrivilegeSetLength,
_Out_ PACCESS_MASK GrantedAccess,
_Out_ PNTSTATUS AccessStatus)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
/* EOF */