/*++ Module Name: SeAstate.c Abstract: This Module implements the privilege check procedures. Author: Robert Reichel (robertre) 20-March-90 Environment: Kernel Mode Revision History: v1: robertre new file, move Access State related routines here --*/ #include "pch.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,SeCreateAccessState) #pragma alloc_text(PAGE,SeDeleteAccessState) #pragma alloc_text(PAGE,SeSetAccessStateGenericMapping) #pragma alloc_text(PAGE,SeAppendPrivileges) #pragma alloc_text(PAGE,SepConcatenatePrivileges) #endif // // Define logical sum of all generic accesses. // #define GENERIC_ACCESS (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL) // // The PRIVILEGE_SET data structure includes an array including ANYSIZE_ARRAY // elements. This definition provides the size of an empty PRIVILEGE_SET // (i.e., one with no privileges in it). // #define SEP_PRIVILEGE_SET_HEADER_SIZE \ ((ULONG)sizeof(PRIVILEGE_SET) - \ (ANYSIZE_ARRAY * (ULONG)sizeof(LUID_AND_ATTRIBUTES))) #if 0 NTSTATUS SeCreateAccessState( IN PACCESS_STATE AccessState, IN ACCESS_MASK DesiredAccess, IN PGENERIC_MAPPING GenericMapping OPTIONAL ) /*++ Routine Description: This routine initializes an ACCESS_STATE structure. This consists of: - zeroing the entire structure - mapping generic access types in the passed DesiredAccess and putting it into the structure - "capturing" the Subject Context, which must be held for the duration of the access attempt (at least until auditing is performed). - Allocating an Operation ID, which is an LUID that will be used to associate different parts of the access attempt in the audit log. Arguments: AccessState - a pointer to the structure to be initialized. DesiredAccess - Access mask containing the desired access GenericMapping - Optionally supplies a pointer to a generic mapping that may be used to map any generic access requests that may have been passed in the DesiredAccess parameter. Note that if this parameter is not supplied, it must be filled in at some later point. The IO system does this in IopParseDevice. Return Value: Error if the attempt to allocate an LUID fails. Note that this error may be safely ignored if it is known that all security checks will be performed with PreviousMode == KernelMode. Know what you're doing if you choose to ignore this. --*/ { ACCESS_MASK MappedAccessMask; PSECURITY_DESCRIPTOR InputSecurityDescriptor = NULL; PAUX_ACCESS_DATA AuxData; PAGED_CODE(); // // Don't modify what he passed in // MappedAccessMask = DesiredAccess; // // Map generic access to object specific access iff generic access types // are specified and a generic access mapping table is provided. // if ( ((DesiredAccess & GENERIC_ACCESS) != 0) && ARGUMENT_PRESENT(GenericMapping) ) { RtlMapGenericMask( &MappedAccessMask, GenericMapping ); } RtlZeroMemory(AccessState, sizeof(ACCESS_STATE)); // // Assume RtlZeroMemory has initialized these fields properly // ASSERT( AccessState->SecurityDescriptor == NULL ); ASSERT( AccessState->PrivilegesAllocated == FALSE ); AccessState->AuxData = ExAllocatePool( PagedPool, sizeof( AUX_ACCESS_DATA )); if (AccessState->AuxData == NULL) { return( STATUS_NO_MEMORY ); } AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; SeCaptureSubjectContext(&AccessState->SubjectSecurityContext); if (((PTOKEN)EffectiveToken( &AccessState->SubjectSecurityContext ))->TokenFlags & TOKEN_HAS_TRAVERSE_PRIVILEGE ) { AccessState->Flags = TOKEN_HAS_TRAVERSE_PRIVILEGE; } if (SeTokenIsRestricted(EffectiveToken( &AccessState-SubjectSecurityContext))) { AccessState->Flags |= TOKEN_IS_RESTRICTED; } AccessState->RemainingDesiredAccess = MappedAccessMask; AccessState->OriginalDesiredAccess = DesiredAccess; AuxData->PrivilegesUsed = (PPRIVILEGE_SET)((PUCHAR)AccessState + (FIELD_OFFSET(ACCESS_STATE, Privileges))); ExAllocateLocallyUniqueId(&AccessState->OperationID); if (ARGUMENT_PRESENT(GenericMapping)) { AuxData->GenericMapping = *GenericMapping; } return( STATUS_SUCCESS ); } #endif NTSTATUS SeCreateAccessState( IN PACCESS_STATE AccessState, IN PAUX_ACCESS_DATA AuxData, IN ACCESS_MASK DesiredAccess, IN PGENERIC_MAPPING GenericMapping OPTIONAL ) /*++ Routine Description: This routine initializes an ACCESS_STATE structure. This consists of: - zeroing the entire structure - mapping generic access types in the passed DesiredAccess and putting it into the structure - "capturing" the Subject Context, which must be held for the duration of the access attempt (at least until auditing is performed). - Allocating an Operation ID, which is an LUID that will be used to associate different parts of the access attempt in the audit log. Arguments: AccessState - a pointer to the structure to be initialized. AuxData - Supplies a buffer big enough for an AuxData structure so we don't have to allocate one. DesiredAccess - Access mask containing the desired access GenericMapping - Optionally supplies a pointer to a generic mapping that may be used to map any generic access requests that may have been passed in the DesiredAccess parameter. Note that if this parameter is not supplied, it must be filled in at some later point. The IO system does this in IopParseDevice. Return Value: Error if the attempt to allocate an LUID fails. Note that this error may be safely ignored if it is known that all security checks will be performed with PreviousMode == KernelMode. Know what you're doing if you choose to ignore this. --*/ { ACCESS_MASK MappedAccessMask; PSECURITY_DESCRIPTOR InputSecurityDescriptor = NULL; PAGED_CODE(); // // Don't modify what he passed in // MappedAccessMask = DesiredAccess; // // Map generic access to object specific access iff generic access types // are specified and a generic access mapping table is provided. // if ( ((DesiredAccess & GENERIC_ACCESS) != 0) && ARGUMENT_PRESENT(GenericMapping) ) { RtlMapGenericMask( &MappedAccessMask, GenericMapping ); } RtlZeroMemory(AccessState, sizeof(ACCESS_STATE)); RtlZeroMemory(AuxData, sizeof(AUX_ACCESS_DATA)); // // Assume RtlZeroMemory has initialized these fields properly // ASSERT( AccessState->SecurityDescriptor == NULL ); ASSERT( AccessState->PrivilegesAllocated == FALSE ); AccessState->AuxData = AuxData; SeCaptureSubjectContext(&AccessState->SubjectSecurityContext); if (((PTOKEN)EffectiveToken( &AccessState->SubjectSecurityContext ))->TokenFlags & TOKEN_HAS_TRAVERSE_PRIVILEGE ) { AccessState->Flags = TOKEN_HAS_TRAVERSE_PRIVILEGE; } AccessState->RemainingDesiredAccess = MappedAccessMask; AccessState->OriginalDesiredAccess = MappedAccessMask; AuxData->PrivilegesUsed = (PPRIVILEGE_SET)((ULONG_PTR)AccessState + (FIELD_OFFSET(ACCESS_STATE, Privileges))); ExAllocateLocallyUniqueId(&AccessState->OperationID); if (ARGUMENT_PRESENT(GenericMapping)) { AuxData->GenericMapping = *GenericMapping; } return( STATUS_SUCCESS ); } #if 0 VOID SeDeleteAccessState( PACCESS_STATE AccessState ) /*++ Routine Description: This routine deallocates any memory that may have been allocated as part of constructing the access state (normally only for an excessive number of privileges), and frees the Subject Context. Arguments: AccessState - a pointer to the ACCESS_STATE structure to be deallocated. Return Value: None. --*/ { PAUX_ACCESS_DATA AuxData; PAGED_CODE(); AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; if (AccessState->PrivilegesAllocated) { ExFreePool( (PVOID)AuxData->PrivilegesUsed ); } if (AccessState->ObjectName.Buffer != NULL) { ExFreePool(AccessState->ObjectName.Buffer); } if (AccessState->ObjectTypeName.Buffer != NULL) { ExFreePool(AccessState->ObjectTypeName.Buffer); } ExFreePool( AuxData ); SeReleaseSubjectContext(&AccessState->SubjectSecurityContext); return; } #endif VOID SeDeleteAccessState( PACCESS_STATE AccessState ) /*++ Routine Description: This routine deallocates any memory that may have been allocated as part of constructing the access state (normally only for an excessive number of privileges), and frees the Subject Context. Arguments: AccessState - a pointer to the ACCESS_STATE structure to be deallocated. Return Value: None. --*/ { PAUX_ACCESS_DATA AuxData; PAGED_CODE(); AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; if (AccessState->PrivilegesAllocated) { ExFreePool( (PVOID)AuxData->PrivilegesUsed ); } if (AccessState->ObjectName.Buffer != NULL) { ExFreePool(AccessState->ObjectName.Buffer); } if (AccessState->ObjectTypeName.Buffer != NULL) { ExFreePool(AccessState->ObjectTypeName.Buffer); } SeReleaseSubjectContext(&AccessState->SubjectSecurityContext); return; } VOID SeSetAccessStateGenericMapping ( PACCESS_STATE AccessState, PGENERIC_MAPPING GenericMapping ) /*++ Routine Description: This routine sets the GenericMapping field in an AccessState structure. It must be called before access validation is performed if the GenericMapping is not passed in when the AccessState structure is created. Arguments: AccessState - a pointer to the ACCESS_STATE structure to be modified. GenericMapping - a pointer to the GenericMapping to be copied into the AccessState. Return Value: --*/ { PAUX_ACCESS_DATA AuxData; PAGED_CODE(); AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; AuxData->GenericMapping = *GenericMapping; return; } NTSTATUS SeAppendPrivileges( PACCESS_STATE AccessState, PPRIVILEGE_SET Privileges ) /*++ Routine Description: This routine takes a privilege set and adds it to the privilege set imbedded in an ACCESS_STATE structure. An AccessState may contain up to three imbedded privileges. To add more, this routine will allocate a block of memory, copy the current privileges into it, and append the new privilege to that block. A bit is set in the AccessState indicating that the pointer to the privilge set in the structure points to pool memory and must be deallocated. Arguments: AccessState - The AccessState structure representing the current access attempt. Privileges - A pointer to a privilege set to be added. Return Value: STATUS_INSUFFICIENT_RESOURCES - an attempt to allocate pool memory failed. --*/ { ULONG NewPrivilegeSetSize; PPRIVILEGE_SET NewPrivilegeSet; PAUX_ACCESS_DATA AuxData; PAGED_CODE(); AuxData = (PAUX_ACCESS_DATA)AccessState->AuxData; if (Privileges->PrivilegeCount + AuxData->PrivilegesUsed->PrivilegeCount > INITIAL_PRIVILEGE_COUNT) { // // Compute the total size of the two privilege sets // NewPrivilegeSetSize = SepPrivilegeSetSize( Privileges ) + SepPrivilegeSetSize( AuxData->PrivilegesUsed ); NewPrivilegeSet = ExAllocatePoolWithTag( PagedPool, NewPrivilegeSetSize, 'rPeS' ); if (NewPrivilegeSet == NULL) { return( STATUS_INSUFFICIENT_RESOURCES ); } RtlCopyMemory( NewPrivilegeSet, AuxData->PrivilegesUsed, SepPrivilegeSetSize( AuxData->PrivilegesUsed ) ); // // Note that this will adjust the privilege count in the // structure for us. // SepConcatenatePrivileges( NewPrivilegeSet, NewPrivilegeSetSize, Privileges ); if (AccessState->PrivilegesAllocated) { ExFreePool( AuxData->PrivilegesUsed ); } AuxData->PrivilegesUsed = NewPrivilegeSet; // // Mark that we've allocated memory for the privilege set, // so we know to free it when we're cleaning up. // AccessState->PrivilegesAllocated = TRUE; } else { // // Note that this will adjust the privilege count in the // structure for us. // SepConcatenatePrivileges( AuxData->PrivilegesUsed, sizeof(INITIAL_PRIVILEGE_SET), Privileges ); } return( STATUS_SUCCESS ); } VOID SepConcatenatePrivileges( IN PPRIVILEGE_SET TargetPrivilegeSet, IN ULONG TargetBufferSize, IN PPRIVILEGE_SET SourcePrivilegeSet ) /*++ Routine Description: Takes two privilege sets and appends the second to the end of the first. There must be enough space left at the end of the first privilege set to contain the second. Arguments: TargetPrivilegeSet - Supplies a buffer containing a privilege set. The buffer must be large enough to contain the second privilege set. TargetBufferSize - Supplies the size of the target buffer. SourcePrivilegeSet - Supplies the privilege set to be copied into the target buffer. Return Value: None --*/ { PVOID Base; PVOID Source; ULONG Length; PAGED_CODE(); ASSERT( ((ULONG)SepPrivilegeSetSize( TargetPrivilegeSet ) + (ULONG)SepPrivilegeSetSize( SourcePrivilegeSet ) - SEP_PRIVILEGE_SET_HEADER_SIZE ) <= TargetBufferSize ); Base = (PVOID)((ULONG_PTR)TargetPrivilegeSet + SepPrivilegeSetSize( TargetPrivilegeSet )); Source = (PVOID) ((ULONG_PTR)SourcePrivilegeSet + SEP_PRIVILEGE_SET_HEADER_SIZE); Length = SourcePrivilegeSet->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); RtlMoveMemory( Base, Source, Length ); TargetPrivilegeSet->PrivilegeCount += SourcePrivilegeSet->PrivilegeCount; }