|
|
/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
obsdata.c
Abstract:
Object Manager Security Descriptor Caching
Author:
Robert Reichel (robertre) 12-Oct-1993
Revision History:
Neill Clift (NeillC) 16-Nov-2000
General cleanup. Don't free/allocate pool under locks. Don't do unaligned fetches during hashing. Reduce lock contention etc. Add fast referencing of security descriptor.
--*/
#include "obp.h"
#if DBG
#define OB_DIAGNOSTICS_ENABLED 1
#endif // DBG
//
// These definitions are useful diagnostics aids
//
#if OB_DIAGNOSTICS_ENABLED
//
// Test for enabled diagnostic
//
#define IF_OB_GLOBAL( FlagName ) if (ObsDebugFlags & (OBS_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
//
// 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 OBS_DEBUG_ALLOC_TRACKING ((ULONG) 0x00000001L)
#define OBS_DEBUG_CACHE_FREES ((ULONG) 0x00000002L)
#define OBS_DEBUG_BREAK_ON_INIT ((ULONG) 0x00000004L)
#define OBS_DEBUG_SHOW_COLLISIONS ((ULONG) 0x00000008L)
#define OBS_DEBUG_SHOW_STATISTICS ((ULONG) 0x00000010L)
#define OBS_DEBUG_SHOW_REFERENCES ((ULONG) 0x00000020L)
#define OBS_DEBUG_SHOW_DEASSIGN ((ULONG) 0x00000040L)
#define OBS_DEBUG_STOP_INVALID_DESCRIPTOR ((ULONG) 0x00000080L)
#define OBS_DEBUG_SHOW_HEADER_FREE ((ULONG) 0x00000100L)
//
// Define struct of single hash clash chain
//
typedef struct _OB_SD_CACHE_LIST { EX_PUSH_LOCK PushLock; LIST_ENTRY Head; } OB_SD_CACHE_LIST, *POB_SD_CACHE_LIST; //
// Array of pointers to security descriptor entries
//
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif
OB_SD_CACHE_LIST ObsSecurityDescriptorCache[SECURITY_DESCRIPTOR_CACHE_ENTRIES];
#if OB_DIAGNOSTICS_ENABLED
LONG ObsTotalCacheEntries = 0; ULONG ObsDebugFlags = 0;
#endif
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif
#if defined (ALLOC_PRAGMA)
#pragma alloc_text(INIT,ObpInitSecurityDescriptorCache)
#pragma alloc_text(PAGE,ObpHashSecurityDescriptor)
#pragma alloc_text(PAGE,ObpHashBuffer)
#pragma alloc_text(PAGE,ObLogSecurityDescriptor)
#pragma alloc_text(PAGE,ObpCreateCacheEntry)
#pragma alloc_text(PAGE,ObpReferenceSecurityDescriptor)
#pragma alloc_text(PAGE,ObDeassignSecurity)
#pragma alloc_text(PAGE,ObDereferenceSecurityDescriptor)
#pragma alloc_text(PAGE,ObpDestroySecurityDescriptorHeader)
#pragma alloc_text(PAGE,ObpCompareSecurityDescriptors)
#pragma alloc_text(PAGE,ObReferenceSecurityDescriptor)
#endif
NTSTATUS ObpInitSecurityDescriptorCache ( VOID )
/*++
Routine Description:
Allocates and initializes the globalSecurity Descriptor Cache
Arguments:
None
Return Value:
STATUS_SUCCESS on success, NTSTATUS on failure.
--*/
{ ULONG i;
IF_OB_GLOBAL( BREAK_ON_INIT ) {
DbgBreakPoint(); }
//
// Initialize all the list heads and their associated locks.
//
for (i = 0; i < SECURITY_DESCRIPTOR_CACHE_ENTRIES; i++) { ExInitializePushLock (&ObsSecurityDescriptorCache[i].PushLock); InitializeListHead (&ObsSecurityDescriptorCache[i].Head); }
//
// And return to our caller
//
return( STATUS_SUCCESS ); }
ULONG ObpHashSecurityDescriptor ( PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG Length )
/*++
Routine Description:
Hashes a security descriptor to a 32 bit value
Arguments:
SecurityDescriptor - Provides the security descriptor to be hashed Length - Length of security descriptor
Return Value:
ULONG - a 32 bit hash value.
--*/
{ ULONG Hash;
Hash = ObpHashBuffer (SecurityDescriptor, Length);
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.
--*/
{ PULONG Buffer, BufferEnd; PUCHAR Bufferp, BufferEndp;
ULONG Result = 0;
//
// Calculate buffer bounds as byte pointers
//
Bufferp = Data; BufferEndp = Bufferp + Length;
//
// Calculate buffer bounds as rounded down ULONG pointers
//
Buffer = Data; BufferEnd = (PULONG)(Bufferp + (Length&~(sizeof (ULONG) - 1)));
//
// Loop over a whole number of ULONGs
//
while (Buffer < BufferEnd) { Result ^= *Buffer++; Result = _rotl (Result, 3); }
//
// Pull in the remaining bytes
//
Bufferp = (PUCHAR) Buffer; while (Bufferp < BufferEndp) { Result ^= *Bufferp++; Result = _rotl (Result, 3); }
return Result; }
NTSTATUS ObLogSecurityDescriptor ( IN PSECURITY_DESCRIPTOR InputSecurityDescriptor, OUT PSECURITY_DESCRIPTOR *OutputSecurityDescriptor, IN ULONG RefBias )
/*++
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. On a successful return this memory will have been freed back to pool.
OutputSecurityDescriptor - Output security descriptor to be used by the caller.
RefBias - Amount to bias the security descriptor reference count by. Typicaly either 1 or ExFastRefGetAdditionalReferenceCount () + 1,
Return Value:
An appropriate status value
--*/
{ ULONG FullHash; ULONG Slot; PSECURITY_DESCRIPTOR_HEADER NewDescriptor; PLIST_ENTRY Front; PSECURITY_DESCRIPTOR_HEADER Header = NULL; BOOLEAN Match; POB_SD_CACHE_LIST Chain; PETHREAD CurrentThread; ULONG Length;
Length = RtlLengthSecurityDescriptor (InputSecurityDescriptor);
FullHash = ObpHashSecurityDescriptor (InputSecurityDescriptor, Length);
Slot = FullHash % SECURITY_DESCRIPTOR_CACHE_ENTRIES;
NewDescriptor = NULL;
//
// First lock the table for read access. We will change this to write if we have to insert later
//
Chain = &ObsSecurityDescriptorCache[Slot];
CurrentThread = PsGetCurrentThread (); KeEnterCriticalRegionThread (&CurrentThread->Tcb); ExAcquirePushLockShared (&Chain->PushLock);
do { //
// See if the list for this slot is in use.
// Lock the table first, unlock if if we don't need it.
//
Match = FALSE;
//
// Zoom down the hash bucket looking for a full hash match
//
for (Front = Chain->Head.Flink; Front != &Chain->Head; Front = Front->Flink) {
Header = LINK_TO_SD_HEADER (Front);
//
// The list is ordered by full hash value and is maintained this way by virtue
// of the fact that we use the 'Back' variable for the insert.
//
if (Header->FullHash > FullHash) { break; }
if (Header->FullHash == FullHash) {
Match = ObpCompareSecurityDescriptors (InputSecurityDescriptor, Length, &Header->SecurityDescriptor);
if (Match) {
break; }
ObPrint (SHOW_COLLISIONS, ("Got a collision on %d, no match\n", Slot)); } }
//
// If we have a match then we'll get the caller to use the old
// cached descriptor, but bumping its ref count, freeing what
// the caller supplied and returning the old one to our caller
//
if (Match) {
InterlockedExchangeAdd ((PLONG)&Header->RefCount, RefBias);
ObPrint (SHOW_REFERENCES, ("Reference Hash = 0x%lX, New RefCount = %d\n", Header->FullHash, Header->RefCount));
ExReleasePushLock (&Chain->PushLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
*OutputSecurityDescriptor = &Header->SecurityDescriptor;
if (NewDescriptor != NULL) { ExFreePool (NewDescriptor); }
return STATUS_SUCCESS; }
if (NewDescriptor == NULL) { ExReleasePushLockShared (&Chain->PushLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
//
// Can't use an existing one, create a new entry
// and insert it into the list.
//
NewDescriptor = ObpCreateCacheEntry (InputSecurityDescriptor, Length, FullHash, RefBias);
if (NewDescriptor == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } //
// Reacquire the lock in write mode. We will probably have to insert now
//
KeEnterCriticalRegionThread (&CurrentThread->Tcb); ExAcquirePushLockExclusive (&Chain->PushLock); } else { break; } } while (1);
#if OB_DIAGNOSTICS_ENABLED
InterlockedIncrement (&ObsTotalCacheEntries);
#endif
ObPrint (SHOW_STATISTICS, ("ObsTotalCacheEntries = %d \n", ObsTotalCacheEntries)); ObPrint (SHOW_COLLISIONS, ("Adding new entry for index #%d \n", Slot));
//
// Insert the entry before the 'Front' entry. If there is no 'Front' entry then this
// is just inserting at the head
//
InsertTailList (Front, &NewDescriptor->Link);
ExReleasePushLockExclusive (&Chain->PushLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
//
// Set the output security descriptor and return to our caller
//
*OutputSecurityDescriptor = &NewDescriptor->SecurityDescriptor;
return( STATUS_SUCCESS ); }
PSECURITY_DESCRIPTOR_HEADER ObpCreateCacheEntry ( PSECURITY_DESCRIPTOR InputSecurityDescriptor, ULONG SecurityDescriptorLength, ULONG FullHash, ULONG RefBias )
/*++
Routine Description:
Allocates and initializes a new cache entry.
Arguments:
InputSecurityDescriptor - The security descriptor to be cached.
Length - Length of security descriptor
FullHash - Full 32 bit hash of the security descriptor.
RefBias - Amount to bias the security descriptor reference count by. Typicaly either 1 or ExFastRefGetAdditionalReferenceCount () + 1,
Return Value:
A pointer to the newly allocated cache entry, or NULL
--*/
{ ULONG CacheEntrySize; PSECURITY_DESCRIPTOR_HEADER NewDescriptor;
//
// Compute the size that we'll need to allocate. We need space for
// the security descriptor cache minus the funny quad at the end and the
// security descriptor itself.
//
ASSERT (SecurityDescriptorLength == RtlLengthSecurityDescriptor (InputSecurityDescriptor)); CacheEntrySize = SecurityDescriptorLength + (sizeof (SECURITY_DESCRIPTOR_HEADER) - sizeof(QUAD));
//
// Now allocate space for the cached entry
//
NewDescriptor = ExAllocatePoolWithTag (PagedPool, CacheEntrySize, 'cSbO');
if (NewDescriptor == NULL) {
return NULL; }
//
// Fill the header, copy over the descriptor data, and return to our
// caller
//
NewDescriptor->RefCount = RefBias; NewDescriptor->FullHash = FullHash;
RtlCopyMemory (&NewDescriptor->SecurityDescriptor, InputSecurityDescriptor, SecurityDescriptorLength);
return NewDescriptor; }
VOID ObReferenceSecurityDescriptor ( IN PSECURITY_DESCRIPTOR SecurityDescriptor, IN ULONG Count ) /*++
Routine Description:
References the security descriptor.
Arguments:
SecurityDescriptor - Security descriptor inside the cache to reference. Count - Amount to reference by
Return Value:
None.
--*/ { PSECURITY_DESCRIPTOR_HEADER SecurityDescriptorHeader;
SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor ); ObPrint( SHOW_REFERENCES, ("Referencing Hash %lX, Refcount = %d \n",SecurityDescriptorHeader->FullHash, SecurityDescriptorHeader->RefCount));
//
// Increment the reference count
//
InterlockedExchangeAdd ((PLONG)&SecurityDescriptorHeader->RefCount, Count); }
PSECURITY_DESCRIPTOR ObpReferenceSecurityDescriptor ( POBJECT_HEADER ObjectHeader )
/*++
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; PSECURITY_DESCRIPTOR SecurityDescriptor; PEX_FAST_REF FastRef; EX_FAST_REF OldRef; ULONG RefsToAdd, Unused;
//
// Attempt the fast reference
//
FastRef = (PEX_FAST_REF) &ObjectHeader->SecurityDescriptor;
OldRef = ExFastReference (FastRef);
SecurityDescriptor = ExFastRefGetObject (OldRef);
//
// See if we can fast reference this security descriptor. Return NULL if there wasn't one
// and go the slow way if there are no more cached references left.
//
Unused = ExFastRefGetUnusedReferences (OldRef);
if (Unused >= 1 || SecurityDescriptor == NULL) { if (Unused == 1) { //
// If we took the counter to zero then attempt to make life easier for
// the next referencer by resetting the counter to its max. Since we now
// have a reference to the security descriptor we can do this.
//
RefsToAdd = ExFastRefGetAdditionalReferenceCount (); SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor ); InterlockedExchangeAdd ((PLONG)&SecurityDescriptorHeader->RefCount, RefsToAdd);
//
// Try to add the added references to the cache. If we fail then just
// release them. This dereference can not take the reference count to zero.
//
if (!ExFastRefAddAdditionalReferenceCounts (FastRef, SecurityDescriptor, RefsToAdd)) { InterlockedExchangeAdd ((PLONG)&SecurityDescriptorHeader->RefCount, -(LONG)RefsToAdd); } } return SecurityDescriptor; }
ObpLockObjectShared( ObjectHeader );
SecurityDescriptor = ExFastRefGetObject (*FastRef);
IF_OB_GLOBAL( STOP_INVALID_DESCRIPTOR ) {
if(!RtlValidSecurityDescriptor ( SecurityDescriptor )) {
DbgBreakPoint(); } }
//
// The obejcts security descriptor is not allowed to go fron NON-NULL to NULL.
//
SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor ); ObPrint( SHOW_REFERENCES, ("Referencing Hash %lX, Refcount = %d \n",SecurityDescriptorHeader->FullHash, SecurityDescriptorHeader->RefCount));
//
// Increment the reference count
//
InterlockedIncrement ((PLONG) &SecurityDescriptorHeader->RefCount);
ObpUnlockObject( ObjectHeader );
return( SecurityDescriptor ); }
NTSTATUS ObDeassignSecurity ( IN OUT PSECURITY_DESCRIPTOR *pSecurityDescriptor )
/*++
Routine Description:
This routine dereferences the input security descriptor
Arguments:
SecurityDescriptor - Supplies the security descriptor being modified
Return Value:
Only returns STATUS_SUCCESS
--*/
{ PSECURITY_DESCRIPTOR SecurityDescriptor; EX_FAST_REF FastRef;
ObPrint( SHOW_DEASSIGN,("Deassigning security descriptor %x\n",*pSecurityDescriptor));
//
// NULL out the SecurityDescriptor in the object's
// header so we don't try to free it again.
//
FastRef = *(PEX_FAST_REF) pSecurityDescriptor; *pSecurityDescriptor = NULL;
SecurityDescriptor = ExFastRefGetObject (FastRef); ObDereferenceSecurityDescriptor (SecurityDescriptor, ExFastRefGetUnusedReferences (FastRef) + 1); return STATUS_SUCCESS; }
VOID ObDereferenceSecurityDescriptor ( PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG Count )
/*++
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; PVOID PoolToFree; LONG OldValue, NewValue; POB_SD_CACHE_LIST Chain; PETHREAD CurrentThread; ULONG Slot;
SecurityDescriptorHeader = SD_TO_SD_HEADER( SecurityDescriptor );
//
// First see if its possible to do a non-zero transition lock free.
//
OldValue = SecurityDescriptorHeader->RefCount;
//
// If the old value is equal to the decrement then we will be the deleter of this block. We need the lock for that
//
while (OldValue != (LONG) Count) {
NewValue = InterlockedCompareExchange ((PLONG)&SecurityDescriptorHeader->RefCount, OldValue - Count, OldValue); if (NewValue == OldValue) { return; } OldValue = NewValue; }
//
// Lock the security descriptor cache and get a pointer
// to the security descriptor header
//
Slot = SecurityDescriptorHeader->FullHash % SECURITY_DESCRIPTOR_CACHE_ENTRIES;
Chain = &ObsSecurityDescriptorCache[Slot];
CurrentThread = PsGetCurrentThread (); KeEnterCriticalRegionThread (&CurrentThread->Tcb); ExAcquirePushLockExclusive (&Chain->PushLock);
//
// Do some debug work
//
ObPrint( SHOW_REFERENCES, ("Dereferencing SecurityDescriptor %x, hash %lx, refcount = %d \n", SecurityDescriptor, SecurityDescriptorHeader->FullHash, SecurityDescriptorHeader->RefCount));
ASSERT(SecurityDescriptorHeader->RefCount != 0);
//
// Decrement the ref count and if it is now zero then
// we can completely remove this entry from the cache
//
if (InterlockedExchangeAdd ((PLONG)&SecurityDescriptorHeader->RefCount, -(LONG)Count) == (LONG)Count) {
PoolToFree = ObpDestroySecurityDescriptorHeader (SecurityDescriptorHeader); //
// Unlock the security descriptor cache and free the pool
//
ExReleasePushLockExclusive (&Chain->PushLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
ExFreePool (PoolToFree); } else {
//
// Unlock the security descriptor cache and return to our caller
//
ExReleasePushLockExclusive (&Chain->PushLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb); }
}
PVOID ObpDestroySecurityDescriptorHeader ( IN PSECURITY_DESCRIPTOR_HEADER Header )
/*++
Routine Description:
Frees a cached security descriptor and unlinks it from the chain.
Arguments:
Header - Pointer to a security descriptor header (cached security descriptor)
Return Value:
None.
--*/
{ ASSERT ( Header->RefCount == 0 );
#if OB_DIAGNOSTICS_ENABLED
InterlockedDecrement (&ObsTotalCacheEntries);
#endif
ObPrint( SHOW_STATISTICS, ("ObsTotalCacheEntries = %d \n",ObsTotalCacheEntries));
//
// Unlink the cached security descriptor from its linked list
//
RemoveEntryList (&Header->Link);
ObPrint( SHOW_HEADER_FREE, ("Freeing memory at %x \n",Header));
//
// Now return the cached descriptor to our caller to free
//
return Header; }
BOOLEAN ObpCompareSecurityDescriptors ( IN PSECURITY_DESCRIPTOR SD1, IN ULONG Length1, 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. Length1 - Length of SD1
Return Value:
TRUE - They are the same.
FALSE - They are different.
--*/
{ ULONG Length2;
//
// Calculating the length is pretty fast, see if we
// can get away with doing only that.
//
ASSERT (Length1 == RtlLengthSecurityDescriptor ( SD1 ));
Length2 = RtlLengthSecurityDescriptor ( SD2 );
if (Length1 != Length2) {
return( FALSE ); }
return (BOOLEAN)RtlEqualMemory ( SD1, SD2, Length1 ); }
|