Windows-Server-2003/ds/security/azroles/opcache.cxx

784 lines
22 KiB
C++

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
opcache.cxx
Abstract:
Routines implementing a cache of which operations have already been checked
for a particular client context
Author:
Cliff Van Dyke (cliffv) 14-Nov-2001
--*/
#include "pch.hxx"
#define AZD_COMPONENT AZD_ACCESS
//
// Windows XP RTM doesn't support RtlLookupElementGenericTableFull
// Windoes XP SP1 does support it
// So avoid the API if building a binary that runs on XP RTM.
#ifndef RUN_ON_XP_RTM
#define USE_AVL_FULL 1
#endif // RUN_ON_XP_RTM
//
// Structure describing the operations that have previously had access checks done on them
// for a particular scope.
//
typedef struct _AZP_OPERATION_CACHE {
//
// Pointer to the scope object this cache entry applies to
// The ReferenceCount is held on Scope.
// This value is NULL if the scope is the application.
//
PAZP_SCOPE Scope;
//
// Pointer to the operation object this cache entry applies to
// The ReferenceCount is held on Operation
// This field must be the first field in the structure.
//
PAZP_OPERATION Operation;
//
// Pointer to the business rule string applicable to this operation
// The actual string is a part of this same allocated buffer.
AZP_STRING BizRuleString;
//
// Result of the access check
//
DWORD Result;
} AZP_OPERATION_CACHE, *PAZP_OPERATION_CACHE;
RTL_GENERIC_COMPARE_RESULTS
AzpAvlCacheCompare(
IN PRTL_GENERIC_TABLE Table,
IN PVOID FirstStruct,
IN PVOID SecondStruct
)
/*++
Routine Description:
This routine will compare twp operation cache entries
Arguments:
IN PRTL_GENERIC_TABLE - Supplies the table containing the announcements
IN PVOID FirstStuct - The first structure to compare.
IN PVOID SecondStruct - The second structure to compare.
Return Value:
Result of the comparison.
--*/
{
PAZP_OPERATION_CACHE Op1 = (PAZP_OPERATION_CACHE) FirstStruct;
PAZP_OPERATION_CACHE Op2 = (PAZP_OPERATION_CACHE) SecondStruct;
if ( Op1->Scope < Op2->Scope ) {
return GenericLessThan;
} else if ( Op1->Scope > Op2->Scope ) {
return GenericGreaterThan;
} else if ( Op1->Operation < Op2->Operation ) {
return GenericLessThan;
} else if ( Op1->Operation > Op2->Operation ) {
return GenericGreaterThan;
} else {
return GenericEqual;
}
UNREFERENCED_PARAMETER(Table);
}
VOID
AzpInitOperationCache(
IN PAZP_CLIENT_CONTEXT ClientContext
)
/*++
Routine Description:
Initializes the operation cache for a client context
On entry, AzGlResource must be locked exclusively.
Arguments:
ClientContext - Specifies the client context to initialize the cache for
Return Value:
None
--*/
{
//
// Initialization
//
ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
//
// Initialize the AVL tree of scopes that have been access checked already
//
RtlInitializeGenericTable( &ClientContext->OperationCacheAvlTree,
AzpAvlCacheCompare,
AzpAvlAllocate,
AzpAvlFree,
NULL);
}
VOID
AzpFlushOperationCache(
IN PAZP_CLIENT_CONTEXT ClientContext
)
/*++
Routine Description:
Flushes the operation cache for a client context
On entry, AcContext->ClientContext.CritSect must be locked OR
AzGlResource must be locked exclusively.
Arguments:
ClientContext - Specifies the client context to flush the cache for
Return Value:
None
--*/
{
ULONG i;
PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) ||
AzpIsLockedExclusive( &AzGlResource ) );
//
// Loop until the OperationCache is empty
//
for (;;) {
//
// Get the first element in the table
//
OperationCache = (PAZP_OPERATION_CACHE) RtlEnumerateGenericTable( &ClientContext->OperationCacheAvlTree, TRUE );
if ( OperationCache == NULL ) {
break;
}
//
// Dereference the Scope object
//
if ( OperationCache->Scope != NULL ) {
ObDereferenceObject( &OperationCache->Scope->GenericObject );
OperationCache->Scope = NULL;
}
//
// Dereference the Operation object
//
ObDereferenceObject( &OperationCache->Operation->GenericObject );
//
// Delete the entry
//
RtlDeleteElementGenericTable( &ClientContext->OperationCacheAvlTree, OperationCache );
}
ASSERT (RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0);
//
// Ditch the arrays of cached parameters
//
if ( ClientContext->UsedParameterNames != NULL ) {
for ( i=0; i<ClientContext->UsedParameterCount; i++) {
VariantClear( &ClientContext->UsedParameterNames[i] );
VariantClear( &ClientContext->UsedParameterValues[i] );
}
AzpFreeHeap( ClientContext->UsedParameterNames );
ClientContext->UsedParameterNames = NULL;
// UsedParameterValues is a part of the UsedParameterNames allocated block
ClientContext->UsedParameterValues = NULL;
ClientContext->UsedParameterCount = 0;
}
}
BOOLEAN
AzpCheckOperationCache(
IN PACCESS_CHECK_CONTEXT AcContext
)
/*++
Routine Description:
This routine checks checks to see if this access check can be satisified by the
cache of operations.
On entry, AcContext->ClientContext.CritSect must be locked.
On entry, AzGlResource must be locked Shared.
Arguments:
AcContext - Specifies the context of the user to check group membership of.
AcContext is updated to indicate any operations that are know to be allowed or denied.
Return Value:
TRUE - All operations were satisfied from cache
--*/
{
ULONG WinStatus;
ULONG OpIndex;
ULONG i;
PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext;
AZP_OPERATION_CACHE TemplateOperationCache = {0};
PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
//
// Check to ensure we should be using the operation cache
//
// Avoid the cache if any interfaces were passed by the caller
// (This could relaxed. It doesn't make any difference that the interface was
// passed by the caller. It only matters if the interface was actually used.
// So we could set a boolean in CScriptEngine::GetItemInfo and simply not cache
// operations that used the interfaces.)
//
if ( AcContext->Interfaces != NULL ) {
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckOperationCache: Operation cache avoided since interfaces passed in\n" ));
return FALSE;
}
//
// If object cache has changed,
// flush the operation cache.
//
// This code doesn't prevent the object cache from changing *during* the access check
// call. That's fine. It does protect against changes made prior to the access check call.
//
if ( ClientContext->OpCacheSerialNumber !=
ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ) {
AzpFlushOperationCache( ClientContext );
//
// Update the serial number to the new serial number
//
AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: OpCacheSerialNumber changed from %ld to %ld\n",
ClientContext->OpCacheSerialNumber,
ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ));
ClientContext->OpCacheSerialNumber =
ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber;
}
//
// If the cache is empty,
// we're done now
//
if ( RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0 ) {
return FALSE;
}
//
// If any of the parmeters used to build the operation cache have changed,
// Don't use the operation cache.
//
//
// We didn't capture the array
// So access it under a try/except
__try {
//
// If the number of passed parameters changed in size,
// flush the cache
//
if ( ClientContext->UsedParameterCount != 0 &&
ClientContext->UsedParameterCount != AcContext->ParameterCount ) {
AzPrint(( AZD_CRITICAL,
"AzpCheckOperationCache: Parameter count changed from previous call %ld %ld\n",
ClientContext->UsedParameterCount,
AcContext->ParameterCount ));
AzpFlushOperationCache( ClientContext );
}
//
//
// For each name on the existing list of used paramaters,
// check to ensure the value hasn't change
//
for ( i=0; i<ClientContext->UsedParameterCount; i++ ) {
//
// Skip parameters that weren't used on the previous call
//
if ( V_VT(&ClientContext->UsedParameterNames[i] ) == VT_EMPTY ) {
continue;
}
//
// If the used parameter wasn't passed in on this new call,
// or if the used parameter has a different value on this new call,
// flush the cache
//
// We rely on the fact that the app always passes the same parameter names
// on every AccessCheck call. That is reasonable since the app has a fixed
// contract with the bizrule writers to supply a fixed set of parameters.
//
if ( i >= AcContext->ParameterCount ||
AzpCompareParameterNames(
&ClientContext->UsedParameterNames[i],
&AcContext->ParameterNames[i] ) != 0 ||
VarCmp(
&ClientContext->UsedParameterValues[i],
&AcContext->ParameterValues[i],
LOCALE_USER_DEFAULT, 0 ) != (HRESULT)VARCMP_EQ ) {
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckOperationCache: Parameter '%ws' changed from previous call\n",
V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
AzpFlushOperationCache( ClientContext );
break;
}
}
} __except( EXCEPTION_EXECUTE_HANDLER ) {
AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", GetExceptionCode()));
return FALSE;
}
//
// Loop handling each operation
//
for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
//
// Lookup the scope/operation pair in the operation cache
//
TemplateOperationCache.Scope = AcContext->Scope;
TemplateOperationCache.Operation = AcContext->OperationObjects[OpIndex];
OperationCache = (PAZP_OPERATION_CACHE) RtlLookupElementGenericTable (
&ClientContext->OperationCacheAvlTree,
&TemplateOperationCache );
if ( OperationCache == NULL ) {
continue;
}
//
// Return the bizrule string for this operation
// The caller cannot depend upon order of evaluation.
// Therefore, only the first cached string need be returned.
//
if ( AcContext->BusinessRuleString.StringSize == 0 ) {
WinStatus = AzpDuplicateString( &AcContext->BusinessRuleString,
&OperationCache->BizRuleString );
if ( WinStatus != NO_ERROR ) {
return FALSE;
}
}
//
// The operation result was found,
// return it.
//
AcContext->Results[OpIndex] = OperationCache->Result;
AcContext->OperationWasProcessed[OpIndex] = TRUE;
AcContext->ProcessedOperationCount++;
AcContext->CachedOperationCount++;
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckOperationCache: '%ws/%ws' found in operation cache\n",
OperationCache->Scope != NULL ? OperationCache->Scope->GenericObject.ObjectName->ObjectName.String : NULL,
OperationCache->Operation->GenericObject.ObjectName->ObjectName.String,
OperationCache->Result ));
if (AcContext->OperationCount == AcContext->CachedOperationCount) {
return TRUE;
}
}
return FALSE;
}
VOID
AzpUpdateOperationCache(
IN PACCESS_CHECK_CONTEXT AcContext
)
/*++
Routine Description:
This routine updated the operation cache with new results.
On entry, AcContext->ClientContext.CritSect must be locked.
On entry, AzGlResource must be locked Shared.
Arguments:
AcContext - Specifies the context of the user to check group membership of.
AcContext is updated to indicate any operations that are know to be allowed or denied.
Return Value:
None
--*/
{
ULONG OpIndex;
PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext;
HRESULT hr;
ULONG i;
#ifdef USE_AVL_FULL
PVOID NodeOrParent;
TABLE_SEARCH_RESULT SearchResult;
#endif USE_AVL_FULL
BOOLEAN NewElement;
PAZP_OPERATION_CACHE TemplateOperationCache = NULL;
ULONG TemplateOperationCacheSize;
PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
//
// If we aren't supposed to use the cache for this AccessCheck,
// we're done
//
if ( AcContext->Interfaces != NULL ) {
goto Cleanup;
}
//
// If all operations were satisfied via the cache,
// simply return
//
if ( AcContext->CachedOperationCount == AcContext->OperationCount ) {
AzPrint(( AZD_ACCESS_MORE,
"AzpUpdateOperationCache: No operations to cache\n" ));
goto Cleanup;
}
//
// Save the list of "used" parameters
//
// This is a combined list of all parameters used on this call and
// all the parameters used on previous calls.
//
// Skip this if there are no newly used parameters
//
if ( AcContext->UsedParameterCount != 0 ) {
//
// We didn't capture the array
// So access it under a try/except
__try {
ASSERT( AcContext->ParameterCount != 0 );
ASSERT( ClientContext->UsedParameterCount == 0 ||
ClientContext->UsedParameterCount == AcContext->ParameterCount );
//
// If no buffer has been allocated yet,
// allocate and initialize it.
//
if ( ClientContext->UsedParameterCount == 0 ) {
VARIANT *ParameterNames = NULL;
//
// Allocate the array
//
ParameterNames = (VARIANT *) AzpAllocateHeap(
2 * sizeof(VARIANT) * AcContext->ParameterCount,
"OPCACHE"
);
if ( ParameterNames == NULL ) {
goto Cleanup;
}
//
// Initialize all of the variants to VT_EMPTY
//
for ( i=0; i<AcContext->ParameterCount*2; i++ ) {
VariantInit( &ParameterNames[i] );
}
//
// Store the pointers to the initialized arrays
//
ClientContext->UsedParameterNames = ParameterNames;
ClientContext->UsedParameterValues = &ParameterNames[AcContext->ParameterCount];
ClientContext->UsedParameterCount = AcContext->ParameterCount;
}
//
// Copy the new names into the new buffer
//
for ( i=0; i<AcContext->ParameterCount; i++ ) {
//
// Only copy parameters that have been used
// and weren't copy on a previous call
//
if ( AcContext->UsedParameters[i] &&
V_VT(&ClientContext->UsedParameterNames[i]) == VT_EMPTY ) {
hr = VariantCopy( &ClientContext->UsedParameterNames[i],
&AcContext->ParameterNames[i] );
if ( FAILED(hr) ) {
goto Cleanup;
}
hr = VariantCopy( &ClientContext->UsedParameterValues[i],
&AcContext->ParameterValues[i] );
if ( FAILED(hr) ) {
VariantClear( &ClientContext->UsedParameterNames[i] );
goto Cleanup;
}
AzPrint(( AZD_ACCESS_MORE,
"AzpUpdateOperationCache: Added parameter '%ws' to the used parameter list\n",
V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
}
}
} __except( EXCEPTION_EXECUTE_HANDLER ) {
hr = GetExceptionCode();
AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", hr));
goto Cleanup;
}
}
//
// Allocate a template for the operation cache entry
//
TemplateOperationCacheSize = sizeof(AZP_OPERATION_CACHE) + AcContext->BusinessRuleString.StringSize;
SafeAllocaAllocate( TemplateOperationCache, TemplateOperationCacheSize );
if ( TemplateOperationCache == NULL ) {
goto Cleanup;
}
//
// Loop handling each operation
//
for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
//
// Lookup the scope/operation pair in the operation cache
//
TemplateOperationCache->Scope = AcContext->Scope;
TemplateOperationCache->Operation = AcContext->OperationObjects[OpIndex];
AzPrint(( AZD_ACCESS_MORE,
"AzpUpdateOperationCache: Added '%ws/%ws' %ld to operation cache\n",
AcContext->Scope != NULL ? AcContext->Scope->GenericObject.ObjectName->ObjectName.String : NULL,
AcContext->OperationObjects[OpIndex]->GenericObject.ObjectName->ObjectName.String,
AcContext->Results[OpIndex] ));
OperationCache = (PAZP_OPERATION_CACHE)
#ifdef USE_AVL_FULL
RtlLookupElementGenericTableFull(
#else // USE_AVL_FULL
RtlLookupElementGenericTable(
#endif // USE_AVL_FULL
&ClientContext->OperationCacheAvlTree,
TemplateOperationCache
#ifdef USE_AVL_FULL
,
&NodeOrParent,
&SearchResult
#endif // USE_AVL_FULL
);
if ( OperationCache == NULL ) {
OperationCache = (PAZP_OPERATION_CACHE)
#ifdef USE_AVL_FULL
RtlInsertElementGenericTableFull(
#else // USE_AVL_FULL
RtlInsertElementGenericTable(
#endif // USE_AVL_FULL
&ClientContext->OperationCacheAvlTree,
TemplateOperationCache,
TemplateOperationCacheSize,
&NewElement
#ifdef USE_AVL_FULL
,
NodeOrParent,
SearchResult
#endif // USE_AVL_FULL
);
if ( OperationCache == NULL ) {
continue;
}
ASSERT( NewElement );
//
// Initialize the new element
//
if ( OperationCache->Scope != NULL) {
InterlockedIncrement( &OperationCache->Scope->GenericObject.ReferenceCount );
AzpDumpGoRef( "Scope Cache", &OperationCache->Scope->GenericObject );
}
InterlockedIncrement( &OperationCache->Operation->GenericObject.ReferenceCount );
AzpDumpGoRef( "Operation Cache", &OperationCache->Operation->GenericObject );
OperationCache->Result = AcContext->Results[OpIndex];
//
// Fill in the biz rule string
// Don't bother if the result is NO_ERROR.
//
if ( OperationCache->Result != NO_ERROR ) {
OperationCache->BizRuleString.String = (LPWSTR)&OperationCache[1];
OperationCache->BizRuleString.StringSize = AcContext->BusinessRuleString.StringSize;
if ( AcContext->BusinessRuleString.StringSize != 0 ) {
RtlCopyMemory( OperationCache->BizRuleString.String,
AcContext->BusinessRuleString.String,
AcContext->BusinessRuleString.StringSize );
}
} else {
OperationCache->BizRuleString.StringSize = 0;
}
//
// The operation is already cached.
// This is one of two cases:
// * The result was already filled in by AzpCheckOperationCache
// * The caller passed the same operation in twice
//
// In the latter case, the cache is assumed to be correct. Return the
// cached value for the all results.
//
} else {
AcContext->Results[OpIndex] = OperationCache->Result;
}
}
//
// Free locally used resources
//
Cleanup:
SafeAllocaFree( TemplateOperationCache );
return;
}