1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Process Pool Quotas Support 5 * COPYRIGHT: Copyright 2005 Alex Ionescu <alex@relsoft.net> 6 * Copyright 2007 Mike Nordell 7 * Copyright 2021 George Bișoc <george.bisoc@reactos.org> 8 */ 9 10 /* INCLUDES **************************************************************/ 11 12 #include <ntoskrnl.h> 13 #define NDEBUG 14 #include <debug.h> 15 16 EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock; 17 static LIST_ENTRY PspQuotaBlockList = {&PspQuotaBlockList, &PspQuotaBlockList}; 18 static KSPIN_LOCK PspQuotaLock; 19 20 #define VALID_QUOTA_FLAGS (QUOTA_LIMITS_HARDWS_MIN_ENABLE | \ 21 QUOTA_LIMITS_HARDWS_MIN_DISABLE | \ 22 QUOTA_LIMITS_HARDWS_MAX_ENABLE | \ 23 QUOTA_LIMITS_HARDWS_MAX_DISABLE) 24 25 /* PRIVATE FUNCTIONS *******************************************************/ 26 27 /** 28 * @brief 29 * Returns pool quotas back to the Memory Manager 30 * when the pool quota block is no longer being 31 * used by anybody. 32 * 33 * @param[in] QuotaBlock 34 * The pool quota block of which quota resources 35 * are to be sent back. 36 * 37 * @return 38 * Nothing. 39 * 40 * @remarks 41 * The function only returns quotas back to Memory 42 * Manager that is paged or non paged. It does not 43 * return page file quotas as page file quota 44 * management is done in a different way. Furthermore, 45 * quota spin lock has to be held when returning quotas. 46 */ 47 _Requires_lock_held_(PspQuotaLock) 48 VOID 49 NTAPI 50 PspReturnQuotasOnDestroy( 51 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) 52 { 53 ULONG PsQuotaTypeIndex; 54 SIZE_T QuotaToReturn; 55 56 /* 57 * We must be in a dispatch level interrupt here 58 * as we should be under a spin lock. 59 */ 60 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL); 61 62 /* Make sure that the quota block is not plain garbage */ 63 ASSERT(QuotaBlock); 64 65 /* Loop over the Process quota types */ 66 for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsPageFile; PsQuotaTypeIndex++) 67 { 68 /* The amount needed to return to Mm is the limit and return fields */ 69 QuotaToReturn = QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Limit + QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Return; 70 MmReturnPoolQuota(PsQuotaTypeIndex, QuotaToReturn); 71 } 72 } 73 74 /** 75 * @brief 76 * Releases some of excess quotas in order to attempt 77 * free up some resources. This is done primarily in 78 * in case the Memory Manager fails to raise the quota 79 * limit. 80 * 81 * @param[in] QuotaType 82 * Process pool quota type. 83 * 84 * @param[out] ReturnedQuotas 85 * A pointer to the returned amount of quotas 86 * back to Memory Manager. 87 * 88 * @return 89 * Nothing. 90 * 91 * @remarks 92 * The function releases excess paged or non 93 * paged pool quotas. Page file quota type is 94 * not permitted. Furthermore, quota spin lock 95 * has to be held when returning quotas. 96 */ 97 _Requires_lock_held_(PspQuotaLock) 98 VOID 99 NTAPI 100 PspReturnExcessQuotas( 101 _In_ PS_QUOTA_TYPE QuotaType, 102 _Outptr_ PSIZE_T ReturnedQuotas) 103 { 104 PLIST_ENTRY PspQuotaList; 105 PEPROCESS_QUOTA_BLOCK QuotaBlockFromList; 106 SIZE_T AmountToReturn = 0; 107 108 /* 109 * We must be in a dispatch level interrupt here 110 * as we should be under a spin lock. 111 */ 112 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL); 113 114 /* 115 * Loop over the quota block lists and reap 116 * whatever quotas we haven't returned which 117 * is needed to free up resources. 118 */ 119 for (PspQuotaList = PspQuotaBlockList.Flink; 120 PspQuotaList != &PspQuotaBlockList; 121 PspQuotaList = PspQuotaList->Flink) 122 { 123 /* Gather the quota block from the list */ 124 QuotaBlockFromList = CONTAINING_RECORD(PspQuotaList, EPROCESS_QUOTA_BLOCK, QuotaList); 125 126 /* 127 * Gather any unreturned quotas and cache 128 * them to a variable. 129 */ 130 AmountToReturn += InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Return, 0); 131 132 /* 133 * If no other process is taking use of this 134 * block, then it means that this block has 135 * only shared pool quota and the last process 136 * no longer uses this block. If the limit is 137 * grater than the usage then trim the limit 138 * and use that as additional amount of quota 139 * to return. 140 */ 141 if (QuotaBlockFromList->ProcessCount == 0) 142 { 143 if (QuotaBlockFromList->QuotaEntry[QuotaType].Usage < 144 QuotaBlockFromList->QuotaEntry[QuotaType].Limit) 145 { 146 InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Limit, 147 QuotaBlockFromList->QuotaEntry[QuotaType].Usage); 148 AmountToReturn += QuotaBlockFromList->QuotaEntry[QuotaType].Limit; 149 } 150 } 151 } 152 153 /* Invoke Mm to return quotas */ 154 DPRINT("PspReturnExcessQuotas(): Amount of quota released -- %lu\n", AmountToReturn); 155 MmReturnPoolQuota(QuotaType, AmountToReturn); 156 *ReturnedQuotas = AmountToReturn; 157 } 158 159 /** 160 * @brief 161 * Internal kernel function that provides the 162 * bulk logic of process quota charging, 163 * necessary for exported kernel routines 164 * needed for quota management. 165 * 166 * @param[in] Process 167 * A process, represented as a EPROCESS object. 168 * This parameter is used to charge the own 169 * process' quota usage. 170 * 171 * @param[in] QuotaBlock 172 * The quota block which quotas are to be charged. 173 * This block can either come from the process itself 174 * or from an object with specified quota charges. 175 * 176 * @param[in] QuotaType 177 * The quota type which quota in question is to 178 * be charged. The permitted types are PsPagedPool, 179 * PsNonPagedPool and PsPageFile. 180 * 181 * @param[in] Amount 182 * The amount of quota to be charged. 183 * 184 * @return 185 * Returns STATUS_SUCCESS if quota charging has 186 * been done successfully without problemns. 187 * STATUS_QUOTA_EXCEEDED is returned if the caller 188 * wants to charge quotas with amount way over 189 * the limits. STATUS_PAGEFILE_QUOTA_EXCEEDED 190 * is returned for the same situation but 191 * specific to page files instead. 192 */ 193 NTSTATUS 194 NTAPI 195 PspChargeProcessQuotaSpecifiedPool( 196 _In_opt_ PEPROCESS Process, 197 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, 198 _In_ PS_QUOTA_TYPE QuotaType, 199 _In_ SIZE_T Amount) 200 { 201 KIRQL OldIrql; 202 SIZE_T ReturnedQuotas; 203 SIZE_T UpdatedLimit; 204 205 /* Sanity checks */ 206 ASSERT(QuotaType < PsQuotaTypes); 207 ASSERT((SSIZE_T)Amount >= 0); 208 209 /* Guard ourselves in a spin lock */ 210 KeAcquireSpinLock(&PspQuotaLock, &OldIrql); 211 212 /* Are we within the bounds of quota limit? */ 213 if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount > 214 QuotaBlock->QuotaEntry[QuotaType].Limit && 215 QuotaBlock != &PspDefaultQuotaBlock) 216 { 217 /* We aren't... Is this a page file quota charging? */ 218 if (QuotaType == PsPageFile) 219 { 220 /* It is, return the appropriate status code */ 221 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit on page file quota (limit -- %lu || amount -- %lu)\n", 222 QuotaBlock->QuotaEntry[QuotaType].Limit, Amount); 223 return STATUS_PAGEFILE_QUOTA_EXCEEDED; 224 } 225 226 /* 227 * This is not a page file charge. What we can do at best 228 * in this scenario is to attempt to raise (expand) the 229 * quota limit charges of the block. 230 */ 231 if (!MmRaisePoolQuota(QuotaType, 232 QuotaBlock->QuotaEntry[QuotaType].Limit, 233 &UpdatedLimit)) 234 { 235 /* 236 * We can't? It could be that we must free 237 * up some resources in order to raise the 238 * limit, which in that case we must return 239 * the excess of quota that hasn't been 240 * returned. If we haven't returned anything 241 * then what we're doing here is futile. 242 * Bail out... 243 */ 244 PspReturnExcessQuotas(QuotaType, &ReturnedQuotas); 245 if (ReturnedQuotas == 0) 246 { 247 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Failed to free some resources in order to raise quota limits...\n"); 248 KeReleaseSpinLock(&PspQuotaLock, OldIrql); 249 return STATUS_QUOTA_EXCEEDED; 250 } 251 252 /* Try to raise the quota limits again */ 253 MmRaisePoolQuota(QuotaType, 254 QuotaBlock->QuotaEntry[QuotaType].Limit, 255 &UpdatedLimit); 256 } 257 258 /* Enforce a new raised limit */ 259 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit, UpdatedLimit); 260 261 /* 262 * Now determine if the current usage and the 263 * amounting by the caller still exceeds the 264 * quota limit of the process. If it's still 265 * over the limit then there's nothing we can 266 * do, so fail. 267 */ 268 if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount > 269 QuotaBlock->QuotaEntry[QuotaType].Limit) 270 { 271 DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit (limit -- %lu || amount -- %lu)\n", 272 QuotaBlock->QuotaEntry[QuotaType].Limit, Amount); 273 return STATUS_QUOTA_EXCEEDED; 274 } 275 } 276 277 /* Update the quota usage */ 278 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, Amount); 279 280 /* Update the entry peak if it's less than the usage */ 281 if (QuotaBlock->QuotaEntry[QuotaType].Peak < 282 QuotaBlock->QuotaEntry[QuotaType].Usage) 283 { 284 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Peak, 285 QuotaBlock->QuotaEntry[QuotaType].Usage); 286 } 287 288 /* Are we being given a process as well? */ 289 if (Process) 290 { 291 /* We're being given, check that's not a system one */ 292 ASSERT(Process != PsInitialSystemProcess); 293 294 InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], Amount); 295 296 /* 297 * OK, we've now updated the quota usage of the process 298 * based upon the amount that the caller wanted to charge. 299 * Although the peak of process quota can be less than it was 300 * before so update the peaks as well accordingly. 301 */ 302 if (Process->QuotaPeak[QuotaType] < Process->QuotaUsage[QuotaType]) 303 { 304 InterlockedExchangeSizeT(&Process->QuotaPeak[QuotaType], 305 Process->QuotaUsage[QuotaType]); 306 } 307 } 308 309 /* Release the lock */ 310 KeReleaseSpinLock(&PspQuotaLock, OldIrql); 311 return STATUS_SUCCESS; 312 } 313 314 /** 315 * @brief 316 * Internal kernel function that provides the 317 * bulk logic of process quota returning. It 318 * returns (takes away) quotas back from a 319 * process and/or quota block, which is 320 * the opposite of charging quotas. 321 * 322 * @param[in] Process 323 * A process, represented as a EPROCESS object. 324 * This parameter is used to return the own 325 * process' quota usage. 326 * 327 * @param[in] QuotaBlock 328 * The quota block which quotas are to be returned. 329 * This block can either come from the process itself 330 * or from an object with specified quota charges. 331 * 332 * @param[in] QuotaType 333 * The quota type which quota in question is to 334 * be returned. The permitted types are PsPagedPool, 335 * PsNonPagedPool and PsPageFile. 336 * 337 * @param[in] Amount 338 * The amount of quota to be returned. 339 * 340 * @return 341 * Nothing. 342 */ 343 VOID 344 NTAPI 345 PspReturnProcessQuotaSpecifiedPool( 346 _In_opt_ PEPROCESS Process, 347 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, 348 _In_ PS_QUOTA_TYPE QuotaType, 349 _In_ SIZE_T Amount) 350 { 351 KIRQL OldIrql; 352 SIZE_T ReturnThreshold; 353 SIZE_T AmountToReturn = 0; 354 355 /* Sanity checks */ 356 ASSERT(QuotaType < PsQuotaTypes); 357 ASSERT((SSIZE_T)Amount >= 0); 358 359 /* Guard ourselves in a spin lock */ 360 KeAcquireSpinLock(&PspQuotaLock, &OldIrql); 361 362 /* Does the caller return more quota than it was previously charged? */ 363 if ((Process && Process->QuotaUsage[QuotaType] < Amount) || 364 QuotaBlock->QuotaEntry[QuotaType].Usage < Amount) 365 { 366 /* It does, crash the system! */ 367 KeBugCheckEx(QUOTA_UNDERFLOW, 368 (ULONG_PTR)Process, 369 (ULONG_PTR)QuotaType, 370 Process ? (ULONG_PTR)Process->QuotaUsage[QuotaType] : 371 QuotaBlock->QuotaEntry[QuotaType].Usage, 372 (ULONG_PTR)Amount); 373 } 374 375 /* The return threshold can be non paged or paged */ 376 ReturnThreshold = QuotaType ? PSP_NON_PAGED_POOL_QUOTA_THRESHOLD : PSP_PAGED_POOL_QUOTA_THRESHOLD; 377 378 /* 379 * We need to trim the quota limits based on the 380 * amount we're going to return quotas back. 381 */ 382 if ((QuotaType != PsPageFile && QuotaBlock != &PspDefaultQuotaBlock) && 383 (QuotaBlock->QuotaEntry[QuotaType].Limit > QuotaBlock->QuotaEntry[QuotaType].Usage + ReturnThreshold)) 384 { 385 /* 386 * If the amount to return exceeds the threshold, 387 * the new amount becomes the default, otherwise 388 * the amount is just the one given by the caller. 389 */ 390 AmountToReturn = min(Amount, ReturnThreshold); 391 392 /* Add up the lots to the Return field */ 393 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Return, AmountToReturn); 394 395 /* 396 * If the amount to return exceeds the threshold then 397 * we have lots of quota to return to Mm. So do it so 398 * and zerou out the Return field. 399 */ 400 if (QuotaBlock->QuotaEntry[QuotaType].Return > ReturnThreshold) 401 { 402 MmReturnPoolQuota(QuotaType, QuotaBlock->QuotaEntry[QuotaType].Return); 403 InterlockedExchangeSizeT(QuotaBlock->QuotaEntry[QuotaType].Return, 0); 404 } 405 406 /* And try to trim the limit */ 407 InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit, 408 QuotaBlock->QuotaEntry[QuotaType].Limit - AmountToReturn); 409 } 410 411 /* Update the usage member of the block */ 412 InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, -(LONG_PTR)Amount); 413 414 /* Are we being given a process? */ 415 if (Process) 416 { 417 /* We're being given, check that's not a system one */ 418 ASSERT(Process != PsInitialSystemProcess); 419 420 /* Decrease the process' quota usage */ 421 InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], -(LONG_PTR)Amount); 422 } 423 424 /* We're done, release the lock */ 425 KeReleaseSpinLock(&PspQuotaLock, OldIrql); 426 } 427 428 /* FUNCTIONS ***************************************************************/ 429 430 /** 431 * @brief 432 * Initializes the quota system during boot 433 * phase of the system, which sets up the 434 * default quota block that is used across 435 * several processes. 436 * 437 * @return 438 * Nothing. 439 */ 440 CODE_SEG("INIT") 441 VOID 442 NTAPI 443 PsInitializeQuotaSystem(VOID) 444 { 445 /* Initialize the default block */ 446 RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock)); 447 448 /* Assign the default quota limits */ 449 PspDefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit = (SIZE_T)-1; 450 PspDefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit = (SIZE_T)-1; 451 PspDefaultQuotaBlock.QuotaEntry[PsPageFile].Limit = (SIZE_T)-1; 452 453 /* 454 * Set up the count references as the 455 * default block will going to be used. 456 */ 457 PspDefaultQuotaBlock.ReferenceCount = 1; 458 PspDefaultQuotaBlock.ProcessCount = 1; 459 460 /* Assign that block to initial process */ 461 PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock; 462 } 463 464 /** 465 * @brief 466 * Inherits the quota block to another newborn 467 * (child) process. If there's no parent 468 * process, the default quota block is 469 * assigned. 470 * 471 * @param[in] Process 472 * The child process which quota block 473 * is to be given. 474 * 475 * @param[in] ParentProcess 476 * The parent process. 477 * 478 * @return 479 * Nothing. 480 */ 481 VOID 482 NTAPI 483 PspInheritQuota( 484 _In_ PEPROCESS Process, 485 _In_opt_ PEPROCESS ParentProcess) 486 { 487 PEPROCESS_QUOTA_BLOCK QuotaBlock; 488 489 if (ParentProcess != NULL) 490 { 491 ASSERT(ParentProcess->QuotaBlock != NULL); 492 QuotaBlock = ParentProcess->QuotaBlock; 493 } 494 else 495 { 496 QuotaBlock = &PspDefaultQuotaBlock; 497 } 498 499 InterlockedIncrementSizeT(&QuotaBlock->ProcessCount); 500 InterlockedIncrementSizeT(&QuotaBlock->ReferenceCount); 501 502 Process->QuotaBlock = QuotaBlock; 503 } 504 505 /** 506 * @brief 507 * Inserts the new quota block into 508 * the quota list. 509 * 510 * @param[in] QuotaBlock 511 * The new quota block. 512 * 513 * @return 514 * Nothing. 515 */ 516 VOID 517 NTAPI 518 PspInsertQuotaBlock( 519 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) 520 { 521 KIRQL OldIrql; 522 523 KeAcquireSpinLock(&PspQuotaLock, &OldIrql); 524 InsertTailList(&PspQuotaBlockList, &QuotaBlock->QuotaList); 525 KeReleaseSpinLock(&PspQuotaLock, OldIrql); 526 } 527 528 /** 529 * @brief 530 * De-references a quota block when quotas 531 * have been returned back because of an 532 * object de-allocation or when a process 533 * gets destroyed. If the last instance 534 * that held up the block gets de-referenced 535 * the function will perform a cleanup against 536 * that block and it'll free the quota block 537 * from memory. 538 * 539 * @param[in] Process 540 * A pointer to a process that de-references the 541 * quota block. 542 * 543 * @param[in] QuotaBlock 544 * A pointer to a quota block that is to be 545 * de-referenced. This block can come from a 546 * process that references it or an object. 547 * 548 * @return 549 * Nothing. 550 */ 551 VOID 552 NTAPI 553 PspDereferenceQuotaBlock( 554 _In_opt_ PEPROCESS Process, 555 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) 556 { 557 ULONG PsQuotaTypeIndex; 558 KIRQL OldIrql; 559 560 /* Make sure the quota block is not trash */ 561 ASSERT(QuotaBlock); 562 563 /* Iterate over the process quota types if we have a process */ 564 if (Process) 565 { 566 for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsQuotaTypes; PsQuotaTypeIndex++) 567 { 568 /* 569 * We need to make sure that the quota usage 570 * uniquely associated with the process is 0 571 * on that moment the process gets destroyed. 572 */ 573 ASSERT(Process->QuotaUsage[PsQuotaTypeIndex] == 0); 574 } 575 576 /* As the process is now gone, decrement the process count */ 577 InterlockedDecrementUL(&QuotaBlock->ProcessCount); 578 } 579 580 /* If no one is using this block, begin to destroy it */ 581 if (QuotaBlock != &PspDefaultQuotaBlock && 582 InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0) 583 { 584 /* Acquire the quota lock */ 585 KeAcquireSpinLock(&PspQuotaLock, &OldIrql); 586 587 /* Return all the quotas back to Mm and remove the quota from list */ 588 PspReturnQuotasOnDestroy(QuotaBlock); 589 RemoveEntryList(&QuotaBlock->QuotaList); 590 591 /* Release the lock and free the block */ 592 KeReleaseSpinLock(&PspQuotaLock, OldIrql); 593 ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK); 594 } 595 } 596 597 /** 598 * @brief 599 * Returns the shared (paged and non paged) 600 * pool quotas. The function is used exclusively 601 * by the Object Manager to manage quota returns 602 * handling of objects. 603 * 604 * @param[in] QuotaBlock 605 * The quota block which quotas are to 606 * be returned. 607 * 608 * @param[in] AmountToReturnPaged 609 * The amount of paged quotas quotas to 610 * be returned. 611 * 612 * @param[in] AmountToReturnNonPaged 613 * The amount of non paged quotas to 614 * be returned. 615 * 616 * @return 617 * Nothing. 618 */ 619 VOID 620 NTAPI 621 PsReturnSharedPoolQuota( 622 _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, 623 _In_ SIZE_T AmountToReturnPaged, 624 _In_ SIZE_T AmountToReturnNonPaged) 625 { 626 /* Sanity check */ 627 ASSERT(QuotaBlock); 628 629 /* Return the pool quotas if there're any */ 630 if (AmountToReturnPaged != 0) 631 { 632 PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsPagedPool, AmountToReturnPaged); 633 } 634 635 if (AmountToReturnNonPaged != 0) 636 { 637 PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsNonPagedPool, AmountToReturnNonPaged); 638 } 639 640 DPRINT("PsReturnSharedPoolQuota(): Amount returned back (paged %lu -- non paged %lu)\n", AmountToReturnPaged, AmountToReturnNonPaged); 641 642 /* Dereference the quota block */ 643 PspDereferenceQuotaBlock(NULL, QuotaBlock); 644 } 645 646 /** 647 * @brief 648 * Charges the shared (paged and non paged) 649 * pool quotas. The function is used exclusively 650 * by the Object Manager to manage quota charges 651 * handling of objects. 652 * 653 * @param[in] Process 654 * The process which quotas are to 655 * be charged within its quota block. 656 * 657 * @param[in] AmountToChargePaged 658 * The amount of paged quotas quotas to 659 * be charged. 660 * 661 * @param[in] AmountToChargeNonPaged 662 * The amount of non paged quotas to 663 * be charged. 664 * 665 * @return 666 * Returns the charged quota block, which it'll 667 * be used by the Object Manager to attach 668 * the charged quotas information to the object. 669 * If the function fails to charge quotas, NULL 670 * is returned to the caller. 671 */ 672 PEPROCESS_QUOTA_BLOCK 673 NTAPI 674 PsChargeSharedPoolQuota( 675 _In_ PEPROCESS Process, 676 _In_ SIZE_T AmountToChargePaged, 677 _In_ SIZE_T AmountToChargeNonPaged) 678 { 679 NTSTATUS Status; 680 681 /* Sanity checks */ 682 ASSERT(Process); 683 ASSERT(Process->QuotaBlock); 684 685 /* Do we have some paged pool quota to charge? */ 686 if (AmountToChargePaged != 0) 687 { 688 /* We do, charge! */ 689 Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged); 690 if (!NT_SUCCESS(Status)) 691 { 692 DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx)\n", Status); 693 return NULL; 694 } 695 } 696 697 /* Do we have some non paged pool quota to charge? */ 698 if (AmountToChargeNonPaged != 0) 699 { 700 /* We do, charge! */ 701 Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsNonPagedPool, AmountToChargeNonPaged); 702 if (!NT_SUCCESS(Status)) 703 { 704 DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx). Attempting to return some paged pool back...\n", Status); 705 PspReturnProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged); 706 return NULL; 707 } 708 } 709 710 /* We have charged the quotas of an object, increment the reference */ 711 InterlockedIncrementSizeT(&Process->QuotaBlock->ReferenceCount); 712 713 DPRINT("PsChargeSharedPoolQuota(): Amount charged (paged %lu -- non paged %lu)\n", AmountToChargePaged, AmountToChargeNonPaged); 714 return Process->QuotaBlock; 715 } 716 717 /** 718 * @brief 719 * Charges the process page file quota. 720 * The function is used internally by 721 * the kernel. 722 * 723 * @param[in] Process 724 * The process which page file quota is 725 * to be charged. 726 * 727 * @param[in] Amount 728 * The amount of page file quota to charge. 729 * 730 * @return 731 * Returns STATUS_SUCCESS if quota charging has 732 * been done with success, otherwise a NTSTATUS 733 * code of STATUS_PAGEFILE_QUOTA_EXCEEDED is 734 * returned. 735 */ 736 NTSTATUS 737 NTAPI 738 PsChargeProcessPageFileQuota( 739 _In_ PEPROCESS Process, 740 _In_ SIZE_T Amount) 741 { 742 /* Don't do anything for the system process */ 743 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; 744 745 return PspChargeProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount); 746 } 747 748 /** 749 * @brief 750 * Charges the pool quota of a given process. 751 * The kind of pool quota to charge is determined 752 * by the PoolType parameter. 753 * 754 * @param[in] Process 755 * The process which quota is to be 756 * charged. 757 * 758 * @param[in] PoolType 759 * The pool type to choose to charge quotas 760 * (e.g. PagedPool or NonPagedPool). 761 * 762 * @param[in] Amount 763 * The amount of quotas to charge into a process. 764 * 765 * @return 766 * Nothing. 767 * 768 * @remarks 769 * The function raises an exception if STATUS_QUOTA_EXCEEDED 770 * status code is returned. Callers are responsible on their 771 * own to handle the raised exception. 772 */ 773 VOID 774 NTAPI 775 PsChargePoolQuota( 776 _In_ PEPROCESS Process, 777 _In_ POOL_TYPE PoolType, 778 _In_ SIZE_T Amount) 779 { 780 NTSTATUS Status; 781 ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); 782 783 /* Don't do anything for the system process */ 784 if (Process == PsInitialSystemProcess) return; 785 786 /* Charge the usage */ 787 Status = PsChargeProcessPoolQuota(Process, PoolType, Amount); 788 if (!NT_SUCCESS(Status)) ExRaiseStatus(Status); 789 } 790 791 /** 792 * @brief 793 * Charges the non paged pool quota 794 * of a given process. 795 * 796 * @param[in] Process 797 * The process which non paged quota 798 * is to be charged. 799 * 800 * @param[in] Amount 801 * The amount of quotas to charge into a process. 802 * 803 * @return 804 * Returns STATUS_SUCCESS if quota charing has 805 * suceeded, STATUS_QUOTA_EXCEEDED is returned 806 * otherwise to indicate the caller attempted 807 * to charge quotas over the limits. 808 */ 809 NTSTATUS 810 NTAPI 811 PsChargeProcessNonPagedPoolQuota( 812 _In_ PEPROCESS Process, 813 _In_ SIZE_T Amount) 814 { 815 /* Call the general function */ 816 return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount); 817 } 818 819 /** 820 * @brief 821 * Charges the paged pool quota of a 822 * given process. 823 * 824 * @param[in] Process 825 * The process which paged quota 826 * is to be charged. 827 * 828 * @param[in] Amount 829 * The amount of quotas to charge into a process. 830 * 831 * @return 832 * Returns STATUS_SUCCESS if quota charing has 833 * suceeded, STATUS_QUOTA_EXCEEDED is returned 834 * otherwise to indicate the caller attempted 835 * to charge quotas over the limits. 836 */ 837 NTSTATUS 838 NTAPI 839 PsChargeProcessPagedPoolQuota( 840 _In_ PEPROCESS Process, 841 _In_ SIZE_T Amount) 842 { 843 /* Call the general function */ 844 return PsChargeProcessPoolQuota(Process, PagedPool, Amount); 845 } 846 847 /** 848 * @brief 849 * Charges the process' quota pool. 850 * The type of quota to be charged 851 * depends upon the PoolType parameter. 852 * 853 * @param[in] Process 854 * The process which quota is to 855 * be charged. 856 * 857 * @param[in] PoolType 858 * The type of quota pool to charge (e.g. 859 * PagedPool or NonPagedPool). 860 * 861 * @param[in] Amount 862 * The amount of quotas to charge into a process. 863 * 864 * @return 865 * Returns STATUS_SUCCESS if quota charing has 866 * suceeded, STATUS_QUOTA_EXCEEDED is returned 867 * otherwise to indicate the caller attempted 868 * to charge quotas over the limits. 869 */ 870 NTSTATUS 871 NTAPI 872 PsChargeProcessPoolQuota( 873 _In_ PEPROCESS Process, 874 _In_ POOL_TYPE PoolType, 875 _In_ SIZE_T Amount) 876 { 877 /* Don't do anything for the system process */ 878 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; 879 880 return PspChargeProcessQuotaSpecifiedPool(Process, 881 Process->QuotaBlock, 882 (PoolType & PAGED_POOL_MASK), 883 Amount); 884 } 885 886 /** 887 * @brief 888 * Returns the pool quota that the 889 * process was taking up. 890 * 891 * @param[in] Process 892 * The process which quota is to 893 * be returned. 894 * 895 * @param[in] PoolType 896 * The type of quota pool to return (e.g. 897 * PagedPool or NonPagedPool). 898 * 899 * @param[in] Amount 900 * The amount of quotas to return from a process. 901 * 902 * @return 903 * Nothing. 904 */ 905 VOID 906 NTAPI 907 PsReturnPoolQuota( 908 _In_ PEPROCESS Process, 909 _In_ POOL_TYPE PoolType, 910 _In_ SIZE_T Amount) 911 { 912 /* Don't do anything for the system process */ 913 if (Process == PsInitialSystemProcess) return; 914 915 PspReturnProcessQuotaSpecifiedPool(Process, 916 Process->QuotaBlock, 917 (PoolType & PAGED_POOL_MASK), 918 Amount); 919 } 920 921 /** 922 * @brief 923 * Returns the non paged quota pool 924 * that the process was taking up. 925 * 926 * @param[in] Process 927 * The process which non paged quota 928 * is to be returned. 929 * 930 * @param[in] Amount 931 * The amount of quotas to return from a process. 932 * 933 * @return 934 * Nothing. 935 */ 936 VOID 937 NTAPI 938 PsReturnProcessNonPagedPoolQuota( 939 _In_ PEPROCESS Process, 940 _In_ SIZE_T Amount) 941 { 942 /* Don't do anything for the system process */ 943 if (Process == PsInitialSystemProcess) return; 944 945 PsReturnPoolQuota(Process, NonPagedPool, Amount); 946 } 947 948 /** 949 * @brief 950 * Returns the paged pool quota that 951 * the process was taking up. 952 * 953 * @param[in] Process 954 * The process which paged pool 955 * quota is to be returned. 956 * 957 * @param[in] Amount 958 * The amount of quotas to return from a process. 959 * 960 * @return 961 * Nothing. 962 */ 963 VOID 964 NTAPI 965 PsReturnProcessPagedPoolQuota( 966 _In_ PEPROCESS Process, 967 _In_ SIZE_T Amount) 968 { 969 /* Don't do anything for the system process */ 970 if (Process == PsInitialSystemProcess) return; 971 972 PsReturnPoolQuota(Process, PagedPool, Amount); 973 } 974 975 /** 976 * @brief 977 * Returns the page file quota that the 978 * process was taking up. The function 979 * is used exclusively by the kernel. 980 * 981 * @param[in] Process 982 * The process which pagefile quota is 983 * to be returned. 984 * 985 * @param[in] Amount 986 * The amount of quotas to return from a process. 987 * 988 * @return 989 * Returns STATUS_SUCCESS. 990 */ 991 NTSTATUS 992 NTAPI 993 PsReturnProcessPageFileQuota( 994 _In_ PEPROCESS Process, 995 _In_ SIZE_T Amount) 996 { 997 /* Don't do anything for the system process */ 998 if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; 999 1000 PspReturnProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount); 1001 return STATUS_SUCCESS; 1002 } 1003 1004 /** 1005 * @brief 1006 * This function adjusts the working set limits 1007 * of a process and sets up new quota limits 1008 * when necessary. The function is used 1009 * when the caller requests to set up 1010 * new working set sizes. 1011 * 1012 * @param[in] Process 1013 * The process which quota limits or working 1014 * set sizes are to be changed. 1015 * 1016 * @param[in] Unused 1017 * This parameter is unused. 1018 * 1019 * @param[in] QuotaLimits 1020 * An arbitrary pointer that points to a quota 1021 * limits structure, needed to determine on 1022 * setting up new working set sizes. 1023 * 1024 * @param[in] QuotaLimitsLength 1025 * The length of QuotaLimits buffer, which size 1026 * is expressed in bytes. 1027 * 1028 * @param[in] PreviousMode 1029 * The processor level access mode. 1030 * 1031 * @return 1032 * Returns STATUS_SUCCESS if the function has completed 1033 * successfully. STATUS_INVALID_PARAMETER is returned if 1034 * the caller has given a quota limits structure with 1035 * invalid data. STATUS_INFO_LENGTH_MISMATCH is returned 1036 * if the length of QuotaLimits pointed by QuotaLimitsLength 1037 * is not right. STATUS_PRIVILEGE_NOT_HELD is returned if 1038 * the calling thread of the process doesn't hold the necessary 1039 * right privilege to increase quotas. STATUS_NO_MEMORY is 1040 * returned if a memory pool allocation has failed. A failure 1041 * NTSTATUS code is returned otherwise. 1042 */ 1043 NTSTATUS 1044 NTAPI 1045 PspSetQuotaLimits( 1046 _In_ PEPROCESS Process, 1047 _In_ ULONG Unused, 1048 _In_ PVOID QuotaLimits, 1049 _In_ ULONG QuotaLimitsLength, 1050 _In_ KPROCESSOR_MODE PreviousMode) 1051 { 1052 QUOTA_LIMITS_EX CapturedQuotaLimits; 1053 PEPROCESS_QUOTA_BLOCK QuotaBlock, OldQuotaBlock; 1054 BOOLEAN IncreaseOkay; 1055 KAPC_STATE SavedApcState; 1056 NTSTATUS Status; 1057 1058 UNREFERENCED_PARAMETER(Unused); 1059 1060 _SEH2_TRY 1061 { 1062 ProbeForRead(QuotaLimits, QuotaLimitsLength, sizeof(ULONG)); 1063 1064 /* Check if we have the basic or extended structure */ 1065 if (QuotaLimitsLength == sizeof(QUOTA_LIMITS)) 1066 { 1067 /* Copy the basic structure, zero init the remaining fields */ 1068 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS)); 1069 CapturedQuotaLimits.WorkingSetLimit = 0; 1070 CapturedQuotaLimits.Reserved2 = 0; 1071 CapturedQuotaLimits.Reserved3 = 0; 1072 CapturedQuotaLimits.Reserved4 = 0; 1073 CapturedQuotaLimits.CpuRateLimit.RateData = 0; 1074 CapturedQuotaLimits.Flags = 0; 1075 } 1076 else if (QuotaLimitsLength == sizeof(QUOTA_LIMITS_EX)) 1077 { 1078 /* Copy the full structure */ 1079 RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS_EX)); 1080 1081 /* Verify that the caller passed valid flags */ 1082 if ((CapturedQuotaLimits.Flags & ~VALID_QUOTA_FLAGS) || 1083 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_ENABLE) && 1084 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_DISABLE)) || 1085 ((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_ENABLE) && 1086 (CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_DISABLE))) 1087 { 1088 DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits.Flags); 1089 _SEH2_YIELD(return STATUS_INVALID_PARAMETER); 1090 } 1091 1092 /* Verify that the caller didn't pass reserved values */ 1093 if ((CapturedQuotaLimits.WorkingSetLimit != 0) || 1094 (CapturedQuotaLimits.Reserved2 != 0) || 1095 (CapturedQuotaLimits.Reserved3 != 0) || 1096 (CapturedQuotaLimits.Reserved4 != 0) || 1097 (CapturedQuotaLimits.CpuRateLimit.RateData != 0)) 1098 { 1099 DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n", 1100 CapturedQuotaLimits.WorkingSetLimit, 1101 CapturedQuotaLimits.Reserved2, 1102 CapturedQuotaLimits.Reserved3, 1103 CapturedQuotaLimits.Reserved4, 1104 CapturedQuotaLimits.CpuRateLimit.RateData); 1105 _SEH2_YIELD(return STATUS_INVALID_PARAMETER); 1106 } 1107 } 1108 else 1109 { 1110 DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength); 1111 _SEH2_YIELD(return STATUS_INFO_LENGTH_MISMATCH); 1112 } 1113 } 1114 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1115 { 1116 DPRINT1("Exception while copying data\n"); 1117 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 1118 } 1119 _SEH2_END; 1120 1121 /* Check the caller changes the working set size limits */ 1122 if ((CapturedQuotaLimits.MinimumWorkingSetSize != 0) && 1123 (CapturedQuotaLimits.MaximumWorkingSetSize != 0)) 1124 { 1125 /* Check for special case: trimming the WS */ 1126 if ((CapturedQuotaLimits.MinimumWorkingSetSize == SIZE_T_MAX) && 1127 (CapturedQuotaLimits.MaximumWorkingSetSize == SIZE_T_MAX)) 1128 { 1129 /* No increase allowed */ 1130 IncreaseOkay = FALSE; 1131 } 1132 else 1133 { 1134 /* Check if the caller has the required privilege */ 1135 IncreaseOkay = SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, 1136 PreviousMode); 1137 } 1138 1139 /* Attach to the target process and disable APCs */ 1140 KeStackAttachProcess(&Process->Pcb, &SavedApcState); 1141 KeEnterGuardedRegion(); 1142 1143 /* Call Mm to adjust the process' working set size */ 1144 Status = MmAdjustWorkingSetSize(CapturedQuotaLimits.MinimumWorkingSetSize, 1145 CapturedQuotaLimits.MaximumWorkingSetSize, 1146 0, 1147 IncreaseOkay); 1148 1149 /* Bring back APCs and detach from the process */ 1150 KeLeaveGuardedRegion(); 1151 KeUnstackDetachProcess(&SavedApcState); 1152 } 1153 else if (Process->QuotaBlock == &PspDefaultQuotaBlock) 1154 { 1155 /* Check if the caller has the required privilege */ 1156 if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, PreviousMode)) 1157 { 1158 return STATUS_PRIVILEGE_NOT_HELD; 1159 } 1160 1161 /* Allocate a new quota block */ 1162 QuotaBlock = ExAllocatePoolWithTag(NonPagedPool, 1163 sizeof(EPROCESS_QUOTA_BLOCK), 1164 TAG_QUOTA_BLOCK); 1165 if (QuotaBlock == NULL) 1166 { 1167 ObDereferenceObject(Process); 1168 return STATUS_NO_MEMORY; 1169 } 1170 1171 /* Initialize the quota block */ 1172 QuotaBlock->ReferenceCount = 1; 1173 QuotaBlock->ProcessCount = 1; 1174 QuotaBlock->QuotaEntry[PsNonPagedPool].Peak = Process->QuotaPeak[PsNonPagedPool]; 1175 QuotaBlock->QuotaEntry[PsPagedPool].Peak = Process->QuotaPeak[PsPagedPool]; 1176 QuotaBlock->QuotaEntry[PsPageFile].Peak = Process->QuotaPeak[PsPageFile]; 1177 QuotaBlock->QuotaEntry[PsNonPagedPool].Limit = PspDefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit; 1178 QuotaBlock->QuotaEntry[PsPagedPool].Limit = PspDefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit; 1179 QuotaBlock->QuotaEntry[PsPageFile].Limit = PspDefaultQuotaBlock.QuotaEntry[PsPageFile].Limit; 1180 1181 /* Try to exchange the quota block, if that failed, just drop it */ 1182 OldQuotaBlock = InterlockedCompareExchangePointer((PVOID*)&Process->QuotaBlock, 1183 QuotaBlock, 1184 &PspDefaultQuotaBlock); 1185 if (OldQuotaBlock == &PspDefaultQuotaBlock) 1186 { 1187 /* Success, insert the new quota block */ 1188 PspInsertQuotaBlock(QuotaBlock); 1189 } 1190 else 1191 { 1192 /* Failed, free the quota block and ignore it */ 1193 ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK); 1194 } 1195 1196 Status = STATUS_SUCCESS; 1197 } 1198 else 1199 { 1200 Status = STATUS_SUCCESS; 1201 } 1202 1203 return Status; 1204 } 1205 1206 /* EOF */ 1207