You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4327 lines
126 KiB
4327 lines
126 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Accessck.c
|
|
|
|
Abstract:
|
|
|
|
This Module implements the access check procedures. Both NtAccessCheck
|
|
and SeAccessCheck check to is if a user (denoted by an input token) can
|
|
be granted the desired access rights to object protected by a security
|
|
descriptor and an optional object owner. Both procedures use a common
|
|
local procedure to do the test.
|
|
|
|
Author:
|
|
|
|
Robert Reichel (RobertRe) 11-30-90
|
|
|
|
Environment:
|
|
|
|
Kernel Mode
|
|
|
|
Revision History:
|
|
|
|
Richard Ward (RichardW) 14-Apr-92 Changed ACE_HEADER
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#pragma hdrstop
|
|
|
|
#include <sertlp.h>
|
|
|
|
|
|
//
|
|
// Define the local macros and procedure for this module
|
|
//
|
|
|
|
#if DBG
|
|
|
|
extern BOOLEAN SepDumpSD;
|
|
extern BOOLEAN SepDumpToken;
|
|
BOOLEAN SepShowAccessFail;
|
|
|
|
#endif // DBG
|
|
|
|
|
|
|
|
VOID
|
|
SepUpdateParentTypeList (
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN ULONG StartIndex
|
|
);
|
|
|
|
typedef enum {
|
|
UpdateRemaining,
|
|
UpdateCurrentGranted,
|
|
UpdateCurrentDenied
|
|
} ACCESS_MASK_FIELD_TO_UPDATE;
|
|
|
|
VOID
|
|
SepAddAccessTypeList (
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN ULONG StartIndex,
|
|
IN ACCESS_MASK AccessMask,
|
|
IN ACCESS_MASK_FIELD_TO_UPDATE FieldToUpdate
|
|
);
|
|
|
|
NTSTATUS
|
|
SeAccessCheckByType (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PSID PrincipalSelfSid,
|
|
IN HANDLE ClientToken,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PPRIVILEGE_SET PrivilegeSet,
|
|
IN OUT PULONG PrivilegeSetLength,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus,
|
|
IN BOOLEAN ReturnResultList
|
|
);
|
|
|
|
VOID
|
|
SepMaximumAccessCheck(
|
|
IN PTOKEN EToken,
|
|
IN PTOKEN PrimaryToken,
|
|
IN PACL Dacl,
|
|
IN PSID PrincipalSelfSid,
|
|
IN ULONG LocalTypeListLength,
|
|
IN PIOBJECT_TYPE_LIST LocalTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN BOOLEAN Restricted
|
|
);
|
|
|
|
VOID
|
|
SepNormalAccessCheck(
|
|
IN ACCESS_MASK Remaining,
|
|
IN PTOKEN EToken,
|
|
IN PTOKEN PrimaryToken,
|
|
IN PACL Dacl,
|
|
IN PSID PrincipalSelfSid,
|
|
IN ULONG LocalTypeListLength,
|
|
IN PIOBJECT_TYPE_LIST LocalTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN BOOLEAN Restricted
|
|
);
|
|
|
|
BOOLEAN
|
|
SepSidInTokenEx (
|
|
IN PACCESS_TOKEN AToken,
|
|
IN PSID PrincipalSelfSid,
|
|
IN PSID Sid,
|
|
IN BOOLEAN DenyAce,
|
|
IN BOOLEAN Restricted
|
|
);
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,SeCaptureObjectTypeList)
|
|
#pragma alloc_text(PAGE,SeFreeCapturedObjectTypeList)
|
|
#pragma alloc_text(PAGE,SepUpdateParentTypeList)
|
|
#pragma alloc_text(PAGE,SepObjectInTypeList)
|
|
#pragma alloc_text(PAGE,SepAddAccessTypeList)
|
|
#pragma alloc_text(PAGE,SepSidInToken)
|
|
#pragma alloc_text(PAGE,SepSidInTokenEx)
|
|
#pragma alloc_text(PAGE,SepAccessCheck)
|
|
#pragma alloc_text(PAGE,NtAccessCheck)
|
|
#pragma alloc_text(PAGE,NtAccessCheckByType)
|
|
#pragma alloc_text(PAGE,NtAccessCheckByTypeResultList)
|
|
#pragma alloc_text(PAGE,SeAccessCheckByType)
|
|
#pragma alloc_text(PAGE,SeFreePrivileges)
|
|
#pragma alloc_text(PAGE,SeAccessCheck)
|
|
#pragma alloc_text(PAGE,SePrivilegePolicyCheck)
|
|
#pragma alloc_text(PAGE,SepTokenIsOwner)
|
|
#pragma alloc_text(PAGE,SeFastTraverseCheck)
|
|
#pragma alloc_text(PAGE,SepMaximumAccessCheck)
|
|
#pragma alloc_text(PAGE,SepNormalAccessCheck)
|
|
#pragma alloc_text(PAGE,SeMaximumAuditMask)
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
SeCaptureObjectTypeList (
|
|
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN KPROCESSOR_MODE RequestorMode,
|
|
OUT PIOBJECT_TYPE_LIST *CapturedObjectTypeList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine probes and captures a copy of any object type list
|
|
that might have been provided via the ObjectTypeList argument.
|
|
|
|
The object type list is converted to the internal form that explicitly
|
|
specifies the hierarchical relationship between the entries.
|
|
|
|
The object typs list is validated to ensure a valid hierarchical
|
|
relationship is represented.
|
|
|
|
Arguments:
|
|
|
|
ObjectTypeList - The object type list from which the type list
|
|
information is to be retrieved.
|
|
|
|
ObjectTypeListLength - Number of elements in ObjectTypeList
|
|
|
|
RequestorMode - Indicates the processor mode by which the access
|
|
is being requested.
|
|
|
|
CapturedObjectTypeList - Receives the captured type list which
|
|
must be freed using SeFreeCapturedObjectTypeList().
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS indicates no exceptions were encountered.
|
|
|
|
Any access violations encountered will be returned.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG i;
|
|
PIOBJECT_TYPE_LIST LocalTypeList = NULL;
|
|
|
|
ULONG Levels[ACCESS_MAX_LEVEL+1];
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Set default return
|
|
//
|
|
|
|
*CapturedObjectTypeList = NULL;
|
|
|
|
if (RequestorMode != UserMode) {
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
try {
|
|
|
|
if ( ObjectTypeListLength == 0 ) {
|
|
|
|
// Drop through
|
|
|
|
} else if ( !ARGUMENT_PRESENT(ObjectTypeList) ) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
if ( !IsValidElementCount( ObjectTypeListLength, IOBJECT_TYPE_LIST ) )
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER ;
|
|
|
|
//
|
|
// No more to do, get out of the try statement:
|
|
//
|
|
|
|
leave ;
|
|
}
|
|
|
|
ProbeForRead( ObjectTypeList,
|
|
sizeof(OBJECT_TYPE_LIST) * ObjectTypeListLength,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
//
|
|
// Allocate a buffer to copy into.
|
|
//
|
|
|
|
LocalTypeList = ExAllocatePoolWithTag( PagedPool, sizeof(IOBJECT_TYPE_LIST) * ObjectTypeListLength, 'tOeS' );
|
|
|
|
if ( LocalTypeList == NULL ) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
//
|
|
// Copy the callers structure to the local structure.
|
|
//
|
|
|
|
} else {
|
|
GUID * CapturedObjectType;
|
|
for ( i=0; i<ObjectTypeListLength; i++ ) {
|
|
USHORT CurrentLevel;
|
|
|
|
//
|
|
// Limit ourselves
|
|
//
|
|
CurrentLevel = ObjectTypeList[i].Level;
|
|
if ( CurrentLevel > ACCESS_MAX_LEVEL ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Copy data the caller passed in
|
|
//
|
|
LocalTypeList[i].Level = CurrentLevel;
|
|
LocalTypeList[i].Flags = 0;
|
|
CapturedObjectType = ObjectTypeList[i].ObjectType;
|
|
ProbeForReadSmallStructure(
|
|
CapturedObjectType,
|
|
sizeof(GUID),
|
|
sizeof(ULONG)
|
|
);
|
|
LocalTypeList[i].ObjectType = *CapturedObjectType;
|
|
LocalTypeList[i].Remaining = 0;
|
|
LocalTypeList[i].CurrentGranted = 0;
|
|
LocalTypeList[i].CurrentDenied = 0;
|
|
|
|
//
|
|
// Ensure that the level number is consistent with the
|
|
// level number of the previous entry.
|
|
//
|
|
|
|
if ( i == 0 ) {
|
|
if ( CurrentLevel != 0 ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The previous entry is either:
|
|
// my immediate parent,
|
|
// my sibling, or
|
|
// the child (or grandchild, etc.) of my sibling.
|
|
//
|
|
if ( CurrentLevel > LocalTypeList[i-1].Level + 1 ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Don't support two roots.
|
|
//
|
|
if ( CurrentLevel == 0 ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If the above rules are maintained,
|
|
// then my parent object is the last object seen that
|
|
// has a level one less than mine.
|
|
//
|
|
|
|
if ( CurrentLevel == 0 ) {
|
|
LocalTypeList[i].ParentIndex = -1;
|
|
} else {
|
|
LocalTypeList[i].ParentIndex = Levels[CurrentLevel-1];
|
|
}
|
|
|
|
//
|
|
// Save this obect as the last object seen at this level.
|
|
//
|
|
|
|
Levels[CurrentLevel] = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end_if
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
Status = GetExceptionCode();
|
|
|
|
} // end_try
|
|
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
*CapturedObjectTypeList = LocalTypeList;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we captured any proxy data, we need to free it now.
|
|
//
|
|
|
|
if ( LocalTypeList != NULL ) {
|
|
ExFreePool( LocalTypeList );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
VOID
|
|
SeFreeCapturedObjectTypeList(
|
|
IN PVOID ObjectTypeList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees the data associated with a captured ObjectTypeList
|
|
structure.
|
|
|
|
Arguments:
|
|
|
|
ObjectTypeList - Points to a captured object type list structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if ( ObjectTypeList != NULL ) {
|
|
ExFreePool( ObjectTypeList );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOLEAN
|
|
SepObjectInTypeList (
|
|
IN GUID *ObjectType,
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
OUT PULONG ReturnedIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches an ObjectTypeList to determine if the specified
|
|
object type is in the list.
|
|
|
|
Arguments:
|
|
|
|
ObjectType - Object Type to search for.
|
|
|
|
ObjectTypeList - The object type list to search.
|
|
|
|
ObjectTypeListLength - Number of elements in ObjectTypeList
|
|
|
|
ReturnedIndex - Index to the element ObjectType was found in
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE: ObjectType was found in list.
|
|
FALSE: ObjectType was not found in list.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Index;
|
|
GUID *LocalObjectType;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( sizeof(GUID) == sizeof(ULONG) * 4 );
|
|
for ( Index=0; Index<ObjectTypeListLength; Index++ ) {
|
|
|
|
LocalObjectType = &ObjectTypeList[Index].ObjectType;
|
|
|
|
if ( RtlpIsEqualGuid( ObjectType, LocalObjectType ) ) {
|
|
*ReturnedIndex = Index;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
SepUpdateParentTypeList (
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN ULONG StartIndex
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update the Access fields of the parent object of the specified object.
|
|
|
|
|
|
The "remaining" field of a parent object is the logical OR of
|
|
the remaining field of all of its children.
|
|
|
|
The CurrentGranted field of the parent is the collection of bits
|
|
granted to every one of its children..
|
|
|
|
The CurrentDenied fields of the parent is the logical OR of
|
|
the bits denied to any of its children.
|
|
|
|
This routine takes an index to one of the children and updates the
|
|
remaining field of the parent (and grandparents recursively).
|
|
|
|
Arguments:
|
|
|
|
ObjectTypeList - The object type list to update.
|
|
|
|
ObjectTypeListLength - Number of elements in ObjectTypeList
|
|
|
|
StartIndex - Index to the "child" element whose parents are to be updated.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Index;
|
|
ULONG ParentIndex;
|
|
ULONG Level;
|
|
ACCESS_MASK NewRemaining = 0;
|
|
ACCESS_MASK NewCurrentGranted = 0xFFFFFFFF;
|
|
ACCESS_MASK NewCurrentDenied = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If the target node is at the root,
|
|
// we're all done.
|
|
//
|
|
|
|
if ( ObjectTypeList[StartIndex].ParentIndex == -1 ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get the index to the parent that needs updating and the level of
|
|
// the siblings.
|
|
//
|
|
|
|
ParentIndex = ObjectTypeList[StartIndex].ParentIndex;
|
|
Level = ObjectTypeList[StartIndex].Level;
|
|
|
|
//
|
|
// Loop through all the children.
|
|
//
|
|
|
|
for ( Index=ParentIndex+1; Index<ObjectTypeListLength; Index++ ) {
|
|
|
|
//
|
|
// By definition, the children of an object are all those entries
|
|
// immediately following the target. The list of children (or
|
|
// grandchildren) stops as soon as we reach an entry the has the
|
|
// same level as the target (a sibling) or lower than the target
|
|
// (an uncle).
|
|
//
|
|
|
|
if ( ObjectTypeList[Index].Level <= ObjectTypeList[ParentIndex].Level ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Only handle direct children of the parent.
|
|
//
|
|
|
|
if ( ObjectTypeList[Index].Level != Level ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Compute the new bits for the parent.
|
|
//
|
|
|
|
NewRemaining |= ObjectTypeList[Index].Remaining;
|
|
NewCurrentGranted &= ObjectTypeList[Index].CurrentGranted;
|
|
NewCurrentDenied |= ObjectTypeList[Index].CurrentDenied;
|
|
|
|
}
|
|
|
|
//
|
|
// If we've not changed the access to the parent,
|
|
// we're done.
|
|
//
|
|
|
|
if ( NewRemaining == ObjectTypeList[ParentIndex].Remaining &&
|
|
NewCurrentGranted == ObjectTypeList[ParentIndex].CurrentGranted &&
|
|
NewCurrentDenied == ObjectTypeList[ParentIndex].CurrentDenied ) {
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Change the parent.
|
|
//
|
|
|
|
ObjectTypeList[ParentIndex].Remaining = NewRemaining;
|
|
ObjectTypeList[ParentIndex].CurrentGranted = NewCurrentGranted;
|
|
ObjectTypeList[ParentIndex].CurrentDenied = NewCurrentDenied;
|
|
|
|
//
|
|
// Go update the grand parents.
|
|
//
|
|
|
|
SepUpdateParentTypeList( ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
ParentIndex );
|
|
}
|
|
|
|
|
|
VOID
|
|
SepAddAccessTypeList (
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN ULONG StartIndex,
|
|
IN ACCESS_MASK AccessMask,
|
|
IN ACCESS_MASK_FIELD_TO_UPDATE FieldToUpdate
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine grants the specified AccessMask to all of the objects that
|
|
are descendents of the object specified by StartIndex.
|
|
|
|
The Access fields of the parent objects are also recomputed as needed.
|
|
|
|
For example, if an ACE granting access to a Property Set is found,
|
|
that access is granted to all the Properties in the Property Set.
|
|
|
|
Arguments:
|
|
|
|
ObjectTypeList - The object type list to update.
|
|
|
|
ObjectTypeListLength - Number of elements in ObjectTypeList
|
|
|
|
StartIndex - Index to the target element to update.
|
|
|
|
AccessMask - Mask of access to grant to the target element and
|
|
all of its decendents
|
|
|
|
FieldToUpdate - Indicate which fields to update in object type list
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Index;
|
|
ACCESS_MASK OldRemaining;
|
|
ACCESS_MASK OldCurrentGranted;
|
|
ACCESS_MASK OldCurrentDenied;
|
|
BOOLEAN AvoidParent = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Update the requested field.
|
|
//
|
|
// Always handle the target entry.
|
|
//
|
|
// If we've not actually changed the bits,
|
|
// early out.
|
|
//
|
|
|
|
switch (FieldToUpdate ) {
|
|
case UpdateRemaining:
|
|
|
|
OldRemaining = ObjectTypeList[StartIndex].Remaining;
|
|
ObjectTypeList[StartIndex].Remaining = OldRemaining & ~AccessMask;
|
|
|
|
if ( OldRemaining == ObjectTypeList[StartIndex].Remaining ) {
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case UpdateCurrentGranted:
|
|
|
|
OldCurrentGranted = ObjectTypeList[StartIndex].CurrentGranted;
|
|
ObjectTypeList[StartIndex].CurrentGranted |=
|
|
AccessMask & ~ObjectTypeList[StartIndex].CurrentDenied;
|
|
|
|
if ( OldCurrentGranted == ObjectTypeList[StartIndex].CurrentGranted ) {
|
|
//
|
|
// We can't simply return here.
|
|
// We have to visit our children. Consider the case where there
|
|
// was a previous deny ACE on a child. That deny would have
|
|
// propagated up the tree to this entry. However, this allow ACE
|
|
// needs to be added all of the children that haven't been
|
|
// explictly denied.
|
|
//
|
|
AvoidParent = TRUE;
|
|
}
|
|
break;
|
|
|
|
case UpdateCurrentDenied:
|
|
|
|
OldCurrentDenied = ObjectTypeList[StartIndex].CurrentDenied;
|
|
ObjectTypeList[StartIndex].CurrentDenied |=
|
|
AccessMask & ~ObjectTypeList[StartIndex].CurrentGranted;
|
|
|
|
if ( OldCurrentDenied == ObjectTypeList[StartIndex].CurrentDenied ) {
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Go update parent of the target.
|
|
//
|
|
|
|
if ( !AvoidParent ) {
|
|
SepUpdateParentTypeList( ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
StartIndex );
|
|
}
|
|
|
|
//
|
|
// Loop handling all children of the target.
|
|
//
|
|
|
|
for ( Index=StartIndex+1; Index<ObjectTypeListLength; Index++ ) {
|
|
|
|
//
|
|
// By definition, the children of an object are all those entries
|
|
// immediately following the target. The list of children (or
|
|
// grandchildren) stops as soon as we reach an entry the has the
|
|
// same level as the target (a sibling) or lower than the target
|
|
// (an uncle).
|
|
//
|
|
|
|
if ( ObjectTypeList[Index].Level <= ObjectTypeList[StartIndex].Level ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Grant access to the children
|
|
//
|
|
|
|
switch (FieldToUpdate) {
|
|
case UpdateRemaining:
|
|
|
|
ObjectTypeList[Index].Remaining &= ~AccessMask;
|
|
break;
|
|
|
|
case UpdateCurrentGranted:
|
|
|
|
ObjectTypeList[Index].CurrentGranted |=
|
|
AccessMask & ~ObjectTypeList[Index].CurrentDenied;
|
|
break;
|
|
|
|
case UpdateCurrentDenied:
|
|
|
|
ObjectTypeList[Index].CurrentDenied |=
|
|
AccessMask & ~ObjectTypeList[Index].CurrentGranted;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOLEAN
|
|
SepSidInToken (
|
|
IN PACCESS_TOKEN AToken,
|
|
IN PSID PrincipalSelfSid,
|
|
IN PSID Sid,
|
|
IN BOOLEAN DenyAce
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks to see if a given SID is in the given token.
|
|
|
|
N.B. The code to compute the length of a SID and test for equality
|
|
is duplicated from the security runtime since this is such a
|
|
frequently used routine.
|
|
|
|
Arguments:
|
|
|
|
Token - Pointer to the token to be examined
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
|
|
Sid - Pointer to the SID of interest
|
|
|
|
Return Value:
|
|
|
|
A value of TRUE indicates that the SID is in the token, FALSE
|
|
otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULONG i;
|
|
PISID MatchSid;
|
|
ULONG SidLength;
|
|
PTOKEN Token;
|
|
PSID_AND_ATTRIBUTES TokenSid;
|
|
ULONG UserAndGroupCount;
|
|
USHORT TargetShort;
|
|
|
|
C_ASSERT (FIELD_OFFSET (SID, Revision) + sizeof (((SID *)Sid)->Revision) == FIELD_OFFSET (SID, SubAuthorityCount));
|
|
C_ASSERT (sizeof (((SID *)Sid)->Revision) + sizeof (((SID *)Sid)->SubAuthorityCount) == sizeof (USHORT));
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
#if DBG
|
|
|
|
SepDumpTokenInfo(AToken);
|
|
|
|
#endif
|
|
|
|
//
|
|
// If Sid is the constant PrincipalSelfSid,
|
|
// replace it with the passed in PrincipalSelfSid.
|
|
//
|
|
|
|
if ( PrincipalSelfSid != NULL &&
|
|
RtlEqualSid( SePrincipalSelfSid, Sid ) ) {
|
|
Sid = PrincipalSelfSid;
|
|
}
|
|
|
|
//
|
|
// Get the length of the source SID since this only needs to be computed
|
|
// once.
|
|
//
|
|
SidLength = 8 + (4 * ((PISID)Sid)->SubAuthorityCount);
|
|
|
|
//
|
|
// To speed up processing we compare the sub authority count and the revision at the same time.
|
|
//
|
|
TargetShort = *(USHORT *)&((PISID)Sid)->Revision;
|
|
|
|
//
|
|
// Get address of user/group array and number of user/groups.
|
|
//
|
|
|
|
Token = (PTOKEN)AToken;
|
|
TokenSid = Token->UserAndGroups;
|
|
UserAndGroupCount = Token->UserAndGroupCount;
|
|
|
|
//
|
|
// Scan through the user/groups and attempt to find a match with the
|
|
// specified SID.
|
|
//
|
|
|
|
for (i = 0 ; i < UserAndGroupCount ; i += 1) {
|
|
MatchSid = (PISID)TokenSid->Sid;
|
|
|
|
//
|
|
// If revision and sub authority count matches, then compare the SIDs
|
|
// for equality.
|
|
//
|
|
|
|
if (*(USHORT *) &MatchSid->Revision == TargetShort) {
|
|
if (RtlEqualMemory(Sid, MatchSid, SidLength)) {
|
|
|
|
//
|
|
// If this is the first one in the list, then it is the User,
|
|
// and return success immediately.
|
|
//
|
|
// If this is not the first one, then it represents a group,
|
|
// and we must make sure the group is currently enabled before
|
|
// we can say that the group is "in" the token.
|
|
//
|
|
|
|
if ((i == 0) || (TokenSid->Attributes & SE_GROUP_ENABLED) ||
|
|
(DenyAce && (TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) {
|
|
return TRUE;
|
|
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
TokenSid += 1;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
SepSidInTokenEx (
|
|
IN PACCESS_TOKEN AToken,
|
|
IN PSID PrincipalSelfSid,
|
|
IN PSID Sid,
|
|
IN BOOLEAN DenyAce,
|
|
IN BOOLEAN Restricted
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks to see if a given restricted SID is in the given token.
|
|
|
|
N.B. The code to compute the length of a SID and test for equality
|
|
is duplicated from the security runtime since this is such a
|
|
frequently used routine.
|
|
|
|
Arguments:
|
|
|
|
Token - Pointer to the token to be examined
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
|
|
Sid - Pointer to the SID of interest
|
|
|
|
DenyAce - The ACE being evaluated is a DENY or ACCESS DENIED ace
|
|
|
|
Restricted - The access check being performed uses the restricted sids.
|
|
|
|
Return Value:
|
|
|
|
A value of TRUE indicates that the SID is in the token, FALSE
|
|
otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULONG i;
|
|
PISID MatchSid;
|
|
ULONG SidLength;
|
|
PTOKEN Token;
|
|
PSID_AND_ATTRIBUTES TokenSid;
|
|
ULONG UserAndGroupCount;
|
|
USHORT TargetShort;
|
|
|
|
C_ASSERT (FIELD_OFFSET (SID, Revision) + sizeof (((SID *)Sid)->Revision) == FIELD_OFFSET (SID, SubAuthorityCount));
|
|
C_ASSERT (sizeof (((SID *)Sid)->Revision) + sizeof (((SID *)Sid)->SubAuthorityCount) == sizeof (USHORT));
|
|
|
|
PAGED_CODE();
|
|
|
|
#if DBG
|
|
|
|
SepDumpTokenInfo(AToken);
|
|
|
|
#endif
|
|
|
|
//
|
|
// If Sid is the constant PrincipalSelfSid,
|
|
// replace it with the passed in PrincipalSelfSid.
|
|
//
|
|
|
|
if ( PrincipalSelfSid != NULL &&
|
|
RtlEqualSid( SePrincipalSelfSid, Sid ) ) {
|
|
Sid = PrincipalSelfSid;
|
|
}
|
|
|
|
//
|
|
// Get the length of the source SID since this only needs to be computed
|
|
// once.
|
|
//
|
|
|
|
//
|
|
// Get the length of the source SID since this only needs to be computed
|
|
// once.
|
|
//
|
|
SidLength = 8 + (4 * ((PISID)Sid)->SubAuthorityCount);
|
|
|
|
//
|
|
// To speed up processing we compare the sub authority count and the revision at the same time.
|
|
//
|
|
TargetShort = *(USHORT *)&((PISID)Sid)->Revision;
|
|
|
|
//
|
|
// Get address of user/group array and number of user/groups.
|
|
//
|
|
|
|
Token = (PTOKEN)AToken;
|
|
if (Restricted) {
|
|
TokenSid = Token->RestrictedSids;
|
|
UserAndGroupCount = Token->RestrictedSidCount;
|
|
} else {
|
|
TokenSid = Token->UserAndGroups;
|
|
UserAndGroupCount = Token->UserAndGroupCount;
|
|
}
|
|
|
|
//
|
|
// Scan through the user/groups and attempt to find a match with the
|
|
// specified SID.
|
|
//
|
|
|
|
for (i = 0; i < UserAndGroupCount ; i += 1) {
|
|
MatchSid = (PISID)TokenSid->Sid;
|
|
|
|
//
|
|
// If the SID revision and length matches, then compare the SIDs
|
|
// for equality.
|
|
//
|
|
|
|
if (*(USHORT *) &MatchSid->Revision == TargetShort) {
|
|
if (RtlEqualMemory(Sid, MatchSid, SidLength)) {
|
|
|
|
//
|
|
// If this is the first one in the list and not deny-only it
|
|
// is not a restricted token then it is the User, and return
|
|
// success immediately.
|
|
//
|
|
// If this is not the first one, then it represents a group,
|
|
// and we must make sure the group is currently enabled before
|
|
// we can say that the group is "in" the token.
|
|
//
|
|
|
|
if ((!Restricted && (i == 0) && ((TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY) == 0)) ||
|
|
(TokenSid->Attributes & SE_GROUP_ENABLED) ||
|
|
(DenyAce && (TokenSid->Attributes & SE_GROUP_USE_FOR_DENY_ONLY))) {
|
|
return TRUE;
|
|
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
TokenSid += 1;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID
|
|
SepMaximumAccessCheck(
|
|
IN PTOKEN EToken,
|
|
IN PTOKEN PrimaryToken,
|
|
IN PACL Dacl,
|
|
IN PSID PrincipalSelfSid,
|
|
IN ULONG LocalTypeListLength,
|
|
IN PIOBJECT_TYPE_LIST LocalTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN BOOLEAN Restricted
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does an access check for maximum allowed or with a result list. If the
|
|
Restricted flag is set, it is done for a restricted token. The sids
|
|
checked are the restricted sids, not the users and groups. The current
|
|
granted access is stored in the Remaining access and then another access
|
|
check is run.
|
|
|
|
Arguments:
|
|
|
|
EToken - Effective token of caller.
|
|
|
|
PrimaryToken - Process token of calling process
|
|
|
|
Dacl - ACL to check
|
|
|
|
PrincipalSelfSid - Sid to use in replacing the well-known self sid
|
|
|
|
LocalTypeListLength - Length of list of types.
|
|
|
|
LocalTypeList - List of types.
|
|
|
|
ObjectTypeList - Length of caller-supplied list of object types.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG i,j;
|
|
PVOID Ace;
|
|
ULONG AceCount;
|
|
ULONG Index;
|
|
ULONG ResultListIndex;
|
|
|
|
//
|
|
// The remaining bits are the granted bits for each object type on a
|
|
// restricted check
|
|
//
|
|
|
|
if ( Restricted ) {
|
|
for ( j=0; j<LocalTypeListLength; j++ ) {
|
|
LocalTypeList[j].Remaining = LocalTypeList[j].CurrentGranted;
|
|
LocalTypeList[j].CurrentGranted = 0;
|
|
}
|
|
}
|
|
|
|
|
|
AceCount = Dacl->AceCount;
|
|
|
|
//
|
|
// granted == NUL
|
|
// denied == NUL
|
|
//
|
|
// for each ACE
|
|
//
|
|
// if grant
|
|
// for each SID
|
|
// if SID match, then add all that is not denied to grant mask
|
|
//
|
|
// if deny
|
|
// for each SID
|
|
// if SID match, then add all that is not granted to deny mask
|
|
//
|
|
|
|
for ( i = 0, Ace = FirstAce( Dacl ) ;
|
|
i < AceCount ;
|
|
i++, Ace = NextAce( Ace )
|
|
) {
|
|
|
|
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
|
|
|
|
if (SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart, FALSE, Restricted )) {
|
|
|
|
//
|
|
// Only grant access types from this mask that have
|
|
// not already been denied
|
|
//
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->CurrentGranted |=
|
|
(((PACCESS_ALLOWED_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied);
|
|
} else {
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateCurrentGranted );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Allowed ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If no object type is in the ACE,
|
|
// treat this as an ACCESS_ALLOWED_ACE.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
|
|
if ( ObjectTypeInAce == NULL ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->CurrentGranted |=
|
|
(((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied);
|
|
} else {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateCurrentGranted );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If no object type list was passed,
|
|
// don't grant access to anyone.
|
|
//
|
|
|
|
} else if ( ObjectTypeListLength == 0 ) {
|
|
|
|
// Drop through
|
|
|
|
|
|
//
|
|
// If an object type is in the ACE,
|
|
// Find it in the LocalTypeList before using the ACE.
|
|
//
|
|
} else {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) {
|
|
|
|
if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
Index, // Element already updated
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateCurrentGranted );
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) {
|
|
|
|
//
|
|
// If we're impersonating, EToken is set to the Client, and if we're not,
|
|
// EToken is set to the Primary. According to the DSA architecture, if
|
|
// we're asked to evaluate a compound ACE and we're not impersonating,
|
|
// pretend we are impersonating ourselves. So we can just use the EToken
|
|
// for the client token, since it's already set to the right thing.
|
|
//
|
|
|
|
|
|
if ( SepSidInTokenEx(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE, Restricted) &&
|
|
SepSidInTokenEx(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE, FALSE)
|
|
) {
|
|
|
|
//
|
|
// Only grant access types from this mask that have
|
|
// not already been denied
|
|
//
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->CurrentGranted |=
|
|
(((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask & ~LocalTypeList->CurrentDenied);
|
|
} else {
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateCurrentGranted );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE, Restricted )) {
|
|
|
|
//
|
|
// Only deny access types from this mask that have
|
|
// not already been granted
|
|
//
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->CurrentDenied |=
|
|
(((PACCESS_DENIED_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted);
|
|
} else {
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_DENIED_ACE)Ace)->Mask, // Access denied
|
|
UpdateCurrentDenied );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Denied ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE, Restricted ) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If there is no object type in the ACE,
|
|
// or if the caller didn't specify an object type list,
|
|
// apply this deny ACE to the entire object.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
|
|
if ( ObjectTypeInAce == NULL ) {
|
|
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->CurrentDenied |=
|
|
(((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted);
|
|
} else {
|
|
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0,
|
|
((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask, // Access denied
|
|
UpdateCurrentDenied );
|
|
}
|
|
//
|
|
// If no object type list was passed,
|
|
// don't grant access to anyone.
|
|
//
|
|
|
|
} else if ( ObjectTypeListLength == 0 ) {
|
|
|
|
LocalTypeList->CurrentDenied |=
|
|
(((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask & ~LocalTypeList->CurrentGranted);
|
|
|
|
|
|
//
|
|
// If an object type is in the ACE,
|
|
// Find it in the LocalTypeList before using the ACE.
|
|
//
|
|
|
|
} else if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
Index, // Element to update
|
|
((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask, // Access denied
|
|
UpdateCurrentDenied );
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
SepNormalAccessCheck(
|
|
IN ACCESS_MASK Remaining,
|
|
IN PTOKEN EToken,
|
|
IN PTOKEN PrimaryToken,
|
|
IN PACL Dacl,
|
|
IN PSID PrincipalSelfSid,
|
|
IN ULONG LocalTypeListLength,
|
|
IN PIOBJECT_TYPE_LIST LocalTypeList,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN BOOLEAN Restricted
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does an access check when the caller isn't asking for MAXIMUM_ALLOWED or
|
|
a type result list. If the Restricted flag is set, the sids checked are
|
|
the restricted sids, not the users and groups. The Remaining field is
|
|
reset to the original remaining value and then another access check is run.
|
|
|
|
Arguments:
|
|
|
|
Remaining - Remaining access desired after special checks
|
|
|
|
EToken - Effective token of caller.
|
|
|
|
PrimaryToken - Process token of calling process
|
|
|
|
Dacl - ACL to check
|
|
|
|
PrincipalSelfSid - Sid to use in replacing the well-known self sid
|
|
|
|
LocalTypeListLength - Length of list of types.
|
|
|
|
LocalTypeList - List of types.
|
|
|
|
ObjectTypeList - Length of caller-supplied list of object types.
|
|
|
|
Restricted - Use the restricted sids for the access check.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
ULONG i,j;
|
|
PVOID Ace;
|
|
ULONG AceCount;
|
|
ULONG Index;
|
|
|
|
AceCount = Dacl->AceCount;
|
|
|
|
//
|
|
// The remaining bits are "remaining" at all levels
|
|
//
|
|
|
|
for ( j=0; j<LocalTypeListLength; j++ ) {
|
|
LocalTypeList[j].Remaining = Remaining;
|
|
}
|
|
|
|
//
|
|
// Process the DACL handling individual access bits.
|
|
//
|
|
|
|
for ( i = 0, Ace = FirstAce( Dacl ) ;
|
|
( i < AceCount ) && ( LocalTypeList->Remaining != 0 ) ;
|
|
i++, Ace = NextAce( Ace ) ) {
|
|
|
|
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
|
|
|
|
//
|
|
// Handle an Access Allowed ACE
|
|
//
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE )Ace)->SidStart, FALSE, Restricted ) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Allowed ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If no object type is in the ACE,
|
|
// treat this as an ACCESS_ALLOWED_ACE.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
|
|
if ( ObjectTypeInAce == NULL ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If no object type list was passed,
|
|
// don't grant access to anyone.
|
|
//
|
|
|
|
} else if ( ObjectTypeListLength == 0 ) {
|
|
|
|
// Drop through
|
|
|
|
|
|
//
|
|
// If an object type is in the ACE,
|
|
// Find it in the LocalTypeList before using the ACE.
|
|
//
|
|
} else {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE, Restricted ) ) {
|
|
|
|
if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
Index, // Element already updated
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle a compound Access Allowed ACE
|
|
//
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) {
|
|
|
|
//
|
|
// See comment in MAXIMUM_ALLOWED case as to why we can use EToken here
|
|
// for the client.
|
|
//
|
|
|
|
if ( SepSidInTokenEx(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE, Restricted) &&
|
|
SepSidInTokenEx(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE, Restricted) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Handle an Access Denied ACE
|
|
//
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE, Restricted ) ) {
|
|
|
|
//
|
|
// The zeroeth element represents the object itself.
|
|
// Just check that element.
|
|
//
|
|
if (LocalTypeList->Remaining & ((PACCESS_DENIED_ACE)Ace)->Mask) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Denied ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInTokenEx( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE, Restricted ) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If there is no object type in the ACE,
|
|
// or if the caller didn't specify an object type list,
|
|
// apply this deny ACE to the entire object.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
if ( ObjectTypeInAce == NULL ||
|
|
ObjectTypeListLength == 0 ) {
|
|
|
|
//
|
|
// The zeroeth element represents the object itself.
|
|
// Just check that element.
|
|
//
|
|
if (LocalTypeList->Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Otherwise apply the deny ACE to the object specified
|
|
// in the ACE.
|
|
//
|
|
|
|
} else if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
|
|
if (LocalTypeList[Index].Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
SeMaximumAuditMask(
|
|
IN PACL Sacl,
|
|
IN ACCESS_MASK GrantedAccess,
|
|
IN PACCESS_TOKEN Token,
|
|
OUT PACCESS_MASK pAuditMask
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine takes the passed security descriptor and applies the
|
|
"MAXIMUM_ALLOWED" algorithm to the SACL contained in the security
|
|
descriptor if one exists. This mask represents all the success audits
|
|
that can occur from the passed subject context accessing the passed
|
|
security descriptor.
|
|
|
|
The code walks the SACL and for each SYSTEM_AUDIT_ACE found that
|
|
matches the passed subject context, keeps a running total of the
|
|
access bits in the ACE. The resulting mask is then masked by the
|
|
passed GrantedAccess mask, since we're only interested in the
|
|
bits that the object is actually being opened for.
|
|
|
|
Arguments:
|
|
|
|
Sacl - The Sacl to be examined.
|
|
|
|
GrantedAccess - The access that has been granted to the object.
|
|
|
|
Token - Supplies to effective token for the access attempt.
|
|
|
|
pAuditMask - Returns the mask of bits to be audited (if any).
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
|
|
--*/
|
|
{
|
|
USHORT AceCount = 0;
|
|
PACE_HEADER Ace = NULL;
|
|
ACCESS_MASK AccessMask = (ACCESS_MASK)0;
|
|
UCHAR AceFlags = 0;
|
|
|
|
USHORT i;
|
|
|
|
//
|
|
// Initialize OUT parameters
|
|
//
|
|
|
|
*pAuditMask = (ACCESS_MASK)0;
|
|
|
|
//
|
|
// Determine if there is an SACL in the security descriptor.
|
|
// If not, nothing to do.
|
|
//
|
|
|
|
if (0 == (AceCount = Sacl->AceCount)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Iterate through the ACEs on the Sacl until either we reach
|
|
// the end or discover that we have to take all possible actions,
|
|
// in which case it doesn't pay to look any further
|
|
//
|
|
|
|
for ( i = 0, Ace = FirstAce( Sacl ) ;
|
|
(i < AceCount) ;
|
|
i++, Ace = NextAce( Ace ) ) {
|
|
|
|
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == SYSTEM_AUDIT_ACE_TYPE) ) {
|
|
|
|
AccessMask = ((PSYSTEM_AUDIT_ACE)Ace)->Mask;
|
|
AceFlags = ((PACE_HEADER)Ace)->AceFlags;
|
|
|
|
if ((AccessMask & GrantedAccess) && (AceFlags & SUCCESSFUL_ACCESS_ACE_FLAG)) {
|
|
|
|
if ( SepSidInToken( (PACCESS_TOKEN)Token, NULL, &((PSYSTEM_AUDIT_ACE)Ace)->SidStart, FALSE ) ) {
|
|
|
|
*pAuditMask |= (AccessMask & GrantedAccess);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
SepAccessCheck (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PSID PrincipalSelfSid,
|
|
IN PTOKEN PrimaryToken,
|
|
IN PTOKEN ClientToken OPTIONAL,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN PIOBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
IN ACCESS_MASK PreviouslyGrantedAccess,
|
|
IN KPROCESSOR_MODE PreviousMode,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PPRIVILEGE_SET *Privileges OPTIONAL,
|
|
OUT PNTSTATUS AccessStatus,
|
|
IN BOOLEAN ReturnResultList,
|
|
OUT PBOOLEAN ReturnSomeAccessGranted,
|
|
OUT PBOOLEAN ReturnSomeAccessDenied
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Worker routine for SeAccessCheck and NtAccessCheck. We actually do the
|
|
access checking here.
|
|
|
|
Whether or not we actually evaluate the DACL is based on the following
|
|
interaction between the SE_DACL_PRESENT bit in the security descriptor
|
|
and the value of the DACL pointer itself.
|
|
|
|
|
|
SE_DACL_PRESENT
|
|
|
|
SET CLEAR
|
|
|
|
+-------------+-------------+
|
|
| | |
|
|
NULL | GRANT | GRANT |
|
|
| ALL | ALL |
|
|
DACL | | |
|
|
Pointer +-------------+-------------+
|
|
| | |
|
|
!NULL | EVALUATE | GRANT |
|
|
| ACL | ALL |
|
|
| | |
|
|
+-------------+-------------+
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Pointer to the security descriptor from the object
|
|
being accessed.
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
Token - Pointer to user's token object.
|
|
|
|
TokenLocked - Boolean describing whether or not there is a read lock
|
|
on the token.
|
|
|
|
DesiredAccess - Access mask describing the user's desired access to the
|
|
object. This mask is assumed not to contain generic access types.
|
|
|
|
ObjectTypeList - Supplies a list of GUIDs representing the object (and
|
|
sub-objects) being accessed. If no list is present, AccessCheckByType
|
|
behaves identically to AccessCheck.
|
|
|
|
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
|
|
|
|
GenericMapping - Supplies a pointer to the generic mapping associated
|
|
with this object type.
|
|
|
|
PreviouslyGrantedAccess - Access mask indicating any access' that have
|
|
already been granted by higher level routines
|
|
|
|
PrivilgedAccessMask - Mask describing access types that may not be
|
|
granted without a privilege.
|
|
|
|
GrantedAccess - Returns an access mask describing all granted access',
|
|
or NULL.
|
|
|
|
Privileges - Optionally supplies a pointer in which will be returned
|
|
any privileges that were used for the access. If this is null,
|
|
it will be assumed that privilege checks have been done already.
|
|
|
|
AccessStatus - Returns STATUS_SUCCESS or other error code to be
|
|
propogated back to the caller
|
|
|
|
ReturnResultList - If true, GrantedAccess and AccessStatus is actually
|
|
an array of entries ObjectTypeListLength elements long.
|
|
|
|
ReturnSomeAccessGranted - Returns a value of TRUE to indicate that some access'
|
|
were granted, FALSE otherwise.
|
|
|
|
ReturnSomeAccessDenied - Returns a value of FALSE if some of the requested
|
|
access was not granted. This will alway be an inverse of SomeAccessGranted
|
|
unless ReturnResultList is TRUE. In that case,
|
|
|
|
Return Value:
|
|
|
|
A value of TRUE indicates that some access' were granted, FALSE
|
|
otherwise.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
ACCESS_MASK Remaining;
|
|
BOOLEAN RetVal = TRUE;
|
|
|
|
PACL Dacl;
|
|
|
|
PVOID Ace;
|
|
ULONG AceCount;
|
|
|
|
ULONG i;
|
|
ULONG j;
|
|
ULONG Index;
|
|
ULONG PrivilegeCount = 0;
|
|
BOOLEAN Success = FALSE;
|
|
BOOLEAN SystemSecurity = FALSE;
|
|
BOOLEAN WriteOwner = FALSE;
|
|
PTOKEN EToken;
|
|
|
|
IOBJECT_TYPE_LIST FixedTypeList;
|
|
PIOBJECT_TYPE_LIST LocalTypeList;
|
|
ULONG LocalTypeListLength;
|
|
ULONG ResultListIndex;
|
|
|
|
PAGED_CODE();
|
|
|
|
#if DBG
|
|
|
|
SepDumpSecurityDescriptor(
|
|
SecurityDescriptor,
|
|
"Input to SeAccessCheck\n"
|
|
);
|
|
|
|
if (ARGUMENT_PRESENT( ClientToken )) {
|
|
SepDumpTokenInfo( ClientToken );
|
|
}
|
|
|
|
SepDumpTokenInfo( PrimaryToken );
|
|
|
|
#endif
|
|
|
|
|
|
EToken = ARGUMENT_PRESENT( ClientToken ) ? ClientToken : PrimaryToken;
|
|
|
|
//
|
|
// Assert that there are no generic accesses in the DesiredAccess
|
|
//
|
|
|
|
SeAssertMappedCanonicalAccess( DesiredAccess );
|
|
|
|
Remaining = DesiredAccess;
|
|
|
|
|
|
//
|
|
// Check for ACCESS_SYSTEM_SECURITY here,
|
|
// fail if he's asking for it and doesn't have
|
|
// the privilege.
|
|
//
|
|
|
|
if ( Remaining & ACCESS_SYSTEM_SECURITY ) {
|
|
|
|
//
|
|
// Bugcheck if we weren't given a pointer to return privileges
|
|
// into. Our caller was supposed to have taken care of this
|
|
// in that case.
|
|
//
|
|
|
|
ASSERT( ARGUMENT_PRESENT( Privileges ));
|
|
|
|
Success = SepSinglePrivilegeCheck (
|
|
SeSecurityPrivilege,
|
|
EToken,
|
|
PreviousMode
|
|
);
|
|
|
|
if (!Success) {
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_PRIVILEGE_NOT_HELD;
|
|
goto ReturnOneStatus;
|
|
}
|
|
|
|
//
|
|
// Success, remove ACCESS_SYSTEM_SECURITY from remaining, add it
|
|
// to PreviouslyGrantedAccess
|
|
//
|
|
|
|
Remaining &= ~ACCESS_SYSTEM_SECURITY;
|
|
PreviouslyGrantedAccess |= ACCESS_SYSTEM_SECURITY;
|
|
|
|
PrivilegeCount++;
|
|
SystemSecurity = TRUE;
|
|
|
|
if ( Remaining == 0 ) {
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Get pointer to client SID's
|
|
//
|
|
|
|
Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor );
|
|
|
|
//
|
|
// If the SE_DACL_PRESENT bit is not set, the object has no
|
|
// security, so all accesses are granted. If he's asking for
|
|
// MAXIMUM_ALLOWED, return the GENERIC_ALL field from the generic
|
|
// mapping.
|
|
//
|
|
// Also grant all access if the Dacl is NULL.
|
|
//
|
|
|
|
if ( !RtlpAreControlBitsSet(
|
|
(PISECURITY_DESCRIPTOR)SecurityDescriptor,
|
|
SE_DACL_PRESENT) || (Dacl == NULL)) {
|
|
|
|
|
|
//
|
|
// Restricted tokens treat a NULL dacl the same as a DACL with no
|
|
// ACEs.
|
|
//
|
|
#ifdef SECURE_NULL_DACLS
|
|
if (SeTokenIsRestricted( EToken )) {
|
|
//
|
|
// We know that Remaining != 0 here, because we
|
|
// know it was non-zero coming into this routine,
|
|
// and we've checked it against 0 every time we've
|
|
// cleared a bit.
|
|
//
|
|
|
|
ASSERT( Remaining != 0 );
|
|
|
|
//
|
|
// There are ungranted accesses. Since there is
|
|
// nothing in the DACL, they will not be granted.
|
|
// If, however, the only ungranted access at this
|
|
// point is MAXIMUM_ALLOWED, and something has been
|
|
// granted in the PreviouslyGranted mask, return
|
|
// what has been granted.
|
|
//
|
|
|
|
if ( (Remaining == MAXIMUM_ALLOWED) && (PreviouslyGrantedAccess != (ACCESS_MASK)0) ) {
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
|
|
} else {
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto ReturnOneStatus;
|
|
}
|
|
}
|
|
#endif //SECURE_NULL_DACLS
|
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|
|
|
//
|
|
// Give him:
|
|
// GenericAll
|
|
// Anything else he asked for
|
|
//
|
|
|
|
PreviouslyGrantedAccess =
|
|
GenericMapping->GenericAll |
|
|
(DesiredAccess | PreviouslyGrantedAccess) & ~MAXIMUM_ALLOWED;
|
|
|
|
} else {
|
|
|
|
PreviouslyGrantedAccess |= DesiredAccess;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
}
|
|
|
|
//
|
|
// There is security on this object. Check to see
|
|
// if he's asking for WRITE_OWNER, and perform the
|
|
// privilege check if so.
|
|
//
|
|
|
|
if ( (Remaining & WRITE_OWNER) && ARGUMENT_PRESENT( Privileges ) ) {
|
|
|
|
Success = SepSinglePrivilegeCheck (
|
|
SeTakeOwnershipPrivilege,
|
|
EToken,
|
|
PreviousMode
|
|
);
|
|
|
|
if (Success) {
|
|
|
|
//
|
|
// Success, remove WRITE_OWNER from remaining, add it
|
|
// to PreviouslyGrantedAccess
|
|
//
|
|
|
|
Remaining &= ~WRITE_OWNER;
|
|
PreviouslyGrantedAccess |= WRITE_OWNER;
|
|
|
|
PrivilegeCount++;
|
|
WriteOwner = TRUE;
|
|
|
|
if ( Remaining == 0 ) {
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If the DACL is empty,
|
|
// deny all access immediately.
|
|
//
|
|
|
|
if ((AceCount = Dacl->AceCount) == 0) {
|
|
|
|
//
|
|
// We know that Remaining != 0 here, because we
|
|
// know it was non-zero coming into this routine,
|
|
// and we've checked it against 0 every time we've
|
|
// cleared a bit.
|
|
//
|
|
|
|
ASSERT( Remaining != 0 );
|
|
|
|
//
|
|
// There are ungranted accesses. Since there is
|
|
// nothing in the DACL, they will not be granted.
|
|
// If, however, the only ungranted access at this
|
|
// point is MAXIMUM_ALLOWED, and something has been
|
|
// granted in the PreviouslyGranted mask, return
|
|
// what has been granted.
|
|
//
|
|
|
|
if ( (Remaining == MAXIMUM_ALLOWED) && (PreviouslyGrantedAccess != (ACCESS_MASK)0) ) {
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
|
|
} else {
|
|
PreviouslyGrantedAccess = 0;
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto ReturnOneStatus;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fake out a top level ObjectType list if none is passed by the caller.
|
|
//
|
|
|
|
if ( ObjectTypeListLength == 0 ) {
|
|
LocalTypeList = &FixedTypeList;
|
|
LocalTypeListLength = 1;
|
|
RtlZeroMemory( &FixedTypeList, sizeof(FixedTypeList) );
|
|
FixedTypeList.ParentIndex = -1;
|
|
} else {
|
|
LocalTypeList = ObjectTypeList;
|
|
LocalTypeListLength = ObjectTypeListLength;
|
|
}
|
|
|
|
//
|
|
// If the caller wants the MAXIMUM_ALLOWED or the caller wants the
|
|
// results on all objects and subobjects, use a slower algorithm
|
|
// that traverses all the ACEs.
|
|
//
|
|
|
|
if ( (DesiredAccess & MAXIMUM_ALLOWED) != 0 ||
|
|
ReturnResultList ) {
|
|
|
|
//
|
|
// Do the normal maximum-allowed access check
|
|
//
|
|
|
|
SepMaximumAccessCheck(
|
|
EToken,
|
|
PrimaryToken,
|
|
Dacl,
|
|
PrincipalSelfSid,
|
|
LocalTypeListLength,
|
|
LocalTypeList,
|
|
ObjectTypeListLength,
|
|
FALSE
|
|
);
|
|
|
|
//
|
|
// If this is a restricted token, do the additional access check
|
|
//
|
|
|
|
if (SeTokenIsRestricted( EToken ) ) {
|
|
SepMaximumAccessCheck(
|
|
EToken,
|
|
PrimaryToken,
|
|
Dacl,
|
|
PrincipalSelfSid,
|
|
LocalTypeListLength,
|
|
LocalTypeList,
|
|
ObjectTypeListLength,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// If the caller wants to know the individual results of each sub-object,
|
|
// sub-object,
|
|
// break it down for him.
|
|
//
|
|
|
|
if ( ReturnResultList ) {
|
|
ACCESS_MASK GrantedAccessMask;
|
|
ACCESS_MASK RequiredAccessMask;
|
|
BOOLEAN SomeAccessGranted = FALSE;
|
|
BOOLEAN SomeAccessDenied = FALSE;
|
|
|
|
//
|
|
// Compute mask of Granted access bits to tell the caller about.
|
|
// If he asked for MAXIMUM_ALLOWED,
|
|
// tell him everything,
|
|
// otherwise
|
|
// tell him what he asked about.
|
|
//
|
|
|
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|
GrantedAccessMask = (ACCESS_MASK) ~MAXIMUM_ALLOWED;
|
|
RequiredAccessMask = (DesiredAccess | PreviouslyGrantedAccess) & ~MAXIMUM_ALLOWED;
|
|
} else {
|
|
GrantedAccessMask = DesiredAccess | PreviouslyGrantedAccess;
|
|
RequiredAccessMask = DesiredAccess | PreviouslyGrantedAccess;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Loop computing the access granted to each object and sub-object.
|
|
//
|
|
for ( ResultListIndex=0;
|
|
ResultListIndex<LocalTypeListLength;
|
|
ResultListIndex++ ) {
|
|
|
|
//
|
|
// Return the subset of the access granted that the caller
|
|
// expressed interest in.
|
|
//
|
|
|
|
GrantedAccess[ResultListIndex] =
|
|
(LocalTypeList[ResultListIndex].CurrentGranted |
|
|
PreviouslyGrantedAccess ) &
|
|
GrantedAccessMask;
|
|
|
|
//
|
|
// If absolutely no access was granted,
|
|
// indicate so.
|
|
//
|
|
if ( GrantedAccess[ResultListIndex] == 0 ) {
|
|
AccessStatus[ResultListIndex] = STATUS_ACCESS_DENIED;
|
|
SomeAccessDenied = TRUE;
|
|
} else {
|
|
|
|
//
|
|
// If some requested access is still missing,
|
|
// the bottom line is that access is denied.
|
|
//
|
|
// Note, that ByTypeResultList actually returns the
|
|
// partially granted access mask even though the caller
|
|
// really has no access to the object.
|
|
//
|
|
|
|
if ( ((~GrantedAccess[ResultListIndex]) & RequiredAccessMask ) != 0 ) {
|
|
AccessStatus[ResultListIndex] = STATUS_ACCESS_DENIED;
|
|
SomeAccessDenied = TRUE;
|
|
} else {
|
|
AccessStatus[ResultListIndex] = STATUS_SUCCESS;
|
|
SomeAccessGranted = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( SomeAccessGranted && PrivilegeCount != 0 ) {
|
|
|
|
SepAssemblePrivileges(
|
|
PrivilegeCount,
|
|
SystemSecurity,
|
|
WriteOwner,
|
|
Privileges
|
|
);
|
|
|
|
if ( ( Privileges != NULL ) && ( *Privileges == NULL ) ) {
|
|
|
|
RetVal = FALSE;
|
|
SomeAccessGranted = FALSE;
|
|
SomeAccessDenied = TRUE;
|
|
|
|
for ( ResultListIndex=0;
|
|
ResultListIndex<LocalTypeListLength;
|
|
ResultListIndex++ ) {
|
|
|
|
AccessStatus[ResultListIndex] = STATUS_NO_MEMORY;
|
|
GrantedAccess[ResultListIndex] = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessGranted)) {
|
|
*ReturnSomeAccessGranted = SomeAccessGranted;
|
|
}
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessDenied)) {
|
|
*ReturnSomeAccessDenied = SomeAccessDenied;
|
|
}
|
|
|
|
return RetVal;
|
|
|
|
//
|
|
// If the caller is only interested in the access to the object itself,
|
|
// just summarize.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Turn off the MAXIMUM_ALLOWED bit and whatever we found that
|
|
// he was granted. If the user passed in extra bits in addition
|
|
// to MAXIMUM_ALLOWED, make sure that he was granted those access
|
|
// types. If not, he didn't get what he wanted, so return failure.
|
|
//
|
|
|
|
Remaining &= ~(MAXIMUM_ALLOWED | LocalTypeList->CurrentGranted);
|
|
|
|
if (Remaining != 0) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
PreviouslyGrantedAccess = 0;
|
|
goto ReturnOneStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
PreviouslyGrantedAccess |= LocalTypeList->CurrentGranted;
|
|
Status = STATUS_SUCCESS;
|
|
goto ReturnOneStatus;
|
|
|
|
}
|
|
|
|
} // if MAXIMUM_ALLOWED...
|
|
|
|
|
|
#ifdef notdef
|
|
//
|
|
// The remaining bits are "remaining" at all levels
|
|
|
|
for ( j=0; j<LocalTypeListLength; j++ ) {
|
|
LocalTypeList[j].Remaining = Remaining;
|
|
}
|
|
|
|
//
|
|
// Process the DACL handling individual access bits.
|
|
//
|
|
|
|
for ( i = 0, Ace = FirstAce( Dacl ) ;
|
|
( i < AceCount ) && ( LocalTypeList->Remaining != 0 ) ;
|
|
i++, Ace = NextAce( Ace ) ) {
|
|
|
|
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
|
|
|
|
//
|
|
// Handle an Access Allowed ACE
|
|
//
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInToken( EToken, PrincipalSelfSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart, FALSE ) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
//
|
|
// The zeroeth object type represents the object itself.
|
|
//
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Allowed ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If no object type is in the ACE,
|
|
// treat this as an ACCESS_ALLOWED_ACE.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
|
|
if ( ObjectTypeInAce == NULL ) {
|
|
|
|
if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE ) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If no object type list was passed,
|
|
// don't grant access to anyone.
|
|
//
|
|
|
|
} else if ( ObjectTypeListLength == 0 ) {
|
|
|
|
// Drop through
|
|
|
|
|
|
//
|
|
// If an object type is in the ACE,
|
|
// Find it in the LocalTypeList before using the ACE.
|
|
//
|
|
} else {
|
|
|
|
if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), FALSE ) ) {
|
|
|
|
if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
Index, // Element already updated
|
|
((PACCESS_ALLOWED_OBJECT_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle a compound Access Allowed ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) {
|
|
|
|
//
|
|
// See comment in MAXIMUM_ALLOWED case as to why we can use EToken here
|
|
// for the client.
|
|
//
|
|
|
|
if ( SepSidInToken(EToken, PrincipalSelfSid, RtlCompoundAceClientSid( Ace ), FALSE) &&
|
|
SepSidInToken(PrimaryToken, NULL, RtlCompoundAceServerSid( Ace ), FALSE) ) {
|
|
|
|
// Optimize 'normal' case
|
|
if ( LocalTypeListLength == 1 ) {
|
|
LocalTypeList->Remaining &= ~((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask;
|
|
} else {
|
|
SepAddAccessTypeList(
|
|
LocalTypeList, // List to modify
|
|
LocalTypeListLength, // Length of list
|
|
0, // Element to update
|
|
((PCOMPOUND_ACCESS_ALLOWED_ACE)Ace)->Mask, // Access Granted
|
|
UpdateRemaining );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Handle an Access Denied ACE
|
|
//
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInToken( EToken, PrincipalSelfSid, &((PACCESS_DENIED_ACE)Ace)->SidStart, TRUE ) ) {
|
|
|
|
//
|
|
// The zeroeth element represents the object itself.
|
|
// Just check that element.
|
|
//
|
|
if (LocalTypeList->Remaining & ((PACCESS_DENIED_ACE)Ace)->Mask) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Handle an object specific Access Denied ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) {
|
|
|
|
if ( SepSidInToken( EToken, PrincipalSelfSid, RtlObjectAceSid(Ace), TRUE ) ) {
|
|
GUID *ObjectTypeInAce;
|
|
|
|
//
|
|
// If there is no object type in the ACE,
|
|
// or if the caller didn't specify an object type list,
|
|
// apply this deny ACE to the entire object.
|
|
//
|
|
|
|
ObjectTypeInAce = RtlObjectAceObjectType(Ace);
|
|
if ( ObjectTypeInAce == NULL ||
|
|
ObjectTypeListLength == 0 ) {
|
|
|
|
//
|
|
// The zeroeth element represents the object itself.
|
|
// Just check that element.
|
|
//
|
|
if (LocalTypeList->Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Otherwise apply the deny ACE to the object specified
|
|
// in the ACE.
|
|
//
|
|
|
|
} else if ( SepObjectInTypeList( ObjectTypeInAce,
|
|
LocalTypeList,
|
|
LocalTypeListLength,
|
|
&Index ) ) {
|
|
|
|
if (LocalTypeList[Index].Remaining & ((PACCESS_DENIED_OBJECT_ACE)Ace)->Mask) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Do the normal access check first
|
|
//
|
|
|
|
SepNormalAccessCheck(
|
|
Remaining,
|
|
EToken,
|
|
PrimaryToken,
|
|
Dacl,
|
|
PrincipalSelfSid,
|
|
LocalTypeListLength,
|
|
LocalTypeList,
|
|
ObjectTypeListLength,
|
|
FALSE
|
|
);
|
|
|
|
if (LocalTypeList->Remaining != 0) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
PreviouslyGrantedAccess = 0;
|
|
goto ReturnOneStatus;
|
|
}
|
|
|
|
//
|
|
// If this is a restricted token, do the additional access check
|
|
//
|
|
|
|
if (SeTokenIsRestricted( EToken ) ) {
|
|
SepNormalAccessCheck(
|
|
Remaining,
|
|
EToken,
|
|
PrimaryToken,
|
|
Dacl,
|
|
PrincipalSelfSid,
|
|
LocalTypeListLength,
|
|
LocalTypeList,
|
|
ObjectTypeListLength,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
|
|
if (LocalTypeList->Remaining != 0) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
PreviouslyGrantedAccess = 0;
|
|
goto ReturnOneStatus;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
PreviouslyGrantedAccess |= DesiredAccess;
|
|
|
|
//
|
|
// Return a single status code to the caller.
|
|
//
|
|
|
|
ReturnOneStatus:
|
|
if ( Status == STATUS_SUCCESS && PreviouslyGrantedAccess == 0 ) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
if ( PrivilegeCount > 0 ) {
|
|
SepAssemblePrivileges(
|
|
PrivilegeCount,
|
|
SystemSecurity,
|
|
WriteOwner,
|
|
Privileges
|
|
);
|
|
|
|
if ( ( Privileges != NULL ) && ( *Privileges == NULL ) ) {
|
|
RetVal = FALSE;
|
|
Status = STATUS_NO_MEMORY;
|
|
PreviouslyGrantedAccess = 0;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// If the caller asked for a list of status',
|
|
// duplicate the status all over.
|
|
//
|
|
if ( ReturnResultList ) {
|
|
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
|
|
AccessStatus[ResultListIndex] = Status;
|
|
GrantedAccess[ResultListIndex] = PreviouslyGrantedAccess;
|
|
}
|
|
} else {
|
|
*AccessStatus = Status;
|
|
*GrantedAccess = PreviouslyGrantedAccess;
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessGranted)) {
|
|
*ReturnSomeAccessGranted = TRUE;
|
|
}
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessDenied)) {
|
|
*ReturnSomeAccessDenied = FALSE;
|
|
}
|
|
} else {
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessGranted)) {
|
|
*ReturnSomeAccessGranted = FALSE;
|
|
}
|
|
if ( ARGUMENT_PRESENT(ReturnSomeAccessDenied)) {
|
|
*ReturnSomeAccessDenied = TRUE;
|
|
}
|
|
}
|
|
return RetVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NtAccessCheck (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN HANDLE ClientToken,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PPRIVILEGE_SET PrivilegeSet,
|
|
IN OUT PULONG PrivilegeSetLength,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
See module abstract.
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the object
|
|
being accessed
|
|
|
|
ClientToken - Supplies the handle of the user's token.
|
|
|
|
DesiredAccess - Supplies the desired access mask.
|
|
|
|
GenericMapping - Supplies the generic mapping associated with this
|
|
object type.
|
|
|
|
PrivilegeSet - A pointer to a buffer that upon return will contain
|
|
any privileges that were used to perform the access validation.
|
|
If no privileges were used, the buffer will contain a privilege
|
|
set consisting of zero privileges.
|
|
|
|
PrivilegeSetLength - The size of the PrivilegeSet buffer in bytes.
|
|
|
|
GrantedAccess - Returns an access mask describing the granted access.
|
|
|
|
AccessStatus - Status value that may be returned indicating the
|
|
reason why access was denied. Routines should avoid hardcoding a
|
|
return value of STATUS_ACCESS_DENIED so that a different value can
|
|
be returned when mandatory access control is implemented.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - The attempt proceeded normally. This does not
|
|
mean access was granted, rather that the parameters were
|
|
correct.
|
|
|
|
STATUS_GENERIC_NOT_MAPPED - The DesiredAccess mask contained
|
|
an unmapped generic access.
|
|
|
|
STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough
|
|
to contain the information being returned.
|
|
|
|
STATUS_NO_IMPERSONTAION_TOKEN - The passed Token was not an impersonation
|
|
token.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
return SeAccessCheckByType (
|
|
SecurityDescriptor,
|
|
NULL, // No Principal Self sid
|
|
ClientToken,
|
|
DesiredAccess,
|
|
NULL, // No ObjectType List
|
|
0, // No ObjectType List
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
GrantedAccess,
|
|
AccessStatus,
|
|
FALSE ); // Return a single GrantedAccess and AccessStatus
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NtAccessCheckByType (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PSID PrincipalSelfSid,
|
|
IN HANDLE ClientToken,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PPRIVILEGE_SET PrivilegeSet,
|
|
IN OUT PULONG PrivilegeSetLength,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
See module abstract.
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the object
|
|
being accessed
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
ClientToken - Supplies the handle of the user's token.
|
|
|
|
DesiredAccess - Supplies the desired access mask.
|
|
|
|
ObjectTypeList - Supplies a list of GUIDs representing the object (and
|
|
sub-objects) being accessed. If no list is present, AccessCheckByType
|
|
behaves identically to AccessCheck.
|
|
|
|
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
|
|
|
|
GenericMapping - Supplies the generic mapping associated with this
|
|
object type.
|
|
|
|
PrivilegeSet - A pointer to a buffer that upon return will contain
|
|
any privileges that were used to perform the access validation.
|
|
If no privileges were used, the buffer will contain a privilege
|
|
set consisting of zero privileges.
|
|
|
|
PrivilegeSetLength - The size of the PrivilegeSet buffer in bytes.
|
|
|
|
GrantedAccess - Returns an access mask describing the granted access.
|
|
|
|
AccessStatus - Status value that may be returned indicating the
|
|
reason why access was denied. Routines should avoid hardcoding a
|
|
return value of STATUS_ACCESS_DENIED so that a different value can
|
|
be returned when mandatory access control is implemented.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - The attempt proceeded normally. This does not
|
|
mean access was granted, rather that the parameters were
|
|
correct.
|
|
|
|
STATUS_GENERIC_NOT_MAPPED - The DesiredAccess mask contained
|
|
an unmapped generic access.
|
|
|
|
STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough
|
|
to contain the information being returned.
|
|
|
|
STATUS_NO_IMPERSONTAION_TOKEN - The passed Token was not an impersonation
|
|
token.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
return SeAccessCheckByType (
|
|
SecurityDescriptor,
|
|
PrincipalSelfSid,
|
|
ClientToken,
|
|
DesiredAccess,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
GrantedAccess,
|
|
AccessStatus,
|
|
FALSE ); // Return a single GrantedAccess and AccessStatus
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NtAccessCheckByTypeResultList (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PSID PrincipalSelfSid,
|
|
IN HANDLE ClientToken,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PPRIVILEGE_SET PrivilegeSet,
|
|
IN OUT PULONG PrivilegeSetLength,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
See module abstract.
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the object
|
|
being accessed
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
ClientToken - Supplies the handle of the user's token.
|
|
|
|
DesiredAccess - Supplies the desired access mask.
|
|
|
|
ObjectTypeList - Supplies a list of GUIDs representing the object (and
|
|
sub-objects) being accessed. If no list is present, AccessCheckByType
|
|
behaves identically to AccessCheck.
|
|
|
|
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
|
|
|
|
GenericMapping - Supplies the generic mapping associated with this
|
|
object type.
|
|
|
|
PrivilegeSet - A pointer to a buffer that upon return will contain
|
|
any privileges that were used to perform the access validation.
|
|
If no privileges were used, the buffer will contain a privilege
|
|
set consisting of zero privileges.
|
|
|
|
PrivilegeSetLength - The size of the PrivilegeSet buffer in bytes.
|
|
|
|
GrantedAccess - Returns an access mask describing the granted access.
|
|
|
|
AccessStatus - Status value that may be returned indicating the
|
|
reason why access was denied. Routines should avoid hardcoding a
|
|
return value of STATUS_ACCESS_DENIED so that a different value can
|
|
be returned when mandatory access control is implemented.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - The attempt proceeded normally. This does not
|
|
mean access was granted, rather that the parameters were
|
|
correct.
|
|
|
|
STATUS_GENERIC_NOT_MAPPED - The DesiredAccess mask contained
|
|
an unmapped generic access.
|
|
|
|
STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough
|
|
to contain the information being returned.
|
|
|
|
STATUS_NO_IMPERSONTAION_TOKEN - The passed Token was not an impersonation
|
|
token.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
return SeAccessCheckByType (
|
|
SecurityDescriptor,
|
|
PrincipalSelfSid,
|
|
ClientToken,
|
|
DesiredAccess,
|
|
ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
GenericMapping,
|
|
PrivilegeSet,
|
|
PrivilegeSetLength,
|
|
GrantedAccess,
|
|
AccessStatus,
|
|
TRUE ); // Return an array of GrantedAccess and AccessStatus
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SeAccessCheckByType (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PSID PrincipalSelfSid,
|
|
IN HANDLE ClientToken,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_TYPE_LIST ObjectTypeList OPTIONAL,
|
|
IN ULONG ObjectTypeListLength,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PPRIVILEGE_SET PrivilegeSet,
|
|
IN OUT PULONG PrivilegeSetLength,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus,
|
|
IN BOOLEAN ReturnResultList
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
See module abstract.
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the object
|
|
being accessed
|
|
|
|
PrincipalSelfSid - If the object being access checked is an object which
|
|
represents a principal (e.g., a user object), this parameter should
|
|
be the SID of the object. Any ACE containing the constant
|
|
PRINCIPAL_SELF_SID is replaced by this SID.
|
|
|
|
The parameter should be NULL if the object does not represent a principal.
|
|
|
|
ClientToken - Supplies the handle of the user's token.
|
|
|
|
DesiredAccess - Supplies the desired access mask.
|
|
|
|
ObjectTypeList - Supplies a list of GUIDs representing the object (and
|
|
sub-objects) being accessed. If no list is present, AccessCheckByType
|
|
behaves identically to AccessCheck.
|
|
|
|
ObjectTypeListLength - Specifies the number of elements in the ObjectTypeList.
|
|
|
|
GenericMapping - Supplies the generic mapping associated with this
|
|
object type.
|
|
|
|
PrivilegeSet - A pointer to a buffer that upon return will contain
|
|
any privileges that were used to perform the access validation.
|
|
If no privileges were used, the buffer will contain a privilege
|
|
set consisting of zero privileges.
|
|
|
|
PrivilegeSetLength - The size of the PrivilegeSet buffer in bytes.
|
|
|
|
GrantedAccess - Returns an access mask describing the granted access.
|
|
|
|
AccessStatus - Status value that may be returned indicating the
|
|
reason why access was denied. Routines should avoid hardcoding a
|
|
return value of STATUS_ACCESS_DENIED so that a different value can
|
|
be returned when mandatory access control is implemented.
|
|
|
|
ReturnResultList - If true, GrantedAccess and AccessStatus are actually
|
|
arrays of entries ObjectTypeListLength elements long.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - The attempt proceeded normally. This does not
|
|
mean access was granted, rather that the parameters were
|
|
correct.
|
|
|
|
STATUS_GENERIC_NOT_MAPPED - The DesiredAccess mask contained
|
|
an unmapped generic access.
|
|
|
|
STATUS_BUFFER_TOO_SMALL - The passed buffer was not large enough
|
|
to contain the information being returned.
|
|
|
|
STATUS_NO_IMPERSONTAION_TOKEN - The passed Token was not an impersonation
|
|
token.
|
|
|
|
--*/
|
|
|
|
{
|
|
ACCESS_MASK LocalGrantedAccess;
|
|
PACCESS_MASK LocalGrantedAccessPointer = NULL;
|
|
NTSTATUS LocalAccessStatus;
|
|
PNTSTATUS LocalAccessStatusPointer = NULL;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PTOKEN Token = NULL;
|
|
PSECURITY_DESCRIPTOR CapturedSecurityDescriptor = NULL;
|
|
PSID CapturedPrincipalSelfSid = NULL;
|
|
ACCESS_MASK PreviouslyGrantedAccess = 0;
|
|
GENERIC_MAPPING LocalGenericMapping;
|
|
PIOBJECT_TYPE_LIST LocalObjectTypeList = NULL;
|
|
PPRIVILEGE_SET Privileges = NULL;
|
|
SECURITY_SUBJECT_CONTEXT SubjectContext;
|
|
ULONG LocalPrivilegeSetLength = 0;
|
|
ULONG ResultListIndex = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode == KernelMode) {
|
|
ASSERT( !ReturnResultList );
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
*GrantedAccess = DesiredAccess;
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
try {
|
|
|
|
if ( ReturnResultList ) {
|
|
|
|
if ( ObjectTypeListLength == 0 ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave ;
|
|
}
|
|
|
|
if ( !IsValidElementCount( ObjectTypeListLength, OBJECT_TYPE_LIST ) )
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER ;
|
|
|
|
leave ;
|
|
}
|
|
|
|
ProbeForWrite(
|
|
AccessStatus,
|
|
sizeof(NTSTATUS) * ObjectTypeListLength,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
ProbeForWrite(
|
|
GrantedAccess,
|
|
sizeof(ACCESS_MASK) * ObjectTypeListLength,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
} else {
|
|
ProbeForWriteUlong((PULONG)AccessStatus);
|
|
ProbeForWriteUlong((PULONG)GrantedAccess);
|
|
}
|
|
|
|
LocalPrivilegeSetLength = ProbeAndReadUlong( PrivilegeSetLength );
|
|
ProbeForWriteUlong(
|
|
PrivilegeSetLength
|
|
);
|
|
|
|
ProbeForWrite(
|
|
PrivilegeSet,
|
|
LocalPrivilegeSetLength,
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
//
|
|
// initialize PrivilegeCount in case the caller passed in an
|
|
// uninitialized PrivilegeSet
|
|
//
|
|
|
|
if ( PrivilegeSet &&
|
|
( LocalPrivilegeSetLength >= sizeof(PRIVILEGE_SET) )) {
|
|
|
|
PrivilegeSet->PrivilegeCount = 0;
|
|
}
|
|
|
|
ProbeForReadSmallStructure(
|
|
GenericMapping,
|
|
sizeof(GENERIC_MAPPING),
|
|
sizeof(ULONG)
|
|
);
|
|
|
|
LocalGenericMapping = *GenericMapping;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = GetExceptionCode();
|
|
}
|
|
if (!NT_SUCCESS( Status ) ) {
|
|
return( Status );
|
|
}
|
|
|
|
if (DesiredAccess &
|
|
( GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL )) {
|
|
|
|
|
|
Status = STATUS_GENERIC_NOT_MAPPED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Obtain a pointer to the passed token
|
|
//
|
|
|
|
Status = ObReferenceObjectByHandle(
|
|
ClientToken, // Handle
|
|
(ACCESS_MASK)TOKEN_QUERY, // DesiredAccess
|
|
SeTokenObjectType, // ObjectType
|
|
PreviousMode, // AccessMode
|
|
(PVOID *)&Token, // Object
|
|
0 // GrantedAccess
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
Token = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// It must be an impersonation token, and at impersonation
|
|
// level of Identification or above.
|
|
//
|
|
|
|
if (Token->TokenType != TokenImpersonation) {
|
|
Status = STATUS_NO_IMPERSONATION_TOKEN;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( Token->ImpersonationLevel < SecurityIdentification ) {
|
|
Status = STATUS_BAD_IMPERSONATION_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Capture any Object type list
|
|
//
|
|
|
|
Status = SeCaptureObjectTypeList( ObjectTypeList,
|
|
ObjectTypeListLength,
|
|
PreviousMode,
|
|
&LocalObjectTypeList );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Compare the DesiredAccess with the privileges in the
|
|
// passed token, and see if we can either satisfy the requested
|
|
// access with a privilege, or bomb out immediately because
|
|
// we don't have a privilege we need.
|
|
//
|
|
|
|
Status = SePrivilegePolicyCheck(
|
|
&DesiredAccess,
|
|
&PreviouslyGrantedAccess,
|
|
NULL,
|
|
(PACCESS_TOKEN)Token,
|
|
&Privileges,
|
|
PreviousMode
|
|
);
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
try {
|
|
|
|
if ( ReturnResultList ) {
|
|
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
|
|
AccessStatus[ResultListIndex] = Status;
|
|
GrantedAccess[ResultListIndex] = 0;
|
|
}
|
|
|
|
} else {
|
|
*AccessStatus = Status;
|
|
*GrantedAccess = 0;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the passed privileges buffer is large enough for
|
|
// whatever we have to put into it.
|
|
//
|
|
|
|
if (Privileges != NULL) {
|
|
|
|
if ( ((ULONG)SepPrivilegeSetSize( Privileges )) > LocalPrivilegeSetLength ) {
|
|
|
|
try {
|
|
|
|
*PrivilegeSetLength = SepPrivilegeSetSize( Privileges );
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} except ( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
SeFreePrivileges( Privileges );
|
|
|
|
goto Cleanup;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
RtlCopyMemory(
|
|
PrivilegeSet,
|
|
Privileges,
|
|
SepPrivilegeSetSize( Privileges )
|
|
);
|
|
|
|
} except ( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
SeFreePrivileges( Privileges );
|
|
Status = GetExceptionCode();
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
SeFreePrivileges( Privileges );
|
|
|
|
} else {
|
|
|
|
//
|
|
// No privileges were used, construct an empty privilege set
|
|
//
|
|
|
|
if ( LocalPrivilegeSetLength < sizeof(PRIVILEGE_SET) ) {
|
|
|
|
try {
|
|
|
|
*PrivilegeSetLength = sizeof(PRIVILEGE_SET);
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} except ( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
try {
|
|
|
|
PrivilegeSet->PrivilegeCount = 0;
|
|
PrivilegeSet->Control = 0;
|
|
|
|
} except ( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
Status = GetExceptionCode();
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Capture the PrincipalSelfSid.
|
|
//
|
|
|
|
if ( PrincipalSelfSid != NULL ) {
|
|
Status = SeCaptureSid(
|
|
PrincipalSelfSid,
|
|
PreviousMode,
|
|
NULL, 0,
|
|
PagedPool,
|
|
TRUE,
|
|
&CapturedPrincipalSelfSid );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
CapturedPrincipalSelfSid = NULL;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Capture the passed security descriptor.
|
|
//
|
|
// SeCaptureSecurityDescriptor probes the input security descriptor,
|
|
// so we don't have to
|
|
//
|
|
|
|
Status = SeCaptureSecurityDescriptor (
|
|
SecurityDescriptor,
|
|
PreviousMode,
|
|
PagedPool,
|
|
FALSE,
|
|
&CapturedSecurityDescriptor
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If there's no security descriptor, then we've been
|
|
// called without all the parameters we need.
|
|
// Return invalid security descriptor.
|
|
//
|
|
|
|
if ( CapturedSecurityDescriptor == NULL ) {
|
|
Status = STATUS_INVALID_SECURITY_DESCR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// A valid security descriptor must have an owner and a group
|
|
//
|
|
|
|
if ( RtlpOwnerAddrSecurityDescriptor(
|
|
(PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor
|
|
) == NULL ||
|
|
RtlpGroupAddrSecurityDescriptor(
|
|
(PISECURITY_DESCRIPTOR)CapturedSecurityDescriptor
|
|
) == NULL ) {
|
|
|
|
SeReleaseSecurityDescriptor (
|
|
CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE
|
|
);
|
|
|
|
Status = STATUS_INVALID_SECURITY_DESCR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
SeCaptureSubjectContext( &SubjectContext );
|
|
|
|
SepAcquireTokenReadLock( Token );
|
|
|
|
//
|
|
// If the user in the token is the owner of the object, we
|
|
// must automatically grant ReadControl and WriteDac access
|
|
// if desired. If the DesiredAccess mask is empty after
|
|
// these bits are turned off, we don't have to do any more
|
|
// access checking (ref section 4, DSA ACL Arch)
|
|
//
|
|
|
|
|
|
if ( DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED) ) {
|
|
|
|
if (SepTokenIsOwner( Token, CapturedSecurityDescriptor, TRUE )) {
|
|
|
|
if ( DesiredAccess & MAXIMUM_ALLOWED ) {
|
|
|
|
PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL);
|
|
|
|
} else {
|
|
|
|
PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL));
|
|
}
|
|
|
|
DesiredAccess &= ~(WRITE_DAC | READ_CONTROL);
|
|
}
|
|
|
|
}
|
|
|
|
if (DesiredAccess == 0) {
|
|
|
|
try {
|
|
|
|
|
|
if ( ReturnResultList ) {
|
|
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
|
|
|
|
//
|
|
// Do not allow the request to go thru if the granted access
|
|
// evaluated to ZERO.
|
|
//
|
|
|
|
if (PreviouslyGrantedAccess == 0) {
|
|
AccessStatus[ResultListIndex] = STATUS_ACCESS_DENIED;
|
|
GrantedAccess[ResultListIndex] = 0;
|
|
} else {
|
|
AccessStatus[ResultListIndex] = STATUS_SUCCESS;
|
|
GrantedAccess[ResultListIndex] = PreviouslyGrantedAccess;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Do not allow the request to go thru if the granted access
|
|
// evaluated to ZERO.
|
|
//
|
|
|
|
if (PreviouslyGrantedAccess == 0) {
|
|
*AccessStatus = STATUS_ACCESS_DENIED;
|
|
*GrantedAccess = 0;
|
|
} else {
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
*GrantedAccess = PreviouslyGrantedAccess;
|
|
}
|
|
}
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
Status = GetExceptionCode();
|
|
|
|
}
|
|
|
|
SepReleaseTokenReadLock( Token );
|
|
|
|
SeReleaseSubjectContext( &SubjectContext );
|
|
|
|
SeReleaseSecurityDescriptor (
|
|
CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE
|
|
);
|
|
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Finally, handle the case where we actually have to check the DACL.
|
|
//
|
|
|
|
if ( ReturnResultList ) {
|
|
LocalGrantedAccessPointer =
|
|
ExAllocatePoolWithTag( PagedPool, (sizeof(ACCESS_MASK)+sizeof(NTSTATUS)) * ObjectTypeListLength, 'aGeS' );
|
|
|
|
if (LocalGrantedAccessPointer == NULL) {
|
|
|
|
SepReleaseTokenReadLock( Token );
|
|
|
|
SeReleaseSubjectContext( &SubjectContext );
|
|
|
|
SeReleaseSecurityDescriptor (
|
|
CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE
|
|
);
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
LocalAccessStatusPointer = (PNTSTATUS)(LocalGrantedAccessPointer + ObjectTypeListLength);
|
|
} else {
|
|
LocalGrantedAccessPointer = &LocalGrantedAccess;
|
|
LocalAccessStatusPointer = &LocalAccessStatus;
|
|
}
|
|
|
|
//
|
|
// This does not ask for privilege set to be returned so we can ignore
|
|
// the return value of the call.
|
|
//
|
|
|
|
(VOID) SepAccessCheck (
|
|
CapturedSecurityDescriptor,
|
|
CapturedPrincipalSelfSid,
|
|
SubjectContext.PrimaryToken,
|
|
Token,
|
|
DesiredAccess,
|
|
LocalObjectTypeList,
|
|
ObjectTypeListLength,
|
|
&LocalGenericMapping,
|
|
PreviouslyGrantedAccess,
|
|
PreviousMode,
|
|
LocalGrantedAccessPointer,
|
|
NULL,
|
|
LocalAccessStatusPointer,
|
|
ReturnResultList,
|
|
NULL,
|
|
NULL );
|
|
|
|
SepReleaseTokenReadLock( Token );
|
|
|
|
SeReleaseSubjectContext( &SubjectContext );
|
|
|
|
SeReleaseSecurityDescriptor (
|
|
CapturedSecurityDescriptor,
|
|
PreviousMode,
|
|
FALSE
|
|
);
|
|
|
|
try {
|
|
|
|
if ( ReturnResultList ) {
|
|
for ( ResultListIndex=0; ResultListIndex<ObjectTypeListLength; ResultListIndex++ ) {
|
|
AccessStatus[ResultListIndex] = LocalAccessStatusPointer[ResultListIndex];
|
|
GrantedAccess[ResultListIndex] = LocalGrantedAccessPointer[ResultListIndex];
|
|
}
|
|
|
|
} else {
|
|
*AccessStatus = *LocalAccessStatusPointer;
|
|
*GrantedAccess = *LocalGrantedAccessPointer;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
if ( ReturnResultList ) {
|
|
if ( LocalGrantedAccessPointer != NULL ) {
|
|
ExFreePool( LocalGrantedAccessPointer );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Free locally used resources.
|
|
//
|
|
Cleanup:
|
|
|
|
if ( Token != NULL ) {
|
|
ObDereferenceObject( Token );
|
|
}
|
|
|
|
if ( LocalObjectTypeList != NULL ) {
|
|
SeFreeCapturedObjectTypeList( LocalObjectTypeList );
|
|
}
|
|
|
|
if (CapturedPrincipalSelfSid != NULL) {
|
|
SeReleaseSid( CapturedPrincipalSelfSid, PreviousMode, TRUE);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
SeFreePrivileges(
|
|
IN PPRIVILEGE_SET Privileges
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees a privilege set returned by SeAccessCheck.
|
|
|
|
Arguments:
|
|
|
|
Privileges - Supplies a pointer to the privilege set to be freed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ExFreePool( Privileges );
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
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 OPTIONAL,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
IN KPROCESSOR_MODE AccessMode,
|
|
OUT PACCESS_MASK GrantedAccess,
|
|
OUT PNTSTATUS AccessStatus
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
See module abstract
|
|
|
|
This routine MAY perform tests for the following
|
|
privileges:
|
|
|
|
SeTakeOwnershipPrivilege
|
|
SeSecurityPrivilege
|
|
|
|
depending upon the accesses being requested.
|
|
|
|
This routine may also check to see if the subject is the owner
|
|
of the object (to grant WRITE_DAC access).
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the
|
|
object being accessed
|
|
|
|
SubjectSecurityContext - A pointer to the subject's captured security
|
|
context
|
|
|
|
SubjectContextLocked - Supplies a flag indiciating whether or not
|
|
the user's subject context is locked, so that it does not have
|
|
to be locked again.
|
|
|
|
DesiredAccess - Supplies the access mask that the user is attempting to
|
|
acquire
|
|
|
|
PreviouslyGrantedAccess - Supplies any accesses that the user has
|
|
already been granted, for example, as a result of holding a
|
|
privilege.
|
|
|
|
Privileges - Supplies a pointer in which will be returned a privilege
|
|
set indicating any privileges that were used as part of the
|
|
access validation.
|
|
|
|
GenericMapping - Supplies the generic mapping associated with this
|
|
object type.
|
|
|
|
AccessMode - Supplies the access mode to be used in the check
|
|
|
|
GrantedAccess - Pointer to a returned access mask indicatating the
|
|
granted access
|
|
|
|
AccessStatus - Status value that may be returned indicating the
|
|
reason why access was denied. Routines should avoid hardcoding a
|
|
return value of STATUS_ACCESS_DENIED so that a different value can
|
|
be returned when mandatory access control is implemented.
|
|
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if access is allowed and FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN Success;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (AccessMode == KernelMode) {
|
|
|
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|
|
|
//
|
|
// Give him:
|
|
// GenericAll
|
|
// Anything else he asked for
|
|
//
|
|
|
|
*GrantedAccess = GenericMapping->GenericAll;
|
|
*GrantedAccess |= (DesiredAccess & ~MAXIMUM_ALLOWED);
|
|
*GrantedAccess |= PreviouslyGrantedAccess;
|
|
|
|
} else {
|
|
|
|
*GrantedAccess = DesiredAccess | PreviouslyGrantedAccess;
|
|
}
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// If the object doesn't have a security descriptor (and it's supposed
|
|
// to), return access denied.
|
|
//
|
|
|
|
if ( SecurityDescriptor == NULL) {
|
|
|
|
*AccessStatus = STATUS_ACCESS_DENIED;
|
|
return( FALSE );
|
|
|
|
}
|
|
|
|
//
|
|
// If we're impersonating a client, we have to be at impersonation level
|
|
// of SecurityImpersonation or above.
|
|
//
|
|
|
|
if ( (SubjectSecurityContext->ClientToken != NULL) &&
|
|
(SubjectSecurityContext->ImpersonationLevel < SecurityImpersonation)
|
|
) {
|
|
*AccessStatus = STATUS_BAD_IMPERSONATION_LEVEL;
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( DesiredAccess == 0 ) {
|
|
|
|
if ( PreviouslyGrantedAccess == 0 ) {
|
|
*AccessStatus = STATUS_ACCESS_DENIED;
|
|
return( FALSE );
|
|
}
|
|
|
|
*GrantedAccess = PreviouslyGrantedAccess;
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
*Privileges = NULL;
|
|
return( TRUE );
|
|
|
|
}
|
|
|
|
SeAssertMappedCanonicalAccess( DesiredAccess );
|
|
|
|
|
|
//
|
|
// If the caller did not lock the subject context for us,
|
|
// lock it here to keep lower level routines from having
|
|
// to lock it.
|
|
//
|
|
|
|
if ( !SubjectContextLocked ) {
|
|
SeLockSubjectContext( SubjectSecurityContext );
|
|
}
|
|
|
|
//
|
|
// If the user in the token is the owner of the object, we
|
|
// must automatically grant ReadControl and WriteDac access
|
|
// if desired. If the DesiredAccess mask is empty after
|
|
// these bits are turned off, we don't have to do any more
|
|
// access checking (ref section 4, DSA ACL Arch)
|
|
//
|
|
|
|
if ( DesiredAccess & (WRITE_DAC | READ_CONTROL | MAXIMUM_ALLOWED) ) {
|
|
|
|
if ( SepTokenIsOwner(
|
|
EffectiveToken( SubjectSecurityContext ),
|
|
SecurityDescriptor,
|
|
TRUE
|
|
) ) {
|
|
|
|
if ( DesiredAccess & MAXIMUM_ALLOWED ) {
|
|
|
|
PreviouslyGrantedAccess |= (WRITE_DAC | READ_CONTROL);
|
|
|
|
} else {
|
|
|
|
PreviouslyGrantedAccess |= (DesiredAccess & (WRITE_DAC | READ_CONTROL));
|
|
}
|
|
|
|
DesiredAccess &= ~(WRITE_DAC | READ_CONTROL);
|
|
}
|
|
}
|
|
|
|
if (DesiredAccess == 0) {
|
|
|
|
if ( !SubjectContextLocked ) {
|
|
SeUnlockSubjectContext( SubjectSecurityContext );
|
|
}
|
|
|
|
*GrantedAccess = PreviouslyGrantedAccess;
|
|
*AccessStatus = STATUS_SUCCESS;
|
|
return( TRUE );
|
|
|
|
} else {
|
|
|
|
BOOLEAN b = SepAccessCheck(
|
|
SecurityDescriptor,
|
|
NULL, // No PrincipalSelfSid
|
|
SubjectSecurityContext->PrimaryToken,
|
|
SubjectSecurityContext->ClientToken,
|
|
DesiredAccess,
|
|
NULL, // No object type list
|
|
0, // No object type list
|
|
GenericMapping,
|
|
PreviouslyGrantedAccess,
|
|
AccessMode,
|
|
GrantedAccess,
|
|
Privileges,
|
|
AccessStatus,
|
|
FALSE, // Don't return a list
|
|
&Success,
|
|
NULL
|
|
);
|
|
#if DBG
|
|
if (!Success && SepShowAccessFail) {
|
|
DbgPrint("SE: Access check failed, DesiredAccess = 0x%x\n",
|
|
DesiredAccess);
|
|
SepDumpSD = TRUE;
|
|
SepDumpSecurityDescriptor(
|
|
SecurityDescriptor,
|
|
"Input to SeAccessCheck\n"
|
|
);
|
|
SepDumpSD = FALSE;
|
|
SepDumpToken = TRUE;
|
|
SepDumpTokenInfo( EffectiveToken( SubjectSecurityContext ) );
|
|
SepDumpToken = FALSE;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If we locked it in this routine, unlock it before we
|
|
// leave.
|
|
//
|
|
|
|
if ( !SubjectContextLocked ) {
|
|
SeUnlockSubjectContext( SubjectSecurityContext );
|
|
}
|
|
|
|
//
|
|
// We return failure if any of the following is TRUE
|
|
// 1. The user was really not granted access.
|
|
// 2. The resource manager asked for the list of privileges used to
|
|
// determine granted access and we failed to allocate memory
|
|
// required to return these.
|
|
//
|
|
|
|
return( b && Success );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SePrivilegePolicyCheck(
|
|
IN OUT PACCESS_MASK RemainingDesiredAccess,
|
|
IN OUT PACCESS_MASK PreviouslyGrantedAccess,
|
|
IN PSECURITY_SUBJECT_CONTEXT SubjectSecurityContext OPTIONAL,
|
|
IN PACCESS_TOKEN ExplicitToken OPTIONAL,
|
|
OUT PPRIVILEGE_SET *PrivilegeSet,
|
|
IN KPROCESSOR_MODE PreviousMode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements privilege policy by examining the bits in
|
|
a DesiredAccess mask and adjusting them based on privilege checks.
|
|
|
|
Currently, a request for ACCESS_SYSTEM_SECURITY may only be satisfied
|
|
by the caller having SeSecurityPrivilege. WRITE_OWNER may optionally
|
|
be satisfied via SeTakeOwnershipPrivilege.
|
|
|
|
Arguments:
|
|
|
|
RemainingDesiredAccess - The desired access for the current operation.
|
|
Bits may be cleared in this if the subject has particular privileges.
|
|
|
|
PreviouslyGrantedAccess - Supplies an access mask describing any
|
|
accesses that have already been granted. Bits may be set in
|
|
here as a result of privilge checks.
|
|
|
|
SubjectSecurityContext - Optionally provides the subject's security
|
|
context.
|
|
|
|
ExplicitToken - Optionally provides the token to be examined.
|
|
|
|
PrivilegeSet - Supplies a pointer to a location in which will be
|
|
returned a pointer to a privilege set.
|
|
|
|
PreviousMode - The previous processor mode.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Any access requests that could be satisfied via
|
|
privileges were done.
|
|
|
|
STATUS_PRIVILEGE_NOT_HELD - An access type was being requested that
|
|
requires a privilege, and the current subject did not have the
|
|
privilege.
|
|
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN Success;
|
|
PTOKEN Token;
|
|
BOOLEAN WriteOwner = FALSE;
|
|
BOOLEAN SystemSecurity = FALSE;
|
|
ULONG PrivilegeNumber = 0;
|
|
ULONG PrivilegeCount = 0;
|
|
ULONG SizeRequired;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (ARGUMENT_PRESENT( SubjectSecurityContext )) {
|
|
|
|
Token = (PTOKEN)EffectiveToken( SubjectSecurityContext );
|
|
|
|
} else {
|
|
|
|
Token = (PTOKEN)ExplicitToken;
|
|
}
|
|
|
|
|
|
if (*RemainingDesiredAccess & ACCESS_SYSTEM_SECURITY) {
|
|
|
|
Success = SepSinglePrivilegeCheck (
|
|
SeSecurityPrivilege,
|
|
Token,
|
|
PreviousMode
|
|
);
|
|
|
|
if (!Success) {
|
|
|
|
return( STATUS_PRIVILEGE_NOT_HELD );
|
|
}
|
|
|
|
PrivilegeCount++;
|
|
SystemSecurity = TRUE;
|
|
|
|
*RemainingDesiredAccess &= ~ACCESS_SYSTEM_SECURITY;
|
|
*PreviouslyGrantedAccess |= ACCESS_SYSTEM_SECURITY;
|
|
}
|
|
|
|
if (*RemainingDesiredAccess & WRITE_OWNER) {
|
|
|
|
Success = SepSinglePrivilegeCheck (
|
|
SeTakeOwnershipPrivilege,
|
|
Token,
|
|
PreviousMode
|
|
);
|
|
|
|
if (Success) {
|
|
|
|
PrivilegeCount++;
|
|
WriteOwner = TRUE;
|
|
|
|
*RemainingDesiredAccess &= ~WRITE_OWNER;
|
|
*PreviouslyGrantedAccess |= WRITE_OWNER;
|
|
|
|
}
|
|
}
|
|
|
|
if (PrivilegeCount > 0) {
|
|
SizeRequired = sizeof(PRIVILEGE_SET) +
|
|
(PrivilegeCount - ANYSIZE_ARRAY) *
|
|
(ULONG)sizeof(LUID_AND_ATTRIBUTES);
|
|
|
|
*PrivilegeSet = ExAllocatePoolWithTag( PagedPool, SizeRequired, 'rPeS' );
|
|
|
|
if ( *PrivilegeSet == NULL ) {
|
|
return( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
(*PrivilegeSet)->PrivilegeCount = PrivilegeCount;
|
|
(*PrivilegeSet)->Control = 0;
|
|
|
|
if (WriteOwner) {
|
|
(*PrivilegeSet)->Privilege[PrivilegeNumber].Luid = SeTakeOwnershipPrivilege;
|
|
(*PrivilegeSet)->Privilege[PrivilegeNumber].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS;
|
|
PrivilegeNumber++;
|
|
}
|
|
|
|
if (SystemSecurity) {
|
|
(*PrivilegeSet)->Privilege[PrivilegeNumber].Luid = SeSecurityPrivilege;
|
|
(*PrivilegeSet)->Privilege[PrivilegeNumber].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS;
|
|
}
|
|
}
|
|
|
|
return( STATUS_SUCCESS );
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
SepTokenIsOwner(
|
|
IN PACCESS_TOKEN EffectiveToken,
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN BOOLEAN TokenLocked
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will determine of the Owner of the passed security descriptor
|
|
is in the passed token. If the token is restricted it cannot be the
|
|
owner.
|
|
|
|
|
|
Arguments:
|
|
|
|
Token - The token representing the current user.
|
|
|
|
SecurityDescriptor - The security descriptor for the object being
|
|
accessed.
|
|
|
|
TokenLocked - A boolean describing whether the caller has taken
|
|
a read lock for the token.
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE - The user of the token is the owner of the object.
|
|
|
|
FALSE - The user of the token is not the owner of the object.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSID Owner;
|
|
BOOLEAN rc;
|
|
|
|
PISECURITY_DESCRIPTOR ISecurityDescriptor;
|
|
PTOKEN Token;
|
|
|
|
PAGED_CODE();
|
|
|
|
ISecurityDescriptor = (PISECURITY_DESCRIPTOR)SecurityDescriptor;
|
|
Token = (PTOKEN)EffectiveToken;
|
|
|
|
|
|
Owner = RtlpOwnerAddrSecurityDescriptor( ISecurityDescriptor );
|
|
ASSERT( Owner != NULL );
|
|
|
|
if (!TokenLocked) {
|
|
SepAcquireTokenReadLock( Token );
|
|
}
|
|
|
|
rc = SepSidInToken( Token, NULL, Owner, FALSE );
|
|
|
|
//
|
|
// For restricted tokens, check the restricted sids too.
|
|
//
|
|
|
|
if (rc && (Token->TokenFlags & TOKEN_IS_RESTRICTED) != 0) {
|
|
rc = SepSidInTokenEx( Token, NULL, Owner, FALSE, TRUE );
|
|
|
|
}
|
|
|
|
if (!TokenLocked) {
|
|
SepReleaseTokenReadLock( Token );
|
|
}
|
|
|
|
return( rc );
|
|
}
|
|
|
|
|
|
#define WORLD_TRAVERSAL_INCLUDES_ANONYMOUS 1
|
|
|
|
BOOLEAN
|
|
SeFastTraverseCheck(
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PACCESS_STATE AccessState,
|
|
IN ACCESS_MASK TraverseAccess,
|
|
IN KPROCESSOR_MODE AccessMode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will perform a fast check against the DACL of the passed
|
|
Security Descriptor to see if Traverse access would be granted. If so, no
|
|
further access checking is necessary.
|
|
|
|
Note that the SubjectContext for the client process does not have
|
|
to be locked to make this call, since it does not examine any data
|
|
structures in the Token.
|
|
|
|
The caller has the following responsibilities:
|
|
1. It is the job of the caller to verify AccessMode is not KernelMode!
|
|
2. The *caller* is expected to check AccessState for
|
|
TOKEN_HAS_TRAVERSE_PRIVILEGE if the override is applicable!
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - The Security Descriptor protecting the container
|
|
object being traversed.
|
|
|
|
AccessState - Running security access state containing caller token
|
|
information for operation.
|
|
|
|
TraverseAccess - Access mask describing Traverse access for this
|
|
object type. There must be only one bit specified in the mask.
|
|
|
|
AccessMode - Supplies the access mode to be used in the check
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE - if Traverse access to this container can be granted. FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PACL Dacl;
|
|
ULONG i;
|
|
PVOID Ace;
|
|
ULONG AceCount;
|
|
#if !WORLD_TRAVERSAL_INCLUDES_ANONYMOUS
|
|
LOGICAL FoundWorld;
|
|
LOGICAL FoundAnonymous;
|
|
#endif
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Note that I/O calls this function even if the traverse bypass privilege
|
|
// is set. This is done as I/O doesn't want the performance override to
|
|
// apply to the DeviceObject->FileName boundary, as not all filesystems
|
|
// supply file-level security.
|
|
//
|
|
// ASSERT( (!ARGUMENT_PRESENT(AccessState)) ||
|
|
// (!(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE)) );
|
|
//
|
|
|
|
ASSERT ( AccessMode != KernelMode );
|
|
|
|
if (SecurityDescriptor == NULL) {
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// See if there is a valid DACL in the passed Security Descriptor.
|
|
// No DACL, no security, all is granted. Note that this function returns
|
|
// NULL if SE_DACL_PRESENT isn't set.
|
|
//
|
|
|
|
Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor );
|
|
|
|
//
|
|
// If no DACL is supplied, the object has no security, so all accesses are
|
|
// granted.
|
|
//
|
|
|
|
if ( Dacl == NULL ) {
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// There is security on this object. If the DACL is empty, deny all access
|
|
// immediately
|
|
//
|
|
|
|
if ((AceCount = Dacl->AceCount) == 0) {
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// A restricted token is a token with two lists of SIDs. Access checks are
|
|
// done against each list, passing only if both lists grant access. The
|
|
// second "restricting SID" list can contain *any allow* SID.
|
|
//
|
|
// This routine doesn't walk a restricted token's restricting-SID list. As
|
|
// such, restricted tokens require a full access check.
|
|
//
|
|
if (AccessState->Flags & TOKEN_IS_RESTRICTED) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// There's stuff in the DACL, walk down the list and see
|
|
// if both Everyone and Anonymous have been granted TraverseAccess
|
|
//
|
|
#if !WORLD_TRAVERSAL_INCLUDES_ANONYMOUS
|
|
FoundWorld = FALSE;
|
|
FoundAnonymous = FALSE;
|
|
#endif
|
|
|
|
for ( i = 0, Ace = FirstAce( Dacl ) ;
|
|
i < AceCount ;
|
|
i++, Ace = NextAce( Ace )
|
|
) {
|
|
|
|
if (((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE) {
|
|
|
|
continue;
|
|
}
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
|
|
|
|
if ( (TraverseAccess & ((PACCESS_ALLOWED_ACE)Ace)->Mask) ) {
|
|
|
|
if ( RtlEqualSid( SeWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) {
|
|
#if WORLD_TRAVERSAL_INCLUDES_ANONYMOUS
|
|
return( TRUE );
|
|
}
|
|
#else
|
|
if (FoundAnonymous) {
|
|
|
|
return( TRUE );
|
|
|
|
} else {
|
|
|
|
FoundWorld = TRUE;
|
|
}
|
|
|
|
} else if ( RtlEqualSid( SeAnonymousLogonSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) {
|
|
|
|
if (FoundWorld) {
|
|
|
|
return( TRUE );
|
|
|
|
} else {
|
|
|
|
FoundAnonymous = TRUE;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) {
|
|
|
|
if ( (TraverseAccess & ((PACCESS_DENIED_ACE)Ace)->Mask) ) {
|
|
|
|
//
|
|
// This ACE might refer to a group the user belongs to
|
|
// (or could be the user). Force a full access check.
|
|
//
|
|
|
|
return( FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
#ifdef SE_NTFS_WORLD_CACHE
|
|
|
|
/*++
|
|
|
|
Note: Do not delete SeGetWorldRights. It might be used by NTFS in future.
|
|
|
|
When that happens:
|
|
|
|
- Add this line to #ifdef ALLOC_PRAGMA.
|
|
#pragma alloc_text(PAGE,SeGetWorldRights)
|
|
|
|
- Uncomment the function prototype declaration in ntos\inc\se.h
|
|
|
|
KedarD - 07/05/2000
|
|
|
|
--*/
|
|
|
|
|
|
|
|
VOID
|
|
SeGetWorldRights (
|
|
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
IN PGENERIC_MAPPING GenericMapping,
|
|
OUT PACCESS_MASK GrantedAccess
|
|
)
|
|
/*++
|
|
|
|
Routine Descriptions:
|
|
|
|
This call acquires the minimum rights that are available to all tokens.
|
|
This takes into account all deny access ACE(s) that would reduce the
|
|
rights granted to an ACE for Everyone.
|
|
|
|
Arguments:
|
|
|
|
SecurityDescriptor - Supplies the security descriptor protecting the
|
|
object being accessed
|
|
|
|
GenericMapping - Supplies a pointer to the generic mapping associated
|
|
with this object type.
|
|
|
|
GrantedAccess - Returns an access mask describing the granted access.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
ACCESS_MASK AlreadyDenied;
|
|
PACL Dacl;
|
|
PVOID Ace;
|
|
ULONG AceCount = 0;
|
|
ULONG Index;
|
|
|
|
PAGED_CODE();
|
|
|
|
*GrantedAccess = 0;
|
|
|
|
//
|
|
// Get a pointer to the ACL.
|
|
//
|
|
|
|
Dacl = RtlpDaclAddrSecurityDescriptor( (PISECURITY_DESCRIPTOR)SecurityDescriptor );
|
|
|
|
//
|
|
// If the SE_DACL_PRESENT bit is not set, the object has no
|
|
// security, so all accesses are granted. If he's asking for
|
|
// MAXIMUM_ALLOWED, return the GENERIC_ALL field from the generic
|
|
// mapping.
|
|
//
|
|
// Also grant all access if the Dacl is NULL.
|
|
//
|
|
|
|
if ( (Dacl == NULL) ||
|
|
!RtlpAreControlBitsSet( (PISECURITY_DESCRIPTOR)SecurityDescriptor,
|
|
SE_DACL_PRESENT) ) {
|
|
|
|
#ifndef SECURE_NULL_DACLS
|
|
|
|
//
|
|
// Grant all access.
|
|
//
|
|
|
|
*GrantedAccess = GenericMapping->GenericAll;
|
|
|
|
#endif //!SECURE_NULL_DACLS
|
|
|
|
} else {
|
|
|
|
AceCount = Dacl->AceCount;
|
|
}
|
|
|
|
for ( Index = 0, Ace = FirstAce( Dacl ), AlreadyDenied = 0 ;
|
|
Index < AceCount ;
|
|
Index += 1, Ace = NextAce( Ace )
|
|
) {
|
|
|
|
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
|
|
|
|
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
|
|
|
|
if ( RtlEqualSid( SeWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ) ) {
|
|
|
|
//
|
|
// Only grant access types from this mask that have
|
|
// not already been denied
|
|
//
|
|
|
|
*GrantedAccess |=
|
|
(((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied);
|
|
}
|
|
|
|
//
|
|
// Handle an object specific Access Allowed ACE
|
|
//
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_OBJECT_ACE_TYPE) ) {
|
|
|
|
//
|
|
// If no object type is in the ACE,
|
|
// treat this as an ACCESS_ALLOWED_ACE.
|
|
//
|
|
|
|
if ( RtlObjectAceObjectType( Ace ) == NULL ) {
|
|
|
|
if ( RtlEqualSid( SeWorldSid, RtlObjectAceSid(Ace) ) ) {
|
|
|
|
*GrantedAccess |=
|
|
(((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied);
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_COMPOUND_ACE_TYPE) ) {
|
|
|
|
if ( RtlEqualSid( SeWorldSid, RtlCompoundAceClientSid(Ace) ) &&
|
|
RtlEqualSid( SeWorldSid, RtlCompoundAceServerSid(Ace) ) ) {
|
|
|
|
//
|
|
// Only grant access types from this mask that have
|
|
// not already been denied
|
|
//
|
|
|
|
*GrantedAccess |=
|
|
(((PACCESS_ALLOWED_ACE)Ace)->Mask & ~AlreadyDenied);
|
|
}
|
|
|
|
|
|
} else if ( ( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_ACE_TYPE) ) ||
|
|
( (((PACE_HEADER)Ace)->AceType == ACCESS_DENIED_OBJECT_ACE_TYPE) ) ) {
|
|
|
|
//
|
|
// We include all of the deny access ACE(s), regardless of to
|
|
// what SID they apply.
|
|
//
|
|
|
|
//
|
|
// Only deny access types from this mask that have
|
|
// not already been granted
|
|
//
|
|
|
|
AlreadyDenied |= (((PACCESS_DENIED_ACE)Ace)->Mask & ~*GrantedAccess);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|