/*++ 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; iUsedParameterCount; 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; iUsedParameterCount; 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; OpIndexOperationCount; 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; iParameterCount*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; iParameterCount; 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; OpIndexOperationCount; 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; }