mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 12:26:32 +00:00
d72025649b
They can be spammy. Also clarify these debug prints, because some people think that "failed to grant access rights" means there's something wrong in the core access check functions.
2328 lines
89 KiB
C
2328 lines
89 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
|
|
* Denies access of a target object and the children objects
|
|
* in an object type list.
|
|
*
|
|
* @param[in,out] ObjectTypeList
|
|
* A pointer to an object type list where access is to be
|
|
* denied for the target object and its children in the
|
|
* hierarchy list.
|
|
*
|
|
* @param[in] ObjectTypeListLength
|
|
* The length of the object type list. This length represents
|
|
* the number of object elements in the list.
|
|
*
|
|
* @param[in] AccessMask
|
|
* The access mask right that is to be denied for the object.
|
|
*
|
|
* @param[in] ObjectTypeGuid
|
|
* A pointer to a object type GUID, that identifies the object.
|
|
* This GUID is used to search for the target object in the list.
|
|
* If this parameter is set to NULL, the function will deny access
|
|
* starting from the object itself in the list (aka the root).
|
|
*/
|
|
static
|
|
VOID
|
|
SepDenyAccessObjectTypeResultList(
|
|
_Inout_ POBJECT_TYPE_LIST_INTERNAL ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ ACCESS_MASK AccessMask,
|
|
_In_opt_ PGUID ObjectTypeGuid)
|
|
{
|
|
ULONG ObjectTypeIndex;
|
|
ULONG ReturnedObjectIndex;
|
|
USHORT Level;
|
|
|
|
PAGED_CODE();
|
|
|
|
DPRINT("Access rights 0x%08lx\n", AccessMask);
|
|
|
|
/*
|
|
* The object type of interest is the one that was supplied
|
|
* by the creator who made the ACE. If the object type was
|
|
* not supplied then we have no clear indication from where
|
|
* shall we start updating the access rights of objects on
|
|
* this list, so we have to begin from the root (aka the
|
|
* object itself).
|
|
*/
|
|
if (!ObjectTypeGuid)
|
|
{
|
|
DPRINT("No object type provided, updating access rights from root\n");
|
|
ReturnedObjectIndex = 0;
|
|
goto LoopAndUpdateRightsObjects;
|
|
}
|
|
|
|
/* Check if that object exists in the list */
|
|
if (SepObjectTypeGuidInList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
ObjectTypeGuid,
|
|
&ReturnedObjectIndex))
|
|
{
|
|
LoopAndUpdateRightsObjects:
|
|
/* Update the access rights of the target object */
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights);
|
|
DPRINT("Denied rights 0x%08lx of target object at index %lu\n",
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights, ReturnedObjectIndex);
|
|
|
|
/* And update the children of the target object */
|
|
for (ObjectTypeIndex = ReturnedObjectIndex + 1;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
/*
|
|
* Stop looking for children objects if we hit an object that has
|
|
* the same level as the target object or less.
|
|
*/
|
|
Level = ObjectTypeList[ObjectTypeIndex].Level;
|
|
if (Level <= ObjectTypeList[ReturnedObjectIndex].Level)
|
|
{
|
|
DPRINT("We looked for all children objects, stop looking\n");
|
|
break;
|
|
}
|
|
|
|
/* Update the access right of the child */
|
|
ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.DeniedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights);
|
|
DPRINT("Denied rights 0x%08lx of child object at index %lu\n",
|
|
ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.DeniedAccessRights, ObjectTypeIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Allows access of a target object and the children objects
|
|
* in an object type list.
|
|
*
|
|
* @param[in,out] ObjectTypeList
|
|
* A pointer to an object type list where access is to be
|
|
* allowed for the target object and its children in the
|
|
* hierarchy list.
|
|
*
|
|
* @param[in] ObjectTypeListLength
|
|
* The length of the object type list. This length represents
|
|
* the number of object elements in the list.
|
|
*
|
|
* @param[in] AccessMask
|
|
* The access mask right that is to be allowed for the object.
|
|
*
|
|
* @param[in] ObjectTypeGuid
|
|
* A pointer to a object type GUID, that identifies the object.
|
|
* This GUID is used to search for the target object in the list.
|
|
* If this parameter is set to NULL, the function will allow access
|
|
* starting from the object itself in the list (aka the root).
|
|
*/
|
|
static
|
|
VOID
|
|
SepAllowAccessObjectTypeResultList(
|
|
_Inout_ POBJECT_TYPE_LIST_INTERNAL ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ ACCESS_MASK AccessMask,
|
|
_In_opt_ PGUID ObjectTypeGuid)
|
|
{
|
|
ULONG ObjectTypeIndex;
|
|
ULONG ReturnedObjectIndex;
|
|
USHORT Level;
|
|
|
|
PAGED_CODE();
|
|
|
|
DPRINT("Access rights 0x%08lx\n", AccessMask);
|
|
|
|
/*
|
|
* Begin updating the access rights from the root object
|
|
* (see comment in SepDenyAccessObjectTypeListMaximum).
|
|
*/
|
|
if (!ObjectTypeGuid)
|
|
{
|
|
DPRINT("No object type provided, updating access rights from root\n");
|
|
ReturnedObjectIndex = 0;
|
|
goto LoopAndUpdateRightsObjects;
|
|
}
|
|
|
|
/* Check if that object exists in the list */
|
|
if (SepObjectTypeGuidInList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
ObjectTypeGuid,
|
|
&ReturnedObjectIndex))
|
|
{
|
|
LoopAndUpdateRightsObjects:
|
|
/* Update the access rights of the target object */
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights);
|
|
DPRINT("Granted rights 0x%08lx of target object at index %lu\n",
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights, ReturnedObjectIndex);
|
|
|
|
/* And update the children of the target object */
|
|
for (ObjectTypeIndex = ReturnedObjectIndex + 1;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
/*
|
|
* Stop looking for children objects if we hit an object that has
|
|
* the same level as the target object or less.
|
|
*/
|
|
Level = ObjectTypeList[ObjectTypeIndex].Level;
|
|
if (Level <= ObjectTypeList[ReturnedObjectIndex].Level)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Update the access right of the child */
|
|
ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.DeniedAccessRights);
|
|
DPRINT("Granted rights 0x%08lx of child object at index %lu\n",
|
|
ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights, ObjectTypeIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Denies access of a target object in the object type
|
|
* list. This access is denied for the whole hierarchy
|
|
* in the list.
|
|
*
|
|
* @param[in,out] ObjectTypeList
|
|
* A pointer to an object type list where access is to be
|
|
* denied for the target object. This operation applies
|
|
* for the entire hierarchy of the object type list.
|
|
*
|
|
* @param[in] ObjectTypeListLength
|
|
* The length of the object type list. This length represents
|
|
* the number of object elements in the list.
|
|
*
|
|
* @param[in] AccessMask
|
|
* The access mask right that is to be denied for the object.
|
|
*
|
|
* @param[in] ObjectTypeGuid
|
|
* A pointer to a object type GUID, that identifies the object.
|
|
* This GUID is used to search for the target object in the list.
|
|
* If this parameter is set to NULL, the function will deny access
|
|
* to the object itself in the list (aka the root).
|
|
*
|
|
* @param[out] BreakOnDeny
|
|
* A pointer returned boolean value to the caller. The function
|
|
* will return TRUE if the requested remaining right is denied
|
|
* by the ACE, otherwise it returns FALSE.
|
|
*/
|
|
static
|
|
VOID
|
|
SepDenyAccessObjectTypeList(
|
|
_Inout_ POBJECT_TYPE_LIST_INTERNAL ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ ACCESS_MASK AccessMask,
|
|
_In_opt_ PGUID ObjectTypeGuid,
|
|
_Out_opt_ PBOOLEAN BreakOnDeny)
|
|
{
|
|
ULONG ReturnedObjectIndex;
|
|
BOOLEAN MustBreak;
|
|
|
|
PAGED_CODE();
|
|
|
|
DPRINT("Access rights 0x%08lx\n", AccessMask);
|
|
|
|
/* Assume we do not want to break at first */
|
|
MustBreak = FALSE;
|
|
|
|
/*
|
|
* If no object type was supplied then tell the caller it has to break on
|
|
* searching for other ACEs if the requested remaining access right is
|
|
* denied by the deny ACE itself. Track down that denied right too.
|
|
*/
|
|
if (!ObjectTypeGuid)
|
|
{
|
|
if (ObjectTypeList[0].ObjectAccessRights.RemainingAccessRights & AccessMask)
|
|
{
|
|
DPRINT("Root object requests remaining access right that is denied 0x%08lx\n", AccessMask);
|
|
MustBreak = TRUE;
|
|
}
|
|
|
|
ObjectTypeList[0].ObjectAccessRights.DeniedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[0].ObjectAccessRights.GrantedAccessRights);
|
|
DPRINT("Denied rights of root object 0x%08lx\n", ObjectTypeList[0].ObjectAccessRights.DeniedAccessRights);
|
|
goto Quit;
|
|
}
|
|
|
|
/*
|
|
* If the object exists tell the caller it has to break down if the requested
|
|
* remaining access right is denied by the ACE. Track down the denied right too.
|
|
*/
|
|
if (SepObjectTypeGuidInList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
ObjectTypeGuid,
|
|
&ReturnedObjectIndex))
|
|
{
|
|
if (ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.RemainingAccessRights & AccessMask)
|
|
{
|
|
DPRINT("Object at index %lu requests remaining access right that is denied 0x%08lx\n", ReturnedObjectIndex, AccessMask);
|
|
MustBreak = TRUE;
|
|
}
|
|
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights);
|
|
DPRINT("Denied rights 0x%08lx of object at index %lu\n",
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights, ReturnedObjectIndex);
|
|
}
|
|
|
|
Quit:
|
|
/* Signal the caller he has to break if he wants to */
|
|
if (BreakOnDeny)
|
|
{
|
|
*BreakOnDeny = MustBreak;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Allows access of a target object in the object type
|
|
* list. This access is allowed for the whole hierarchy
|
|
* in the list.
|
|
*
|
|
* @param[in,out] ObjectTypeList
|
|
* A pointer to an object type list where access is to be
|
|
* allowed for the target object. This operation applies
|
|
* for the entire hierarchy of the object type list.
|
|
*
|
|
* @param[in] ObjectTypeListLength
|
|
* The length of the object type list. This length represents
|
|
* the number of object elements in the list.
|
|
*
|
|
* @param[in] AccessMask
|
|
* The access mask right that is to be allowed for the object.
|
|
*
|
|
* @param[in] RemoveRemainingRights
|
|
* If set to TRUE, the function will remove the remaining rights
|
|
* of a target object. It will also grant access of the said object.
|
|
* Otherwise if set to FALSE, the function will only grant access.
|
|
*
|
|
* @param[in] ObjectTypeGuid
|
|
* A pointer to a object type GUID, that identifies the object.
|
|
* This GUID is used to search for the target object in the list.
|
|
* If this parameter is set to NULL, the function will allow access
|
|
* to the object itself in the list (aka the root).
|
|
*/
|
|
static
|
|
VOID
|
|
SepAllowAccessObjectTypeList(
|
|
_Inout_ POBJECT_TYPE_LIST_INTERNAL ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ ACCESS_MASK AccessMask,
|
|
_In_ BOOLEAN RemoveRemainingRights,
|
|
_In_opt_ PGUID ObjectTypeGuid)
|
|
{
|
|
ULONG ReturnedObjectIndex;
|
|
|
|
PAGED_CODE();
|
|
|
|
DPRINT("Access rights 0x%08lx\n", AccessMask);
|
|
|
|
/*
|
|
* If no object type was supplied then remove the remaining rights
|
|
* of the object itself, the root. Track down that right to the
|
|
* granted rights as well.
|
|
*/
|
|
if (!ObjectTypeGuid)
|
|
{
|
|
if (RemoveRemainingRights)
|
|
{
|
|
ObjectTypeList[0].ObjectAccessRights.RemainingAccessRights &= ~AccessMask;
|
|
DPRINT("Remaining rights of root object 0x%08lx\n", ObjectTypeList[0].ObjectAccessRights.RemainingAccessRights);
|
|
}
|
|
|
|
ObjectTypeList[0].ObjectAccessRights.GrantedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[0].ObjectAccessRights.DeniedAccessRights);
|
|
DPRINT("Granted rights of root object 0x%08lx\n", ObjectTypeList[0].ObjectAccessRights.GrantedAccessRights);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Grant access to the object if it exists by removing the remaining
|
|
* rights. Unlike the NtAccessCheckByTypeResultList variant we do not
|
|
* care about the children of the target object beccause NtAccessCheckByType
|
|
* will either grant or deny access to the entire hierarchy of the list.
|
|
*/
|
|
if (SepObjectTypeGuidInList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
ObjectTypeGuid,
|
|
&ReturnedObjectIndex))
|
|
{
|
|
/* Remove the remaining rights of that object */
|
|
if (RemoveRemainingRights)
|
|
{
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.RemainingAccessRights &= ~AccessMask;
|
|
DPRINT("Remaining rights of object 0x%08lx at index %lu\n",
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.RemainingAccessRights, ReturnedObjectIndex);
|
|
}
|
|
|
|
/* And track it down to the granted access rights */
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights |=
|
|
(AccessMask & ~ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.DeniedAccessRights);
|
|
DPRINT("Granted rights of object 0x%08lx at index %lu\n",
|
|
ObjectTypeList[ReturnedObjectIndex].ObjectAccessRights.GrantedAccessRights, ReturnedObjectIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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] UseResultList
|
|
* This parameter is to used to determine how to perform an object type access check.
|
|
* If set to TRUE, the function will either grant or deny access to the object and sub-objects
|
|
* in the hierarchy list. If set to FALSE, the function will either grant or deny access to
|
|
* the target that will affect the entire hierarchy of the list. This parameter is used
|
|
* if the access action type is AccessCheckMaximum.
|
|
*
|
|
* @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!
|
|
*/
|
|
static
|
|
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_INTERNAL ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ BOOLEAN UseResultList,
|
|
_Inout_ PACCESS_CHECK_RIGHTS AccessCheckRights)
|
|
{
|
|
NTSTATUS Status;
|
|
PACE CurrentAce;
|
|
ULONG AceIndex;
|
|
ULONG ObjectTypeIndex;
|
|
PSID Sid;
|
|
PGUID ObjectTypeGuid;
|
|
ACCESS_MASK Access;
|
|
BOOLEAN BreakOnDeny;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* These parameters are really needed */
|
|
ASSERT(Dacl);
|
|
ASSERT(AccessToken);
|
|
|
|
/* 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(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("DeniedAccessRights 0x%08lx\n", AccessCheckRights->DeniedAccessRights);
|
|
}
|
|
}
|
|
else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
|
|
{
|
|
/* Get the SID from this ACE */
|
|
Sid = SepGetSidFromAce(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("GrantedAccessRights 0x%08lx\n", AccessCheckRights->GrantedAccessRights);
|
|
}
|
|
}
|
|
else if (CurrentAce->Header.AceType == ACCESS_DENIED_OBJECT_ACE_TYPE)
|
|
{
|
|
/* Get the SID and object type from this ACE */
|
|
Sid = SepGetSidFromAce(CurrentAce);
|
|
ObjectTypeGuid = SepGetObjectTypeGuidFromAce(CurrentAce, TRUE);
|
|
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);
|
|
}
|
|
|
|
/* If no list was passed treat this is as ACCESS_DENIED_ACE_TYPE */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
AccessCheckRights->DeniedAccessRights |= (Access & ~AccessCheckRights->GrantedAccessRights);
|
|
DPRINT("DeniedAccessRights 0x%08lx\n", AccessCheckRights->DeniedAccessRights);
|
|
}
|
|
else if (!UseResultList)
|
|
{
|
|
/*
|
|
* We have an object type list but the caller wants to deny access
|
|
* to the entire hierarchy list. Evaluate the rights of the object
|
|
* for the whole list. Ignore what the function tells us if we have
|
|
* to break on deny or not because we only want to keep track of
|
|
* denied rights.
|
|
*/
|
|
SepDenyAccessObjectTypeList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
ObjectTypeGuid,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise evaluate the access rights for each sub-object */
|
|
SepDenyAccessObjectTypeResultList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
ObjectTypeGuid);
|
|
}
|
|
}
|
|
}
|
|
else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE)
|
|
{
|
|
/* Get the SID and object type from this ACE */
|
|
Sid = SepGetSidFromAce(CurrentAce);
|
|
ObjectTypeGuid = SepGetObjectTypeGuidFromAce(CurrentAce, FALSE);
|
|
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);
|
|
}
|
|
|
|
/* If no list was passed treat this is as ACCESS_ALLOWED_ACE_TYPE */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
AccessCheckRights->GrantedAccessRights |= (Access & ~AccessCheckRights->DeniedAccessRights);
|
|
DPRINT("GrantedAccessRights 0x%08lx\n", AccessCheckRights->GrantedAccessRights);
|
|
}
|
|
else if (!UseResultList)
|
|
{
|
|
/*
|
|
* We have an object type list but the caller wants to allow access
|
|
* to the entire hierarchy list. Evaluate the rights of the object
|
|
* for the whole list.
|
|
*/
|
|
SepAllowAccessObjectTypeList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
FALSE,
|
|
ObjectTypeGuid);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise evaluate the access rights for each sub-object */
|
|
SepAllowAccessObjectTypeResultList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
ObjectTypeGuid);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT1("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;
|
|
|
|
/* Fill the remaining rights of each object in the list if we have one */
|
|
if (ObjectTypeList && (ObjectTypeListLength != 0))
|
|
{
|
|
for (ObjectTypeIndex = 0;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.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(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("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(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("RemainingAccessRights 0x%08lx Access 0x%08lx\n", AccessCheckRights->RemainingAccessRights, Access);
|
|
AccessCheckRights->RemainingAccessRights &= ~Access;
|
|
DPRINT("RemainingAccessRights 0x%08lx\n", AccessCheckRights->RemainingAccessRights);
|
|
|
|
/* Track the granted access right */
|
|
AccessCheckRights->GrantedAccessRights |= Access;
|
|
}
|
|
}
|
|
else if (CurrentAce->Header.AceType == ACCESS_DENIED_OBJECT_ACE_TYPE)
|
|
{
|
|
/* Get the SID and object type from this ACE */
|
|
Sid = SepGetSidFromAce(CurrentAce);
|
|
ObjectTypeGuid = SepGetObjectTypeGuidFromAce(CurrentAce, TRUE);
|
|
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);
|
|
}
|
|
|
|
/* If no list was passed treat this is as ACCESS_DENIED_ACE_TYPE */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
if (AccessCheckRights->RemainingAccessRights & Access)
|
|
{
|
|
DPRINT("Refuted access 0x%08lx\n", Access);
|
|
AccessCheckRights->DeniedAccessRights |= Access;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise evaluate the rights of the object for the entire list.
|
|
* The function will signal us if the caller requested a right that is
|
|
* denied by the ACE of an object in the list.
|
|
*/
|
|
SepDenyAccessObjectTypeList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
ObjectTypeGuid,
|
|
&BreakOnDeny);
|
|
|
|
/* We are acknowledged the caller requested a denied right */
|
|
if (BreakOnDeny)
|
|
{
|
|
DPRINT("Refuted access 0x%08lx\n", Access);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE)
|
|
{
|
|
/* Get the SID and object type from this ACE */
|
|
Sid = SepGetSidFromAce(CurrentAce);
|
|
ObjectTypeGuid = SepGetObjectTypeGuidFromAce(CurrentAce, FALSE);
|
|
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);
|
|
}
|
|
|
|
/* If no list was passed treat this is as ACCESS_ALLOWED_ACE_TYPE */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
/* Remove the remaining rights */
|
|
DPRINT("RemainingAccessRights 0x%08lx Access 0x%08lx\n", AccessCheckRights->RemainingAccessRights, Access);
|
|
AccessCheckRights->RemainingAccessRights &= ~Access;
|
|
DPRINT("RemainingAccessRights 0x%08lx\n", AccessCheckRights->RemainingAccessRights);
|
|
|
|
/* Track the granted access right */
|
|
AccessCheckRights->GrantedAccessRights |= Access;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise evaluate the rights of the object for the entire list */
|
|
SepAllowAccessObjectTypeList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
Access,
|
|
TRUE,
|
|
ObjectTypeGuid);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT1("Unsupported ACE type 0x%lx\n", CurrentAce->Header.AceType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We're done here */
|
|
break;
|
|
}
|
|
|
|
/* We shouldn't reach here */
|
|
DEFAULT_UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Private worker function that determines whether security access rights can be
|
|
* givento 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.
|
|
*/
|
|
static
|
|
BOOLEAN
|
|
SepAccessCheckWorker(
|
|
_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_INTERNAL 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;
|
|
ACCESS_MASK WantedRights;
|
|
ACCESS_MASK MaskDesired;
|
|
ACCESS_MASK GrantedRights = 0;
|
|
ULONG ResultListIndex;
|
|
ULONG ObjectTypeIndex;
|
|
PACL Dacl;
|
|
BOOLEAN Present;
|
|
BOOLEAN Defaulted;
|
|
NTSTATUS Status;
|
|
BOOLEAN AccessIsGranted = FALSE;
|
|
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("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;
|
|
|
|
/*
|
|
* Initialize the required rights if the caller wants to know access
|
|
* for the object and each sub-object in the list.
|
|
*/
|
|
if (UseResultList)
|
|
{
|
|
if (DesiredAccess & MAXIMUM_ALLOWED)
|
|
{
|
|
WantedRights = (DesiredAccess | PreviouslyGrantedAccess) & ~MAXIMUM_ALLOWED;
|
|
MaskDesired = ~MAXIMUM_ALLOWED;
|
|
}
|
|
else
|
|
{
|
|
WantedRights = MaskDesired = DesiredAccess | PreviouslyGrantedAccess;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* HACK: Temporary hack that checks if the caller passed an empty
|
|
* generic mapping. In such cases we cannot mask out the remaining
|
|
* access rights without a proper mapping so the only option we
|
|
* can do is to check if the client is an administrator,
|
|
* since they are powerful users.
|
|
*
|
|
* See CORE-18576 for information.
|
|
*/
|
|
if (GenericMapping->GenericRead == 0 &&
|
|
GenericMapping->GenericWrite == 0 &&
|
|
GenericMapping->GenericExecute == 0 &&
|
|
GenericMapping->GenericAll == 0)
|
|
{
|
|
if (SeTokenIsAdmin(Token))
|
|
{
|
|
/* Grant him access */
|
|
PreviouslyGrantedAccess |= RemainingAccess;
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnCommonStatus;
|
|
}
|
|
|
|
/* It's not an admin so bail out */
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_ACCESS_DENIED;
|
|
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("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.
|
|
* Or if the caller is supplying a list of object types then determine
|
|
* the rights of each object on that list.
|
|
*/
|
|
if ((DesiredAccess & MAXIMUM_ALLOWED) || UseResultList)
|
|
{
|
|
/* Perform access checks against ACEs from this DACL */
|
|
SepAnalyzeAcesFromDacl(AccessCheckMaximum,
|
|
0,
|
|
Dacl,
|
|
Token,
|
|
PrimaryAccessToken,
|
|
FALSE,
|
|
PrincipalSelfSid,
|
|
GenericMapping,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
UseResultList,
|
|
&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,
|
|
UseResultList,
|
|
&AccessCheckRights);
|
|
}
|
|
|
|
/* The caller did not provide an object type list, check access only for that object */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
/* Fail if some rights have not been granted */
|
|
RemainingAccess &= ~(MAXIMUM_ALLOWED | AccessCheckRights.GrantedAccessRights);
|
|
if (RemainingAccess != 0)
|
|
{
|
|
DPRINT("Failed to grant access rights, access denied. 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
|
|
{
|
|
DPRINT("Failed to grant access rights, access denied. PreviouslyGrantedAccess == 0 DesiredAccess = %08lx\n", DesiredAccess);
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* We are done here */
|
|
goto ReturnCommonStatus;
|
|
}
|
|
else if (!UseResultList)
|
|
{
|
|
/*
|
|
* We have a list but the caller wants to know if access can be granted
|
|
* to an object in the list. Access will either be granted or denied
|
|
* to the whole hierarchy of the list. Look for every object in the list
|
|
* that has granted access rights and collect them.
|
|
*/
|
|
for (ObjectTypeIndex = 0;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
if (ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights != 0)
|
|
{
|
|
GrantedRights |= ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights;
|
|
}
|
|
}
|
|
|
|
/* Now check if acccess can be granted */
|
|
RemainingAccess &= ~(MAXIMUM_ALLOWED | GrantedRights);
|
|
if (RemainingAccess != 0)
|
|
{
|
|
DPRINT("Failed to grant access rights to the whole object hierarchy list, access denied. 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 |= GrantedRights;
|
|
if (PreviouslyGrantedAccess != 0)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Failed to grant access rights to the whole object hierarchy list, access denied. PreviouslyGrantedAccess == 0 DesiredAccess = %08lx\n",
|
|
DesiredAccess);
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* We are done here */
|
|
goto ReturnCommonStatus;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We have a list and the caller wants to know access for each
|
|
* sub-object in the list. Report the access status and granted
|
|
* rights for the object and each sub-object in the list.
|
|
*/
|
|
for (ObjectTypeIndex = 0;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
/* Check if we have some rights */
|
|
GrantedRights = (ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.GrantedAccessRights | PreviouslyGrantedAccess) & MaskDesired;
|
|
if (GrantedRights != 0)
|
|
{
|
|
/*
|
|
* If we still have some remaining rights to grant the ultimate
|
|
* conclusion is that the caller has no access to the object itself.
|
|
*/
|
|
RemainingAccess = (~GrantedRights & WantedRights);
|
|
if (RemainingAccess != 0)
|
|
{
|
|
DPRINT("Failed to grant access rights at specific object at index %lu, access denied. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n",
|
|
ObjectTypeIndex, RemainingAccess, DesiredAccess);
|
|
AccessStatusList[ObjectTypeIndex] = STATUS_ACCESS_DENIED;
|
|
}
|
|
else
|
|
{
|
|
AccessStatusList[ObjectTypeIndex] = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No access is given */
|
|
DPRINT("Failed to grant access rights at specific object at index %lu. No access is given\n", ObjectTypeIndex);
|
|
AccessStatusList[ObjectTypeIndex] = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* Return the access rights to the caller */
|
|
GrantedAccessList[ObjectTypeIndex] = GrantedRights;
|
|
}
|
|
|
|
/*
|
|
* We have built a list of access statuses for each object but
|
|
* we still need to figure out the common status for the
|
|
* function. The same status code will be used to check if
|
|
* we should report any security debug stuff once we are done.
|
|
*/
|
|
Status = STATUS_SUCCESS;
|
|
for (ResultListIndex = 0; ResultListIndex < ObjectTypeListLength; ResultListIndex++)
|
|
{
|
|
/* There is at least one sub-object of which access cannot be granted */
|
|
if (AccessStatusList[ResultListIndex] == STATUS_ACCESS_DENIED)
|
|
{
|
|
Status = AccessStatusList[ResultListIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We are done here */
|
|
goto ReturnCommonStatus;
|
|
}
|
|
}
|
|
|
|
/* Grant rights according to the DACL */
|
|
SepAnalyzeAcesFromDacl(AccessCheckRegular,
|
|
RemainingAccess,
|
|
Dacl,
|
|
Token,
|
|
PrimaryAccessToken,
|
|
FALSE,
|
|
PrincipalSelfSid,
|
|
GenericMapping,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
UseResultList,
|
|
&AccessCheckRights);
|
|
|
|
/* The caller did not provide an object type list, check access only for that object */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
/* Fail if some rights have not been granted */
|
|
if (AccessCheckRights.RemainingAccessRights != 0)
|
|
{
|
|
DPRINT("Failed to grant access rights, access denied. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n", AccessCheckRights.RemainingAccessRights, DesiredAccess);
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto ReturnCommonStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We have an object type list, look for the object of which
|
|
* remaining rights are all granted.
|
|
*/
|
|
for (ObjectTypeIndex = 0;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
if (ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.RemainingAccessRights == 0)
|
|
{
|
|
AccessIsGranted = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!AccessIsGranted)
|
|
{
|
|
DPRINT("Failed to grant access rights to the whole object hierarchy list, access denied. DesiredAccess = 0x%08lx\n", 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,
|
|
UseResultList,
|
|
&AccessCheckRights);
|
|
|
|
/* The caller did not provide an object type list, check access only for that object */
|
|
if (!ObjectTypeList && !ObjectTypeListLength)
|
|
{
|
|
/* Fail if some rights have not been granted */
|
|
if (AccessCheckRights.RemainingAccessRights != 0)
|
|
{
|
|
DPRINT("Failed to grant access rights, access denied. RemainingAccess = 0x%08lx DesiredAccess = 0x%08lx\n", AccessCheckRights.RemainingAccessRights, DesiredAccess);
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto ReturnCommonStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We have an object type list, look for the object of which remaining
|
|
* rights are all granted. The user may have access to the requested
|
|
* object but on a restricted token case the user is only granted partial
|
|
* access. If access is denied to restricted SIDs, the bottom line is that
|
|
* access is denied to the user.
|
|
*/
|
|
AccessIsGranted = FALSE;
|
|
for (ObjectTypeIndex = 0;
|
|
ObjectTypeIndex < ObjectTypeListLength;
|
|
ObjectTypeIndex++)
|
|
{
|
|
if (ObjectTypeList[ObjectTypeIndex].ObjectAccessRights.RemainingAccessRights == 0)
|
|
{
|
|
AccessIsGranted = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!AccessIsGranted)
|
|
{
|
|
DPRINT("Failed to grant access rights to the whole object hierarchy list, access denied. DesiredAccess = 0x%08lx\n", 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)
|
|
{
|
|
DPRINT("Failed to grant access rights, access denied. 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:
|
|
if (!UseResultList)
|
|
{
|
|
*GrantedAccessList = PreviouslyGrantedAccess;
|
|
*AccessStatusList = Status;
|
|
}
|
|
|
|
#if DBG
|
|
/* Dump security debug info on access denied case */
|
|
if (Status == STATUS_ACCESS_DENIED)
|
|
{
|
|
SepDumpSdDebugInfo(SecurityDescriptor);
|
|
SepDumpTokenDebugInfo(Token);
|
|
|
|
if (ObjectTypeList && (ObjectTypeListLength != 0))
|
|
{
|
|
SepDumpAccessAndStatusList(GrantedAccessList,
|
|
AccessStatusList,
|
|
UseResultList,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength);
|
|
}
|
|
else
|
|
{
|
|
SepDumpAccessRightsStats(&AccessCheckRights);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
/**
|
|
* @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));
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Internal function that performs a security check against the
|
|
* client who requests access on a resource object. This function
|
|
* is used by access check NT system calls.
|
|
*
|
|
* @param[in] SecurityDescriptor
|
|
* A pointer to a security descriptor that identifies the security
|
|
* information of an object being accessed. This function walks
|
|
* through this descriptor for any ACLs and respective access
|
|
* rights if access can be granted.
|
|
*
|
|
* @param[in] ClientToken
|
|
* A handle to an access token, that identifies the client of which
|
|
* requests access to the target object.
|
|
*
|
|
* @param[in] PrincipalSelfSid
|
|
* A pointer to a principal self SID. This parameter can be NULL if
|
|
* the associated object being checked for access does not represent
|
|
* a principal.
|
|
*
|
|
* @param[in] DesiredAccess
|
|
* The access right bitmask where the client wants to acquire. This
|
|
* can be an OR'ed set of multiple access rights or MAXIMUM_ALLOWED
|
|
* to request all of possible access rights the target object allows.
|
|
* If only some rights were granted but not all the access is deemed
|
|
* as denied.
|
|
*
|
|
* @param[in] GenericMapping
|
|
* The generic mapping of access rights of an object type.
|
|
*
|
|
* @param[out] PrivilegeSet
|
|
* A pointer to a set of privileges that were used to perform the
|
|
* access check, returned to caller. This function will return no
|
|
* privileges (privilege count set to 0) if no privileges were used
|
|
* to accomplish the access check. This parameter must not be NULL!
|
|
*
|
|
* @param[in,out] PrivilegeSetLength
|
|
* The total length size of a set of privileges. This length represents
|
|
* the count of elements in the privilege set array.
|
|
*
|
|
* @param[in] ObjectTypeList
|
|
* A pointer to a given object type list. If this parameter is not NULL
|
|
* the function will perform an access check against the main object
|
|
* and sub-objects of this list. If this parameter is NULL and
|
|
* ObjectTypeListLength is 0, the function will perform a normal
|
|
* access check instead.
|
|
*
|
|
* @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] UseResultList
|
|
* If this parameter is set to TRUE, the function will return the GrantedAccess
|
|
* and AccessStatus parameter as arrays of granted rights and status value for
|
|
* each individual object element pointed by ObjectTypeList.
|
|
*
|
|
* @param[out] GrantedAccess
|
|
* A pointer to granted access rights, returned to the caller. If ObjectTypeList
|
|
* is not NULL this paramater is an array of granted access rights for the object
|
|
* and each individual sub-object of the list.
|
|
*
|
|
* @param[out] AccessStatus
|
|
* A pointer to a status code, returned to the caller. This status code
|
|
* represents whether access is granted or denied to the client on the
|
|
* target object. The difference between the status code of the function
|
|
* is that code indicates whether the function has successfully completed
|
|
* the access check operation. If ObjectTypeList is not NULL, this
|
|
* parameter is an array of access status for the object and each individual
|
|
* sub-object of the list.
|
|
*
|
|
* @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. STATUS_INVALID_PARAMETER is returned if the caller
|
|
* did not provide an object type list but the caller wanted to invoke an object type
|
|
* result list access check, or if the list is out of order or the list is invalid.
|
|
* A failure NTSTATUS code is returned otherwise.
|
|
*
|
|
* @remarks
|
|
* The function performs an access check against the object type list,
|
|
* if provided, depending on the UseResultList parameter. That is, if that
|
|
* parameter was set to TRUE the function will either grant or deny access
|
|
* to each individual sub-object and return an array of access status and
|
|
* granted rights for the corresponding object type list. Otherwise the function
|
|
* will grant or deny access to the object type list hierarchy as a whole.
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
SepAccessCheck(
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
_In_ HANDLE ClientToken,
|
|
_In_opt_ PSID PrincipalSelfSid,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ PGENERIC_MAPPING GenericMapping,
|
|
_Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
|
|
_Inout_ PULONG PrivilegeSetLength,
|
|
_In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ BOOLEAN UseResultList,
|
|
_Out_ PACCESS_MASK GrantedAccess,
|
|
_Out_ PNTSTATUS AccessStatus)
|
|
{
|
|
PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = NULL;
|
|
POBJECT_TYPE_LIST_INTERNAL CapturedObjectTypeList = NULL;
|
|
PSID CapturedPrincipalSelfSid = NULL;
|
|
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
ACCESS_MASK PreviouslyGrantedAccess = 0;
|
|
PPRIVILEGE_SET Privileges = NULL;
|
|
ULONG CapturedPrivilegeSetLength, RequiredPrivilegeSetLength;
|
|
ULONG ResultListIndex;
|
|
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));
|
|
|
|
/*
|
|
* Probe the access and status list based on the way
|
|
* we are going to fill data in.
|
|
*/
|
|
if (UseResultList)
|
|
{
|
|
/* Bail out on an empty list */
|
|
if (!ObjectTypeListLength)
|
|
{
|
|
DPRINT1("The object type list is empty\n");
|
|
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
ProbeForWrite(GrantedAccess, sizeof(ACCESS_MASK) * ObjectTypeListLength, sizeof(ULONG));
|
|
ProbeForWrite(AccessStatus, sizeof(NTSTATUS) * ObjectTypeListLength, sizeof(ULONG));
|
|
}
|
|
else
|
|
{
|
|
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))
|
|
{
|
|
DPRINT1("Some generic rights are not mapped\n");
|
|
return STATUS_GENERIC_NOT_MAPPED;
|
|
}
|
|
|
|
/* Reference the token */
|
|
Status = ObReferenceObjectByHandle(ClientToken,
|
|
TOKEN_QUERY,
|
|
SeTokenObjectType,
|
|
PreviousMode,
|
|
(PVOID*)&Token,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to reference token (Status 0x%08lx)\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)
|
|
{
|
|
DPRINT1("Impersonation level < SecurityIdentification\n");
|
|
ObDereferenceObject(Token);
|
|
return STATUS_BAD_IMPERSONATION_LEVEL;
|
|
}
|
|
|
|
/* Capture the object type list, the list is probed by the function itself */
|
|
Status = SeCaptureObjectTypeList(ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
PreviousMode,
|
|
&CapturedObjectTypeList);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to capture the object type list (Status 0x%08lx)\n", Status);
|
|
ObDereferenceObject(Token);
|
|
return Status;
|
|
}
|
|
|
|
/* Check for ACCESS_SYSTEM_SECURITY and WRITE_OWNER access */
|
|
Status = SePrivilegePolicyCheck(&DesiredAccess,
|
|
&PreviouslyGrantedAccess,
|
|
NULL,
|
|
Token,
|
|
&Privileges,
|
|
PreviousMode);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("SePrivilegePolicyCheck failed (Status 0x%08lx)\n", Status);
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
|
|
/*
|
|
* The caller does not have the required access to do an access check.
|
|
* Propagate the access and status for the whole hierarchy of the list
|
|
* or just to single target object.
|
|
*/
|
|
if (UseResultList)
|
|
{
|
|
for (ResultListIndex = 0; ResultListIndex < ObjectTypeListLength; ResultListIndex++)
|
|
{
|
|
AccessStatus[ResultListIndex] = Status;
|
|
GrantedAccess[ResultListIndex] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*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)
|
|
{
|
|
SeFreePrivileges(Privileges);
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
*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))
|
|
{
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
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))
|
|
{
|
|
DPRINT1("Failed to capture the Security Descriptor\n");
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
return Status;
|
|
}
|
|
|
|
/* Check the captured security descriptor */
|
|
if (CapturedSecurityDescriptor == NULL)
|
|
{
|
|
DPRINT1("Security Descriptor is NULL\n");
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
return STATUS_INVALID_SECURITY_DESCR;
|
|
}
|
|
|
|
/* Check security descriptor for valid owner and group */
|
|
if (SepGetOwnerFromDescriptor(CapturedSecurityDescriptor) == NULL ||
|
|
SepGetGroupFromDescriptor(CapturedSecurityDescriptor) == NULL)
|
|
{
|
|
DPRINT1("Security Descriptor does not have a valid group or owner\n");
|
|
SeReleaseSecurityDescriptor(CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE);
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
return STATUS_INVALID_SECURITY_DESCR;
|
|
}
|
|
|
|
/* Capture the principal self SID if we have one */
|
|
if (PrincipalSelfSid)
|
|
{
|
|
Status = SepCaptureSid(PrincipalSelfSid,
|
|
PreviousMode,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedPrincipalSelfSid);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to capture the principal self SID (Status 0x%08lx)\n", Status);
|
|
SeReleaseSecurityDescriptor(CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE);
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
ObDereferenceObject(Token);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/*
|
|
* Propagate the access and status for the whole hierarchy
|
|
* of the list or just to single target object.
|
|
*/
|
|
if (UseResultList)
|
|
{
|
|
for (ResultListIndex = 0; ResultListIndex < ObjectTypeListLength; ResultListIndex++)
|
|
{
|
|
AccessStatus[ResultListIndex] = STATUS_SUCCESS;
|
|
GrantedAccess[ResultListIndex] = PreviouslyGrantedAccess;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*GrantedAccess = PreviouslyGrantedAccess;
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Now perform the access check */
|
|
SepAccessCheckWorker(CapturedSecurityDescriptor,
|
|
Token,
|
|
&SubjectSecurityContext.PrimaryToken,
|
|
CapturedPrincipalSelfSid,
|
|
DesiredAccess,
|
|
CapturedObjectTypeList,
|
|
ObjectTypeListLength,
|
|
PreviouslyGrantedAccess,
|
|
GenericMapping,
|
|
PreviousMode,
|
|
UseResultList,
|
|
NULL,
|
|
GrantedAccess,
|
|
AccessStatus);
|
|
}
|
|
|
|
/* Release subject context and unlock the token */
|
|
SeReleaseSubjectContext(&SubjectSecurityContext);
|
|
SepReleaseTokenLock(Token);
|
|
|
|
/* Release the caputed principal self SID */
|
|
SepReleaseSid(CapturedPrincipalSelfSid,
|
|
PreviousMode,
|
|
TRUE);
|
|
|
|
/* Release the captured security descriptor */
|
|
SeReleaseSecurityDescriptor(CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE);
|
|
|
|
/* Release the object type list */
|
|
SeReleaseObjectTypeList(CapturedObjectTypeList, PreviousMode);
|
|
|
|
/* Dereference the token */
|
|
ObDereferenceObject(Token);
|
|
|
|
/* Check succeeded */
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* 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 = SepAccessCheckWorker(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 can be granted to a client
|
|
* that requests such access on an object.
|
|
*
|
|
* @remarks
|
|
* For more documentation details about the parameters and
|
|
* overall function behavior, see SepAccessCheck.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtAccessCheck(
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
_In_ HANDLE ClientToken,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ PGENERIC_MAPPING GenericMapping,
|
|
_Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
|
|
_Inout_ PULONG PrivilegeSetLength,
|
|
_Out_ PACCESS_MASK GrantedAccess,
|
|
_Out_ PNTSTATUS AccessStatus)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* Invoke the internal function to do the job */
|
|
return SepAccessCheck(SecurityDescriptor,
|
|
ClientToken,
|
|
NULL,
|
|
DesiredAccess,
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
NULL,
|
|
0,
|
|
FALSE,
|
|
GrantedAccess,
|
|
AccessStatus);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Determines whether security access can be granted to a client
|
|
* that requests such access on the object type list. The access
|
|
* is either granted or denied for the whole object hierarchy
|
|
* in the list.
|
|
*
|
|
* @remarks
|
|
* For more documentation details about the parameters and
|
|
* overall function behavior, see SepAccessCheck.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtAccessCheckByType(
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
_In_opt_ PSID PrincipalSelfSid,
|
|
_In_ HANDLE ClientToken,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ PGENERIC_MAPPING GenericMapping,
|
|
_Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
|
|
_Inout_ PULONG PrivilegeSetLength,
|
|
_Out_ PACCESS_MASK GrantedAccess,
|
|
_Out_ PNTSTATUS AccessStatus)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* Invoke the internal function to do the job */
|
|
return SepAccessCheck(SecurityDescriptor,
|
|
ClientToken,
|
|
PrincipalSelfSid,
|
|
DesiredAccess,
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
FALSE,
|
|
GrantedAccess,
|
|
AccessStatus);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Determines whether security access can be granted to a client
|
|
* that requests such access on the object type list. Unlike the
|
|
* NtAccessCheckByType variant, this function will grant or deny
|
|
* access to each individual object and sub-object in the list.
|
|
*
|
|
* @remarks
|
|
* For more documentation details about the parameters and
|
|
* overall function behavior, see SepAccessCheck.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtAccessCheckByTypeResultList(
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
_In_opt_ PSID PrincipalSelfSid,
|
|
_In_ HANDLE ClientToken,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_reads_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList,
|
|
_In_ ULONG ObjectTypeListLength,
|
|
_In_ PGENERIC_MAPPING GenericMapping,
|
|
_Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
|
|
_Inout_ PULONG PrivilegeSetLength,
|
|
_Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess,
|
|
_Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* Invoke the internal function to do the job */
|
|
return SepAccessCheck(SecurityDescriptor,
|
|
ClientToken,
|
|
PrincipalSelfSid,
|
|
DesiredAccess,
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
TRUE,
|
|
GrantedAccess,
|
|
AccessStatus);
|
|
}
|
|
|
|
/* EOF */
|