Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

783 lines
22 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name:
  4. opcache.cxx
  5. Abstract:
  6. Routines implementing a cache of which operations have already been checked
  7. for a particular client context
  8. Author:
  9. Cliff Van Dyke (cliffv) 14-Nov-2001
  10. --*/
  11. #include "pch.hxx"
  12. #define AZD_COMPONENT AZD_ACCESS
  13. //
  14. // Windows XP RTM doesn't support RtlLookupElementGenericTableFull
  15. // Windoes XP SP1 does support it
  16. // So avoid the API if building a binary that runs on XP RTM.
  17. #ifndef RUN_ON_XP_RTM
  18. #define USE_AVL_FULL 1
  19. #endif // RUN_ON_XP_RTM
  20. //
  21. // Structure describing the operations that have previously had access checks done on them
  22. // for a particular scope.
  23. //
  24. typedef struct _AZP_OPERATION_CACHE {
  25. //
  26. // Pointer to the scope object this cache entry applies to
  27. // The ReferenceCount is held on Scope.
  28. // This value is NULL if the scope is the application.
  29. //
  30. PAZP_SCOPE Scope;
  31. //
  32. // Pointer to the operation object this cache entry applies to
  33. // The ReferenceCount is held on Operation
  34. // This field must be the first field in the structure.
  35. //
  36. PAZP_OPERATION Operation;
  37. //
  38. // Pointer to the business rule string applicable to this operation
  39. // The actual string is a part of this same allocated buffer.
  40. AZP_STRING BizRuleString;
  41. //
  42. // Result of the access check
  43. //
  44. DWORD Result;
  45. } AZP_OPERATION_CACHE, *PAZP_OPERATION_CACHE;
  46. RTL_GENERIC_COMPARE_RESULTS
  47. AzpAvlCacheCompare(
  48. IN PRTL_GENERIC_TABLE Table,
  49. IN PVOID FirstStruct,
  50. IN PVOID SecondStruct
  51. )
  52. /*++
  53. Routine Description:
  54. This routine will compare twp operation cache entries
  55. Arguments:
  56. IN PRTL_GENERIC_TABLE - Supplies the table containing the announcements
  57. IN PVOID FirstStuct - The first structure to compare.
  58. IN PVOID SecondStruct - The second structure to compare.
  59. Return Value:
  60. Result of the comparison.
  61. --*/
  62. {
  63. PAZP_OPERATION_CACHE Op1 = (PAZP_OPERATION_CACHE) FirstStruct;
  64. PAZP_OPERATION_CACHE Op2 = (PAZP_OPERATION_CACHE) SecondStruct;
  65. if ( Op1->Scope < Op2->Scope ) {
  66. return GenericLessThan;
  67. } else if ( Op1->Scope > Op2->Scope ) {
  68. return GenericGreaterThan;
  69. } else if ( Op1->Operation < Op2->Operation ) {
  70. return GenericLessThan;
  71. } else if ( Op1->Operation > Op2->Operation ) {
  72. return GenericGreaterThan;
  73. } else {
  74. return GenericEqual;
  75. }
  76. UNREFERENCED_PARAMETER(Table);
  77. }
  78. VOID
  79. AzpInitOperationCache(
  80. IN PAZP_CLIENT_CONTEXT ClientContext
  81. )
  82. /*++
  83. Routine Description:
  84. Initializes the operation cache for a client context
  85. On entry, AzGlResource must be locked exclusively.
  86. Arguments:
  87. ClientContext - Specifies the client context to initialize the cache for
  88. Return Value:
  89. None
  90. --*/
  91. {
  92. //
  93. // Initialization
  94. //
  95. ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
  96. //
  97. // Initialize the AVL tree of scopes that have been access checked already
  98. //
  99. RtlInitializeGenericTable( &ClientContext->OperationCacheAvlTree,
  100. AzpAvlCacheCompare,
  101. AzpAvlAllocate,
  102. AzpAvlFree,
  103. NULL);
  104. }
  105. VOID
  106. AzpFlushOperationCache(
  107. IN PAZP_CLIENT_CONTEXT ClientContext
  108. )
  109. /*++
  110. Routine Description:
  111. Flushes the operation cache for a client context
  112. On entry, AcContext->ClientContext.CritSect must be locked OR
  113. AzGlResource must be locked exclusively.
  114. Arguments:
  115. ClientContext - Specifies the client context to flush the cache for
  116. Return Value:
  117. None
  118. --*/
  119. {
  120. ULONG i;
  121. PAZP_OPERATION_CACHE OperationCache;
  122. //
  123. // Initialization
  124. //
  125. ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) ||
  126. AzpIsLockedExclusive( &AzGlResource ) );
  127. //
  128. // Loop until the OperationCache is empty
  129. //
  130. for (;;) {
  131. //
  132. // Get the first element in the table
  133. //
  134. OperationCache = (PAZP_OPERATION_CACHE) RtlEnumerateGenericTable( &ClientContext->OperationCacheAvlTree, TRUE );
  135. if ( OperationCache == NULL ) {
  136. break;
  137. }
  138. //
  139. // Dereference the Scope object
  140. //
  141. if ( OperationCache->Scope != NULL ) {
  142. ObDereferenceObject( &OperationCache->Scope->GenericObject );
  143. OperationCache->Scope = NULL;
  144. }
  145. //
  146. // Dereference the Operation object
  147. //
  148. ObDereferenceObject( &OperationCache->Operation->GenericObject );
  149. //
  150. // Delete the entry
  151. //
  152. RtlDeleteElementGenericTable( &ClientContext->OperationCacheAvlTree, OperationCache );
  153. }
  154. ASSERT (RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0);
  155. //
  156. // Ditch the arrays of cached parameters
  157. //
  158. if ( ClientContext->UsedParameterNames != NULL ) {
  159. for ( i=0; i<ClientContext->UsedParameterCount; i++) {
  160. VariantClear( &ClientContext->UsedParameterNames[i] );
  161. VariantClear( &ClientContext->UsedParameterValues[i] );
  162. }
  163. AzpFreeHeap( ClientContext->UsedParameterNames );
  164. ClientContext->UsedParameterNames = NULL;
  165. // UsedParameterValues is a part of the UsedParameterNames allocated block
  166. ClientContext->UsedParameterValues = NULL;
  167. ClientContext->UsedParameterCount = 0;
  168. }
  169. }
  170. BOOLEAN
  171. AzpCheckOperationCache(
  172. IN PACCESS_CHECK_CONTEXT AcContext
  173. )
  174. /*++
  175. Routine Description:
  176. This routine checks checks to see if this access check can be satisified by the
  177. cache of operations.
  178. On entry, AcContext->ClientContext.CritSect must be locked.
  179. On entry, AzGlResource must be locked Shared.
  180. Arguments:
  181. AcContext - Specifies the context of the user to check group membership of.
  182. AcContext is updated to indicate any operations that are know to be allowed or denied.
  183. Return Value:
  184. TRUE - All operations were satisfied from cache
  185. --*/
  186. {
  187. ULONG WinStatus;
  188. ULONG OpIndex;
  189. ULONG i;
  190. PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext;
  191. AZP_OPERATION_CACHE TemplateOperationCache = {0};
  192. PAZP_OPERATION_CACHE OperationCache;
  193. //
  194. // Initialization
  195. //
  196. ASSERT( AzpIsLockedShared( &AzGlResource ) );
  197. ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
  198. //
  199. // Check to ensure we should be using the operation cache
  200. //
  201. // Avoid the cache if any interfaces were passed by the caller
  202. // (This could relaxed. It doesn't make any difference that the interface was
  203. // passed by the caller. It only matters if the interface was actually used.
  204. // So we could set a boolean in CScriptEngine::GetItemInfo and simply not cache
  205. // operations that used the interfaces.)
  206. //
  207. if ( AcContext->Interfaces != NULL ) {
  208. AzPrint(( AZD_ACCESS_MORE,
  209. "AzpCheckOperationCache: Operation cache avoided since interfaces passed in\n" ));
  210. return FALSE;
  211. }
  212. //
  213. // If object cache has changed,
  214. // flush the operation cache.
  215. //
  216. // This code doesn't prevent the object cache from changing *during* the access check
  217. // call. That's fine. It does protect against changes made prior to the access check call.
  218. //
  219. if ( ClientContext->OpCacheSerialNumber !=
  220. ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ) {
  221. AzpFlushOperationCache( ClientContext );
  222. //
  223. // Update the serial number to the new serial number
  224. //
  225. AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: OpCacheSerialNumber changed from %ld to %ld\n",
  226. ClientContext->OpCacheSerialNumber,
  227. ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ));
  228. ClientContext->OpCacheSerialNumber =
  229. ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber;
  230. }
  231. //
  232. // If the cache is empty,
  233. // we're done now
  234. //
  235. if ( RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0 ) {
  236. return FALSE;
  237. }
  238. //
  239. // If any of the parmeters used to build the operation cache have changed,
  240. // Don't use the operation cache.
  241. //
  242. //
  243. // We didn't capture the array
  244. // So access it under a try/except
  245. __try {
  246. //
  247. // If the number of passed parameters changed in size,
  248. // flush the cache
  249. //
  250. if ( ClientContext->UsedParameterCount != 0 &&
  251. ClientContext->UsedParameterCount != AcContext->ParameterCount ) {
  252. AzPrint(( AZD_CRITICAL,
  253. "AzpCheckOperationCache: Parameter count changed from previous call %ld %ld\n",
  254. ClientContext->UsedParameterCount,
  255. AcContext->ParameterCount ));
  256. AzpFlushOperationCache( ClientContext );
  257. }
  258. //
  259. //
  260. // For each name on the existing list of used paramaters,
  261. // check to ensure the value hasn't change
  262. //
  263. for ( i=0; i<ClientContext->UsedParameterCount; i++ ) {
  264. //
  265. // Skip parameters that weren't used on the previous call
  266. //
  267. if ( V_VT(&ClientContext->UsedParameterNames[i] ) == VT_EMPTY ) {
  268. continue;
  269. }
  270. //
  271. // If the used parameter wasn't passed in on this new call,
  272. // or if the used parameter has a different value on this new call,
  273. // flush the cache
  274. //
  275. // We rely on the fact that the app always passes the same parameter names
  276. // on every AccessCheck call. That is reasonable since the app has a fixed
  277. // contract with the bizrule writers to supply a fixed set of parameters.
  278. //
  279. if ( i >= AcContext->ParameterCount ||
  280. AzpCompareParameterNames(
  281. &ClientContext->UsedParameterNames[i],
  282. &AcContext->ParameterNames[i] ) != 0 ||
  283. VarCmp(
  284. &ClientContext->UsedParameterValues[i],
  285. &AcContext->ParameterValues[i],
  286. LOCALE_USER_DEFAULT, 0 ) != (HRESULT)VARCMP_EQ ) {
  287. AzPrint(( AZD_ACCESS_MORE,
  288. "AzpCheckOperationCache: Parameter '%ws' changed from previous call\n",
  289. V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
  290. AzpFlushOperationCache( ClientContext );
  291. break;
  292. }
  293. }
  294. } __except( EXCEPTION_EXECUTE_HANDLER ) {
  295. AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", GetExceptionCode()));
  296. return FALSE;
  297. }
  298. //
  299. // Loop handling each operation
  300. //
  301. for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
  302. //
  303. // Lookup the scope/operation pair in the operation cache
  304. //
  305. TemplateOperationCache.Scope = AcContext->Scope;
  306. TemplateOperationCache.Operation = AcContext->OperationObjects[OpIndex];
  307. OperationCache = (PAZP_OPERATION_CACHE) RtlLookupElementGenericTable (
  308. &ClientContext->OperationCacheAvlTree,
  309. &TemplateOperationCache );
  310. if ( OperationCache == NULL ) {
  311. continue;
  312. }
  313. //
  314. // Return the bizrule string for this operation
  315. // The caller cannot depend upon order of evaluation.
  316. // Therefore, only the first cached string need be returned.
  317. //
  318. if ( AcContext->BusinessRuleString.StringSize == 0 ) {
  319. WinStatus = AzpDuplicateString( &AcContext->BusinessRuleString,
  320. &OperationCache->BizRuleString );
  321. if ( WinStatus != NO_ERROR ) {
  322. return FALSE;
  323. }
  324. }
  325. //
  326. // The operation result was found,
  327. // return it.
  328. //
  329. AcContext->Results[OpIndex] = OperationCache->Result;
  330. AcContext->OperationWasProcessed[OpIndex] = TRUE;
  331. AcContext->ProcessedOperationCount++;
  332. AcContext->CachedOperationCount++;
  333. AzPrint(( AZD_ACCESS_MORE,
  334. "AzpCheckOperationCache: '%ws/%ws' found in operation cache\n",
  335. OperationCache->Scope != NULL ? OperationCache->Scope->GenericObject.ObjectName->ObjectName.String : NULL,
  336. OperationCache->Operation->GenericObject.ObjectName->ObjectName.String,
  337. OperationCache->Result ));
  338. if (AcContext->OperationCount == AcContext->CachedOperationCount) {
  339. return TRUE;
  340. }
  341. }
  342. return FALSE;
  343. }
  344. VOID
  345. AzpUpdateOperationCache(
  346. IN PACCESS_CHECK_CONTEXT AcContext
  347. )
  348. /*++
  349. Routine Description:
  350. This routine updated the operation cache with new results.
  351. On entry, AcContext->ClientContext.CritSect must be locked.
  352. On entry, AzGlResource must be locked Shared.
  353. Arguments:
  354. AcContext - Specifies the context of the user to check group membership of.
  355. AcContext is updated to indicate any operations that are know to be allowed or denied.
  356. Return Value:
  357. None
  358. --*/
  359. {
  360. ULONG OpIndex;
  361. PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext;
  362. HRESULT hr;
  363. ULONG i;
  364. #ifdef USE_AVL_FULL
  365. PVOID NodeOrParent;
  366. TABLE_SEARCH_RESULT SearchResult;
  367. #endif USE_AVL_FULL
  368. BOOLEAN NewElement;
  369. PAZP_OPERATION_CACHE TemplateOperationCache = NULL;
  370. ULONG TemplateOperationCacheSize;
  371. PAZP_OPERATION_CACHE OperationCache;
  372. //
  373. // Initialization
  374. //
  375. ASSERT( AzpIsLockedShared( &AzGlResource ) );
  376. ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
  377. //
  378. // If we aren't supposed to use the cache for this AccessCheck,
  379. // we're done
  380. //
  381. if ( AcContext->Interfaces != NULL ) {
  382. goto Cleanup;
  383. }
  384. //
  385. // If all operations were satisfied via the cache,
  386. // simply return
  387. //
  388. if ( AcContext->CachedOperationCount == AcContext->OperationCount ) {
  389. AzPrint(( AZD_ACCESS_MORE,
  390. "AzpUpdateOperationCache: No operations to cache\n" ));
  391. goto Cleanup;
  392. }
  393. //
  394. // Save the list of "used" parameters
  395. //
  396. // This is a combined list of all parameters used on this call and
  397. // all the parameters used on previous calls.
  398. //
  399. // Skip this if there are no newly used parameters
  400. //
  401. if ( AcContext->UsedParameterCount != 0 ) {
  402. //
  403. // We didn't capture the array
  404. // So access it under a try/except
  405. __try {
  406. ASSERT( AcContext->ParameterCount != 0 );
  407. ASSERT( ClientContext->UsedParameterCount == 0 ||
  408. ClientContext->UsedParameterCount == AcContext->ParameterCount );
  409. //
  410. // If no buffer has been allocated yet,
  411. // allocate and initialize it.
  412. //
  413. if ( ClientContext->UsedParameterCount == 0 ) {
  414. VARIANT *ParameterNames = NULL;
  415. //
  416. // Allocate the array
  417. //
  418. ParameterNames = (VARIANT *) AzpAllocateHeap(
  419. 2 * sizeof(VARIANT) * AcContext->ParameterCount,
  420. "OPCACHE"
  421. );
  422. if ( ParameterNames == NULL ) {
  423. goto Cleanup;
  424. }
  425. //
  426. // Initialize all of the variants to VT_EMPTY
  427. //
  428. for ( i=0; i<AcContext->ParameterCount*2; i++ ) {
  429. VariantInit( &ParameterNames[i] );
  430. }
  431. //
  432. // Store the pointers to the initialized arrays
  433. //
  434. ClientContext->UsedParameterNames = ParameterNames;
  435. ClientContext->UsedParameterValues = &ParameterNames[AcContext->ParameterCount];
  436. ClientContext->UsedParameterCount = AcContext->ParameterCount;
  437. }
  438. //
  439. // Copy the new names into the new buffer
  440. //
  441. for ( i=0; i<AcContext->ParameterCount; i++ ) {
  442. //
  443. // Only copy parameters that have been used
  444. // and weren't copy on a previous call
  445. //
  446. if ( AcContext->UsedParameters[i] &&
  447. V_VT(&ClientContext->UsedParameterNames[i]) == VT_EMPTY ) {
  448. hr = VariantCopy( &ClientContext->UsedParameterNames[i],
  449. &AcContext->ParameterNames[i] );
  450. if ( FAILED(hr) ) {
  451. goto Cleanup;
  452. }
  453. hr = VariantCopy( &ClientContext->UsedParameterValues[i],
  454. &AcContext->ParameterValues[i] );
  455. if ( FAILED(hr) ) {
  456. VariantClear( &ClientContext->UsedParameterNames[i] );
  457. goto Cleanup;
  458. }
  459. AzPrint(( AZD_ACCESS_MORE,
  460. "AzpUpdateOperationCache: Added parameter '%ws' to the used parameter list\n",
  461. V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
  462. }
  463. }
  464. } __except( EXCEPTION_EXECUTE_HANDLER ) {
  465. hr = GetExceptionCode();
  466. AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", hr));
  467. goto Cleanup;
  468. }
  469. }
  470. //
  471. // Allocate a template for the operation cache entry
  472. //
  473. TemplateOperationCacheSize = sizeof(AZP_OPERATION_CACHE) + AcContext->BusinessRuleString.StringSize;
  474. SafeAllocaAllocate( TemplateOperationCache, TemplateOperationCacheSize );
  475. if ( TemplateOperationCache == NULL ) {
  476. goto Cleanup;
  477. }
  478. //
  479. // Loop handling each operation
  480. //
  481. for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
  482. //
  483. // Lookup the scope/operation pair in the operation cache
  484. //
  485. TemplateOperationCache->Scope = AcContext->Scope;
  486. TemplateOperationCache->Operation = AcContext->OperationObjects[OpIndex];
  487. AzPrint(( AZD_ACCESS_MORE,
  488. "AzpUpdateOperationCache: Added '%ws/%ws' %ld to operation cache\n",
  489. AcContext->Scope != NULL ? AcContext->Scope->GenericObject.ObjectName->ObjectName.String : NULL,
  490. AcContext->OperationObjects[OpIndex]->GenericObject.ObjectName->ObjectName.String,
  491. AcContext->Results[OpIndex] ));
  492. OperationCache = (PAZP_OPERATION_CACHE)
  493. #ifdef USE_AVL_FULL
  494. RtlLookupElementGenericTableFull(
  495. #else // USE_AVL_FULL
  496. RtlLookupElementGenericTable(
  497. #endif // USE_AVL_FULL
  498. &ClientContext->OperationCacheAvlTree,
  499. TemplateOperationCache
  500. #ifdef USE_AVL_FULL
  501. ,
  502. &NodeOrParent,
  503. &SearchResult
  504. #endif // USE_AVL_FULL
  505. );
  506. if ( OperationCache == NULL ) {
  507. OperationCache = (PAZP_OPERATION_CACHE)
  508. #ifdef USE_AVL_FULL
  509. RtlInsertElementGenericTableFull(
  510. #else // USE_AVL_FULL
  511. RtlInsertElementGenericTable(
  512. #endif // USE_AVL_FULL
  513. &ClientContext->OperationCacheAvlTree,
  514. TemplateOperationCache,
  515. TemplateOperationCacheSize,
  516. &NewElement
  517. #ifdef USE_AVL_FULL
  518. ,
  519. NodeOrParent,
  520. SearchResult
  521. #endif // USE_AVL_FULL
  522. );
  523. if ( OperationCache == NULL ) {
  524. continue;
  525. }
  526. ASSERT( NewElement );
  527. //
  528. // Initialize the new element
  529. //
  530. if ( OperationCache->Scope != NULL) {
  531. InterlockedIncrement( &OperationCache->Scope->GenericObject.ReferenceCount );
  532. AzpDumpGoRef( "Scope Cache", &OperationCache->Scope->GenericObject );
  533. }
  534. InterlockedIncrement( &OperationCache->Operation->GenericObject.ReferenceCount );
  535. AzpDumpGoRef( "Operation Cache", &OperationCache->Operation->GenericObject );
  536. OperationCache->Result = AcContext->Results[OpIndex];
  537. //
  538. // Fill in the biz rule string
  539. // Don't bother if the result is NO_ERROR.
  540. //
  541. if ( OperationCache->Result != NO_ERROR ) {
  542. OperationCache->BizRuleString.String = (LPWSTR)&OperationCache[1];
  543. OperationCache->BizRuleString.StringSize = AcContext->BusinessRuleString.StringSize;
  544. if ( AcContext->BusinessRuleString.StringSize != 0 ) {
  545. RtlCopyMemory( OperationCache->BizRuleString.String,
  546. AcContext->BusinessRuleString.String,
  547. AcContext->BusinessRuleString.StringSize );
  548. }
  549. } else {
  550. OperationCache->BizRuleString.StringSize = 0;
  551. }
  552. //
  553. // The operation is already cached.
  554. // This is one of two cases:
  555. // * The result was already filled in by AzpCheckOperationCache
  556. // * The caller passed the same operation in twice
  557. //
  558. // In the latter case, the cache is assumed to be correct. Return the
  559. // cached value for the all results.
  560. //
  561. } else {
  562. AcContext->Results[OpIndex] = OperationCache->Result;
  563. }
  564. }
  565. //
  566. // Free locally used resources
  567. //
  568. Cleanup:
  569. SafeAllocaFree( TemplateOperationCache );
  570. return;
  571. }