/*++ Copyright (c) 1989 Microsoft Corporation Module Name: obsdata.c Abstract: Object Manager Security Descriptor Caching Author: Robert Reichel (robertre) 12-Oct-1993 Revision History: --*/ #include "obp.h" #if DBG #define OB_DIAGNOSTICS_ENABLED 1 #endif // DBG // // These definitions are useful diagnostics aids // #if OB_DIAGNOSTICS_ENABLED ULONG ObDebugFlags = 0; // // Test for enabled diagnostic // #define IF_OB_GLOBAL( FlagName ) \ if (ObDebugFlags & (OB_DEBUG_##FlagName)) // // Diagnostics print statement // #define ObPrint( FlagName, _Text_ ) \ IF_OB_GLOBAL( FlagName ) \ DbgPrint _Text_ #else // // diagnostics not enabled - No diagnostics included in build // // // Test for diagnostics enabled // #define IF_OB_GLOBAL( FlagName ) if (FALSE) // // Diagnostics print statement (expands to no-op) // #define ObPrint( FlagName, _Text_ ) ; #endif // OB_DIAGNOSTICS_ENABLED #if OB_DIAGNOSTICS_ENABLED ULONG TotalCacheEntries = 0; #endif // // The following flags enable or disable various diagnostic // capabilities within OB code. These flags are set in // ObGlobalFlag (only available within a DBG system). // // #define OB_DEBUG_ALLOC_TRACKING ((ULONG) 0x00000001L) #define OB_DEBUG_CACHE_FREES ((ULONG) 0x00000002L) #define OB_DEBUG_BREAK_ON_INIT ((ULONG) 0x00000004L) #define OB_DEBUG_SHOW_COLLISIONS ((ULONG) 0x00000008L) #define OB_DEBUG_SHOW_STATISTICS ((ULONG) 0x00000010L) #define OB_DEBUG_SHOW_REFERENCES ((ULONG) 0x00000020L) #define OB_DEBUG_SHOW_DEASSIGN ((ULONG) 0x00000040L) #define OB_DEBUG_STOP_INVALID_DESCRIPTOR ((ULONG) 0x00000080L) #define OB_DEBUG_SHOW_HEADER_FREE ((ULONG) 0x00000100L) // // Array of pointers to security descriptor entries // PLIST_ENTRY *SecurityDescriptorCache = NULL; // // Resource used to protect the security descriptor cache // ERESOURCE SecurityDescriptorCacheLock; #if defined (ALLOC_PRAGMA) #pragma alloc_text(PAGE,ObpDereferenceSecurityDescriptor) #pragma alloc_text(PAGE,ObpDestroySecurityDescriptorHeader) #pragma alloc_text(PAGE,ObpHashBuffer) #pragma alloc_text(PAGE,ObpHashSecurityDescriptor) #pragma alloc_text(PAGE,ObpInitSecurityDescriptorCache) #pragma alloc_text(PAGE,ObpLogSecurityDescriptor) #pragma alloc_text(PAGE,ObpReferenceSecurityDescriptor) #pragma alloc_text(PAGE,OpbCreateCacheEntry) #endif NTSTATUS ObpInitSecurityDescriptorCache( VOID ) /*++ Routine Description: Allocates and initializes the Security Descriptor Cache Arguments: None Return Value: STATUS_SUCCESS on success, NTSTATUS on failure. --*/ { ULONG Size; NTSTATUS Status; IF_OB_GLOBAL( BREAK_ON_INIT ) { DbgBreakPoint(); } Size = SECURITY_DESCRIPTOR_CACHE_ENTRIES * sizeof(PLIST_ENTRY); SecurityDescriptorCache = ExAllocatePoolWithTag( PagedPool, Size, 'cCdS' ); if (SecurityDescriptorCache == NULL ) { return( STATUS_INSUFFICIENT_RESOURCES ); } RtlZeroMemory( SecurityDescriptorCache, Size ); Status = ExInitializeResource ( &SecurityDescriptorCacheLock ); if ( !NT_SUCCESS(Status) ) { ExFreePool( SecurityDescriptorCache ); return( Status ); } return( STATUS_SUCCESS ); } ULONG ObpHashSecurityDescriptor( PSECURITY_DESCRIPTOR SecurityDescriptor ) /*++ Routine Description: Hashes a security descriptor to a 32 bit value Arguments: SecurityDescriptor - Provides the security descriptor to be hashed Return Value: ULONG - a 32 bit hash value. --*/ { PSID Owner = NULL; PSID Group = NULL; PACL Dacl; PACL Sacl; ULONG Hash = 0; BOOLEAN Junk; NTSTATUS Status; BOOLEAN DaclPresent = FALSE; BOOLEAN SaclPresent = FALSE; PISECURITY_DESCRIPTOR sd; sd = (PISECURITY_DESCRIPTOR)SecurityDescriptor; Status = RtlGetOwnerSecurityDescriptor ( sd, &Owner, &Junk ); Status = RtlGetGroupSecurityDescriptor( sd, &Group, &Junk ); Status = RtlGetDaclSecurityDescriptor ( sd, &DaclPresent, &Dacl, &Junk ); Status = RtlGetSaclSecurityDescriptor ( sd, &SaclPresent, &Sacl, &Junk ); if ( Owner != NULL ) { Hash = ObpHashBuffer( Owner, RtlLengthSid( Owner )); } if ( Group != NULL ) { Hash += ObpHashBuffer( Group, RtlLengthSid( Group)); } if ( DaclPresent && (Dacl != NULL)) { Hash += ObpHashBuffer( Dacl, Dacl->AclSize); } if ( SaclPresent && (Sacl != NULL)) { Hash += ObpHashBuffer( Sacl, Sacl->AclSize); } return( Hash ); } ULONG ObpHashBuffer( PVOID Data, ULONG Length ) /*++ Routine Description: Hashes a buffer into a 32 bit value Arguments: Data - Buffer containing the data to be hashed. Length - The length in bytes of the buffer Return Value: ULONG - a 32 bit hash value. --*/ { PCHAR Buffer; ULONG Result = 0; LONG i; Buffer = (PCHAR)Data; for (i=0 ; i<=(LONG)((Length-3)-sizeof(ULONG)) ; i++) { ULONG Tmp; Tmp = *((ULONG UNALIGNED *)(Buffer + i)); Result += Tmp; } return( Result ); } NTSTATUS ObpLogSecurityDescriptor( IN PSECURITY_DESCRIPTOR InputSecurityDescriptor, OUT PSECURITY_DESCRIPTOR *OutputSecurityDescriptor ) /*++ Routine Description: Takes a passed security descriptor and registers it into the security descriptor database. Arguments: InputSecurityDescriptor - The new security descriptor to be logged into the database. OutputSecurityDescriptor - Output security descriptor to be used by the caller. Return Value: NT_STATUS --*/ { ULONG FullHash; UCHAR SmallHash; PSECURITY_DESCRIPTOR_HEADER NewDescriptor; PLIST_ENTRY Front; PLIST_ENTRY Back; PSECURITY_DESCRIPTOR_HEADER Header; BOOLEAN Match; FullHash = ObpHashSecurityDescriptor( InputSecurityDescriptor ); SmallHash = (UCHAR)FullHash; // // See if the entry matching SmallHash is in use. // Lock the table first, unlock if if we don't need it. // ObpAcquireDescriptorCacheWriteLock(); Front = SecurityDescriptorCache[SmallHash]; Back = NULL; Match = FALSE; while ( Front != NULL ) { Header = LINK_TO_SD_HEADER( Front ); if ( Header->FullHash > FullHash ) { break; } if ( Header->FullHash == FullHash ) { Match = ObpCompareSecurityDescriptors( InputSecurityDescriptor, &Header->SecurityDescriptor ); if ( Match ) { break; } ObPrint( SHOW_COLLISIONS,("Got a collision on %d, no match\n",SmallHash)); } Back = Front; Front = Front->Flink; } if ( Match ) { Header->RefCount++; ObPrint( SHOW_REFERENCES, ("Reference Index = %d, New RefCount = %d\n",Header->Index,Header->RefCount)); *OutputSecurityDescriptor = &Header->SecurityDescriptor; ExFreePool( InputSecurityDescriptor ); ObpReleaseDescriptorCacheLock(); return( STATUS_SUCCESS ); } // // Can't use an existing one, create a new entry // and insert it into the list. // NewDescriptor = OpbCreateCacheEntry( InputSecurityDescriptor, FullHash, SmallHash ); if ( NewDescriptor == NULL ) { ObpReleaseDescriptorCacheLock(); return( STATUS_INSUFFICIENT_RESOURCES ); } #if OB_DIAGNOSTICS_ENABLED TotalCacheEntries++; #endif ObPrint( SHOW_STATISTICS, ("TotalCacheEntries = %d \n",TotalCacheEntries)); ObPrint( SHOW_COLLISIONS, ("Adding new entry for index #%d \n",SmallHash)); // // We don't need the old security descriptor any more. // ExFreePool( InputSecurityDescriptor ); if ( Back == NULL ) { // // We're inserting at the beginning of the list for this // minor index // NewDescriptor->Link.Flink = SecurityDescriptorCache[SmallHash]; SecurityDescriptorCache[SmallHash] = &NewDescriptor->Link; if ( NewDescriptor->Link.Flink != NULL ) { NewDescriptor->Link.Flink->Blink = &NewDescriptor->Link; } } else { // // Hook new descriptor entry into list. // NewDescriptor->Link.Flink = Front; NewDescriptor->Link.Blink = Back; Back->Flink = &NewDescriptor->Link; if (Front != NULL) { Front->Blink = &NewDescriptor->Link; } } *OutputSecurityDescriptor = &NewDescriptor->SecurityDescriptor; ObpReleaseDescriptorCacheLock(); return( STATUS_SUCCESS ); } PSECURITY_DESCRIPTOR_HEADER OpbCreateCacheEntry( PSECURITY_DESCRIPTOR InputSecurityDescriptor, ULONG FullHash, UCHAR SmallHash ) /*++ Routine Description: Allocates and initializes a new cache entry. Arguments: InputSecurityDescriptor - The security descriptor to be cached. FullHash - Full 32 bit hash of the security descriptor. SmallHash - Index into the cache table. Return Value: A pointer to the newly allocated cache entry, or NULL --*/ { ULONG SecurityDescriptorLength; ULONG CacheEntrySize; PSECURITY_DESCRIPTOR_HEADER NewDescriptor; SecurityDescriptorLength = RtlLengthSecurityDescriptor ( InputSecurityDescriptor ); CacheEntrySize = SecurityDescriptorLength + (sizeof(SECURITY_DESCRIPTOR_HEADER) - sizeof( QUAD )); NewDescriptor = ExAllocatePoolWithTag( PagedPool, CacheEntrySize, 'dSeS'); if ( NewDescriptor == NULL ) { return( NULL ); } NewDescriptor->Index = SmallHash; NewDescriptor->RefCount = 1; NewDescriptor->FullHash = FullHash; NewDescriptor->Link.Flink = NULL; NewDescriptor->Link.Blink = NULL; RtlCopyMemory( &NewDescriptor->SecurityDescriptor, InputSecurityDescriptor, SecurityDescriptorLength ); return( NewDescriptor ); } PSECURITY_DESCRIPTOR ObpReferenceSecurityDescriptor( PVOID Object ) /*++ Routine Description: References the security descriptor of the passed object. Arguments: Object - Object being access validated. Return Value: The security descriptor of the object. --*/ { PSECURITY_DESCRIPTOR_HEADER SecurityDescriptorHeader; POBJECT_HEADER ObjectHeader; POBJECT_TYPE ObjectType; PSECURITY_DESCRIPTOR SecurityDescriptor; ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object ); ObjectType = ObjectHeader->Type; ASSERT( ObpCentralizedSecurity(ObjectType) ); ObpAcquireDescriptorCacheWriteLock(); SecurityDescriptor = OBJECT_TO_OBJECT_HEADER( Object )->SecurityDescriptor; IF_OB_GLOBAL( STOP_INVALID_DESCRIPTOR ) { if( !RtlValidSecurityDescriptor ( SecurityDescriptor )) { DbgBreakPoint(); } } if ( SecurityDescriptor != NULL ) { SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor ); ObPrint( SHOW_REFERENCES, ("Referencing index #%d, Refcount = %d \n",SecurityDescriptorHeader->Index,SecurityDescriptorHeader->RefCount)); SecurityDescriptorHeader->RefCount++; } ObpReleaseDescriptorCacheLock(); return( SecurityDescriptor ); } NTSTATUS ObDeassignSecurity ( IN OUT PSECURITY_DESCRIPTOR *SecurityDescriptor ) { PSECURITY_DESCRIPTOR_HEADER Header; Header = SD_TO_SD_HEADER( *SecurityDescriptor ); ObPrint( SHOW_DEASSIGN,("Deassigning security descriptor %x, Index = %d\n",*SecurityDescriptor, Header->Index)); ObpDereferenceSecurityDescriptor( *SecurityDescriptor ); // // NULL out the SecurityDescriptor in the object's // header so we don't try to free it again. // *SecurityDescriptor = NULL; return( STATUS_SUCCESS ); } VOID ObpDereferenceSecurityDescriptor( PSECURITY_DESCRIPTOR SecurityDescriptor ) /*++ Routine Description: Decrements the refcount of a cached security descriptor Arguments: SecurityDescriptor - Points to a cached security descriptor Return Value: None. --*/ { PSECURITY_DESCRIPTOR_HEADER SecurityDescriptorHeader; ObpAcquireDescriptorCacheWriteLock(); SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor ); ObPrint( SHOW_REFERENCES, ("Dereferencing SecurityDescriptor %x, index #%d, refcount = %d \n", SecurityDescriptor, SecurityDescriptorHeader->Index,SecurityDescriptorHeader->RefCount)); // DbgPrint("Dereferencing SecurityDescriptor %x, index #%d, refcount = %d \n", SecurityDescriptor, SecurityDescriptorHeader->Index,SecurityDescriptorHeader->RefCount); ASSERT(SecurityDescriptorHeader->RefCount != 0); if (--SecurityDescriptorHeader->RefCount == 0) { ObpDestroySecurityDescriptorHeader( SecurityDescriptorHeader ); } ObpReleaseDescriptorCacheLock(); } VOID ObpDestroySecurityDescriptorHeader( IN PSECURITY_DESCRIPTOR_HEADER Header ) /*++ Routine Description: Frees a cached security descriptor and unlinks it from the chain. Does nothing if it's being reused. Arguments: Header - Pointer to a security descriptor header (cached security descriptor) Return Value: None. --*/ { PLIST_ENTRY Forward; PLIST_ENTRY Rear; UCHAR Index; ASSERT ( Header->RefCount == 0 ); #if OB_DIAGNOSTICS_ENABLED TotalCacheEntries--; #endif ObPrint( SHOW_STATISTICS, ("TotalCacheEntries = %d \n",TotalCacheEntries)); Index = Header->Index; Forward = Header->Link.Flink; Rear = Header->Link.Blink; if ( Forward != NULL ) { Forward->Blink = Rear; } if ( Rear != NULL ) { Rear->Flink = Forward; } else { // // if Rear is NULL, we're deleting the head of the list // SecurityDescriptorCache[Index] = Forward; } ObPrint( SHOW_HEADER_FREE, ("Freeing memory at %x \n",Header)); ExFreePool( Header ); return; } BOOLEAN ObpCompareSecurityDescriptors( IN PSECURITY_DESCRIPTOR SD1, IN PSECURITY_DESCRIPTOR SD2 ) /*++ Routine Description: Performs a byte by byte comparison of two self relative security descriptors to determine if they are identical. Arguments: SD1, SD2 - Security descriptors to be compared. Return Value: TRUE - They are the same. FALSE - They are different. --*/ { ULONG Length1; ULONG Length2; ULONG Compare; // // Calculating the lenght is pretty fast, see if we // can get away with doing only that. // Length1 = RtlLengthSecurityDescriptor ( SD1 ); Length2 = RtlLengthSecurityDescriptor ( SD2 ); if (Length1 != Length2) { return( FALSE ); } return (BOOLEAN)RtlEqualMemory ( SD1, SD2, Length1 ); } VOID ObpAcquireDescriptorCacheWriteLock( VOID ) /*++ Routine Description: Takes a write lock on the security descriptor cache. Arguments: none Return Value: None. --*/ { KeEnterCriticalRegion(); (VOID) ExAcquireResourceExclusive( &SecurityDescriptorCacheLock, TRUE ); return; } VOID ObpAcquireDescriptorCacheReadLock( VOID ) /*++ Routine Description: Takes a read lock on the security descriptor cache. Arguments: none Return Value: None. --*/ { KeEnterCriticalRegion(); (VOID)ExAcquireResourceShared( &SecurityDescriptorCacheLock,TRUE); return; } VOID ObpReleaseDescriptorCacheLock( VOID ) /*++ Routine Description: Releases a lock on the security descriptor cache. Arguments: none Return Value: None. --*/ { (VOID) ExReleaseResource( &SecurityDescriptorCacheLock ); KeLeaveCriticalRegion (); return; }