diff --git a/ntoskrnl/include/internal/tag.h b/ntoskrnl/include/internal/tag.h index 0e466482dd2..16616ccc92c 100644 --- a/ntoskrnl/include/internal/tag.h +++ b/ntoskrnl/include/internal/tag.h @@ -131,6 +131,7 @@ #define TAG_KAPC 'papk' /* kpap - kernel ps apc */ #define TAG_PS_APC 'pasP' /* Psap - Ps APC */ #define TAG_SHIM 'MIHS' +#define TAG_QUOTA_BLOCK 'bQsP' /* Run-Time Library Tags */ #define TAG_HDTB 'BTDH' diff --git a/ntoskrnl/ps/quota.c b/ntoskrnl/ps/quota.c index 947f78a658e..822b5ee44ee 100644 --- a/ntoskrnl/ps/quota.c +++ b/ntoskrnl/ps/quota.c @@ -1,11 +1,10 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS kernel - * FILE: ntoskrnl/ps/quota.c - * PURPOSE: Process Pool Quotas - * - * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - * Mike Nordell + * PROJECT: ReactOS Kernel + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Process Pool Quotas Support + * COPYRIGHT: Copyright 2005 Alex Ionescu + * Copyright 2007 Mike Nordell + * Copyright 2021 George Bișoc */ /* INCLUDES **************************************************************/ @@ -18,7 +17,6 @@ EPROCESS_QUOTA_BLOCK PspDefaultQuotaBlock; static LIST_ENTRY PspQuotaBlockList = {&PspQuotaBlockList, &PspQuotaBlockList}; static KSPIN_LOCK PspQuotaLock; -#define TAG_QUOTA_BLOCK 'bQsP' #define VALID_QUOTA_FLAGS (QUOTA_LIMITS_HARDWS_MIN_ENABLE | \ QUOTA_LIMITS_HARDWS_MIN_DISABLE | \ QUOTA_LIMITS_HARDWS_MAX_ENABLE | \ @@ -26,115 +24,499 @@ static KSPIN_LOCK PspQuotaLock; /* PRIVATE FUNCTIONS *******************************************************/ -/* - * Private helper to charge the specified process quota. - * Returns STATUS_QUOTA_EXCEEDED on quota limit check failure. - * Updates QuotaPeak as needed for specified quota type in PS_QUOTA_TYPE enum. - * Notes: Conceptually translation unit local/private. +/** + * @brief + * Returns pool quotas back to the Memory Manager + * when the pool quota block is no longer being + * used by anybody. + * + * @param[in] QuotaBlock + * The pool quota block of which quota resources + * are to be sent back. + * + * @return + * Nothing. + * + * @remarks + * The function only returns quotas back to Memory + * Manager that is paged or non paged. It does not + * return page file quotas as page file quota + * management is done in a different way. Furthermore, + * quota spin lock has to be held when returning quotas. + */ +_Requires_lock_held_(PspQuotaLock) +VOID +NTAPI +PspReturnQuotasOnDestroy( + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) +{ + ULONG PsQuotaTypeIndex; + SIZE_T QuotaToReturn; + + /* + * We must be in a dispatch level interrupt here + * as we should be under a spin lock. + */ + ASSERT_IRQL_EQUAL(DISPATCH_LEVEL); + + /* Make sure that the quota block is not plain garbage */ + ASSERT(QuotaBlock); + + /* Loop over the Process quota types */ + for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsPageFile; PsQuotaTypeIndex++) + { + /* The amount needed to return to Mm is the limit and return fields */ + QuotaToReturn = QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Limit + QuotaBlock->QuotaEntry[PsQuotaTypeIndex].Return; + MmReturnPoolQuota(PsQuotaTypeIndex, QuotaToReturn); + } +} + +/** + * @brief + * Releases some of excess quotas in order to attempt + * free up some resources. This is done primarily in + * in case the Memory Manager fails to raise the quota + * limit. + * + * @param[in] QuotaType + * Process pool quota type. + * + * @param[out] ReturnedQuotas + * A pointer to the returned amount of quotas + * back to Memory Manager. + * + * @return + * Nothing. + * + * @remarks + * The function releases excess paged or non + * paged pool quotas. Page file quota type is + * not permitted. Furthermore, quota spin lock + * has to be held when returning quotas. + */ +_Requires_lock_held_(PspQuotaLock) +VOID +NTAPI +PspReturnExcessQuotas( + _In_ PS_QUOTA_TYPE QuotaType, + _Outptr_ PSIZE_T ReturnedQuotas) +{ + PLIST_ENTRY PspQuotaList; + PEPROCESS_QUOTA_BLOCK QuotaBlockFromList; + SIZE_T AmountToReturn = 0; + + /* + * We must be in a dispatch level interrupt here + * as we should be under a spin lock. + */ + ASSERT_IRQL_EQUAL(DISPATCH_LEVEL); + + /* + * Loop over the quota block lists and reap + * whatever quotas we haven't returned which + * is needed to free up resources. + */ + for (PspQuotaList = PspQuotaBlockList.Flink; + PspQuotaList != &PspQuotaBlockList; + PspQuotaList = PspQuotaList->Flink) + { + /* Gather the quota block from the list */ + QuotaBlockFromList = CONTAINING_RECORD(PspQuotaList, EPROCESS_QUOTA_BLOCK, QuotaList); + + /* + * Gather any unreturned quotas and cache + * them to a variable. + */ + AmountToReturn += InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Return, 0); + + /* + * If no other process is taking use of this + * block, then it means that this block has + * only shared pool quota and the last process + * no longer uses this block. If the limit is + * grater than the usage then trim the limit + * and use that as additional amount of quota + * to return. + */ + if (QuotaBlockFromList->ProcessCount == 0) + { + if (QuotaBlockFromList->QuotaEntry[QuotaType].Usage < + QuotaBlockFromList->QuotaEntry[QuotaType].Limit) + { + InterlockedExchangeSizeT(&QuotaBlockFromList->QuotaEntry[QuotaType].Limit, + QuotaBlockFromList->QuotaEntry[QuotaType].Usage); + AmountToReturn += QuotaBlockFromList->QuotaEntry[QuotaType].Limit; + } + } + } + + /* Invoke Mm to return quotas */ + DPRINT("PspReturnExcessQuotas(): Amount of quota released -- %lu\n", AmountToReturn); + MmReturnPoolQuota(QuotaType, AmountToReturn); + *ReturnedQuotas = AmountToReturn; +} + +/** + * @brief + * Internal kernel function that provides the + * bulk logic of process quota charging, + * necessary for exported kernel routines + * needed for quota management. + * + * @param[in] Process + * A process, represented as a EPROCESS object. + * This parameter is used to charge the own + * process' quota usage. + * + * @param[in] QuotaBlock + * The quota block which quotas are to be charged. + * This block can either come from the process itself + * or from an object with specified quota charges. + * + * @param[in] QuotaType + * The quota type which quota in question is to + * be charged. The permitted types are PsPagedPool, + * PsNonPagedPool and PsPageFile. + * + * @param[in] Amount + * The amount of quota to be charged. + * + * @return + * Returns STATUS_SUCCESS if quota charging has + * been done successfully without problemns. + * STATUS_QUOTA_EXCEEDED is returned if the caller + * wants to charge quotas with amount way over + * the limits. STATUS_PAGEFILE_QUOTA_EXCEEDED + * is returned for the same situation but + * specific to page files instead. */ NTSTATUS NTAPI -PspChargeProcessQuotaSpecifiedPool(IN PEPROCESS Process, - IN PS_QUOTA_TYPE QuotaType, - IN SIZE_T Amount) +PspChargeProcessQuotaSpecifiedPool( + _In_opt_ PEPROCESS Process, + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, + _In_ PS_QUOTA_TYPE QuotaType, + _In_ SIZE_T Amount) { KIRQL OldIrql; - ASSERT(Process); - ASSERT(Process != PsInitialSystemProcess); - ASSERT(QuotaType < PsQuotaTypes); - ASSERT(Process->QuotaBlock); + SIZE_T ReturnedQuotas; + SIZE_T UpdatedLimit; - /* Guard our quota in a spin lock */ + /* Sanity checks */ + ASSERT(QuotaType < PsQuotaTypes); + ASSERT((SSIZE_T)Amount >= 0); + + /* Guard ourselves in a spin lock */ KeAcquireSpinLock(&PspQuotaLock, &OldIrql); - if (Process->QuotaUsage[QuotaType] + Amount > - Process->QuotaBlock->QuotaEntry[QuotaType].Limit) + /* Are we within the bounds of quota limit? */ + if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount > + QuotaBlock->QuotaEntry[QuotaType].Limit && + QuotaBlock != &PspDefaultQuotaBlock) { - DPRINT1("Quota exceeded, but ROS will let it slide...\n"); - KeReleaseSpinLock(&PspQuotaLock, OldIrql); - return STATUS_SUCCESS; - //return STATUS_QUOTA_EXCEEDED; /* caller raises the exception */ + /* We aren't... Is this a page file quota charging? */ + if (QuotaType == PsPageFile) + { + /* It is, return the appropriate status code */ + DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit on page file quota (limit -- %lu || amount -- %lu)\n", + QuotaBlock->QuotaEntry[QuotaType].Limit, Amount); + return STATUS_PAGEFILE_QUOTA_EXCEEDED; + } + + /* + * This is not a page file charge. What we can do at best + * in this scenario is to attempt to raise (expand) the + * quota limit charges of the block. + */ + if (!MmRaisePoolQuota(QuotaType, + QuotaBlock->QuotaEntry[QuotaType].Limit, + &UpdatedLimit)) + { + /* + * We can't? It could be that we must free + * up some resources in order to raise the + * limit, which in that case we must return + * the excess of quota that hasn't been + * returned. If we haven't returned anything + * then what we're doing here is futile. + * Bail out... + */ + PspReturnExcessQuotas(QuotaType, &ReturnedQuotas); + if (ReturnedQuotas == 0) + { + DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Failed to free some resources in order to raise quota limits...\n"); + KeReleaseSpinLock(&PspQuotaLock, OldIrql); + return STATUS_QUOTA_EXCEEDED; + } + + /* Try to raise the quota limits again */ + MmRaisePoolQuota(QuotaType, + QuotaBlock->QuotaEntry[QuotaType].Limit, + &UpdatedLimit); + } + + /* Enforce a new raised limit */ + InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit, UpdatedLimit); + + /* + * Now determine if the current usage and the + * amounting by the caller still exceeds the + * quota limit of the process. If it's still + * over the limit then there's nothing we can + * do, so fail. + */ + if (QuotaBlock->QuotaEntry[QuotaType].Usage + Amount > + QuotaBlock->QuotaEntry[QuotaType].Limit) + { + DPRINT1("PspChargeProcessQuotaSpecifiedPool(): Quota amount exceeds the limit (limit -- %lu || amount -- %lu)\n", + QuotaBlock->QuotaEntry[QuotaType].Limit, Amount); + return STATUS_QUOTA_EXCEEDED; + } } - InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[QuotaType], Amount); + /* Update the quota usage */ + InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, Amount); - if (Process->QuotaPeak[QuotaType] < Process->QuotaUsage[QuotaType]) + /* Update the entry peak if it's less than the usage */ + if (QuotaBlock->QuotaEntry[QuotaType].Peak < + QuotaBlock->QuotaEntry[QuotaType].Usage) { - Process->QuotaPeak[QuotaType] = Process->QuotaUsage[QuotaType]; + InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Peak, + QuotaBlock->QuotaEntry[QuotaType].Usage); } + /* Are we being given a process as well? */ + if (Process) + { + /* We're being given, check that's not a system one */ + ASSERT(Process != PsInitialSystemProcess); + + InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], Amount); + + /* + * OK, we've now updated the quota usage of the process + * based upon the amount that the caller wanted to charge. + * Although the peak of process quota can be less than it was + * before so update the peaks as well accordingly. + */ + if (Process->QuotaPeak[QuotaType] < Process->QuotaUsage[QuotaType]) + { + InterlockedExchangeSizeT(&Process->QuotaPeak[QuotaType], + Process->QuotaUsage[QuotaType]); + } + } + + /* Release the lock */ KeReleaseSpinLock(&PspQuotaLock, OldIrql); return STATUS_SUCCESS; } -/* - * Private helper to remove quota charge from the specified process quota. - * Notes: Conceptually translation unit local/private. +/** + * @brief + * Internal kernel function that provides the + * bulk logic of process quota returning. It + * returns (takes away) quotas back from a + * process and/or quota block, which is + * the opposite of charging quotas. + * + * @param[in] Process + * A process, represented as a EPROCESS object. + * This parameter is used to return the own + * process' quota usage. + * + * @param[in] QuotaBlock + * The quota block which quotas are to be returned. + * This block can either come from the process itself + * or from an object with specified quota charges. + * + * @param[in] QuotaType + * The quota type which quota in question is to + * be returned. The permitted types are PsPagedPool, + * PsNonPagedPool and PsPageFile. + * + * @param[in] Amount + * The amount of quota to be returned. + * + * @return + * Nothing. */ VOID NTAPI -PspReturnProcessQuotaSpecifiedPool(IN PEPROCESS Process, - IN PS_QUOTA_TYPE QuotaType, - IN SIZE_T Amount) +PspReturnProcessQuotaSpecifiedPool( + _In_opt_ PEPROCESS Process, + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, + _In_ PS_QUOTA_TYPE QuotaType, + _In_ SIZE_T Amount) { KIRQL OldIrql; - ASSERT(Process); - ASSERT(Process != PsInitialSystemProcess); - ASSERT(QuotaType < PsQuotaTypes); - ASSERT(!(Amount & 0x80000000)); /* we need to be able to negate it */ + SIZE_T ReturnThreshold; + SIZE_T AmountToReturn = 0; - /* Guard our quota in a spin lock */ + /* Sanity checks */ + ASSERT(QuotaType < PsQuotaTypes); + ASSERT((SSIZE_T)Amount >= 0); + + /* Guard ourselves in a spin lock */ KeAcquireSpinLock(&PspQuotaLock, &OldIrql); - if (Process->QuotaUsage[QuotaType] < Amount) + /* Does the caller return more quota than it was previously charged? */ + if ((Process && Process->QuotaUsage[QuotaType] < Amount) || + QuotaBlock->QuotaEntry[QuotaType].Usage < Amount) { - DPRINT1("WARNING: Process->QuotaUsage sanity check failed.\n"); - } - else - { - InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[QuotaType], - -(LONG)Amount); + /* It does, crash the system! */ + KeBugCheckEx(QUOTA_UNDERFLOW, + (ULONG_PTR)Process, + (ULONG_PTR)QuotaType, + Process ? (ULONG_PTR)Process->QuotaUsage[QuotaType] : + QuotaBlock->QuotaEntry[QuotaType].Usage, + (ULONG_PTR)Amount); } + /* The return threshold can be non paged or paged */ + ReturnThreshold = QuotaType ? PSP_NON_PAGED_POOL_QUOTA_THRESHOLD : PSP_PAGED_POOL_QUOTA_THRESHOLD; + + /* + * We need to trim the quota limits based on the + * amount we're going to return quotas back. + */ + if ((QuotaType != PsPageFile && QuotaBlock != &PspDefaultQuotaBlock) && + (QuotaBlock->QuotaEntry[QuotaType].Limit > QuotaBlock->QuotaEntry[QuotaType].Usage + ReturnThreshold)) + { + /* + * If the amount to return exceeds the threshold, + * the new amount becomes the default, otherwise + * the amount is just the one given by the caller. + */ + AmountToReturn = min(Amount, ReturnThreshold); + + /* Add up the lots to the Return field */ + InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Return, AmountToReturn); + + /* + * If the amount to return exceeds the threshold then + * we have lots of quota to return to Mm. So do it so + * and zerou out the Return field. + */ + if (QuotaBlock->QuotaEntry[QuotaType].Return > ReturnThreshold) + { + MmReturnPoolQuota(QuotaType, QuotaBlock->QuotaEntry[QuotaType].Return); + InterlockedExchangeSizeT(QuotaBlock->QuotaEntry[QuotaType].Return, 0); + } + + /* And try to trim the limit */ + InterlockedExchangeSizeT(&QuotaBlock->QuotaEntry[QuotaType].Limit, + QuotaBlock->QuotaEntry[QuotaType].Limit - AmountToReturn); + } + + /* Update the usage member of the block */ + InterlockedExchangeAddSizeT(&QuotaBlock->QuotaEntry[QuotaType].Usage, -(LONG_PTR)Amount); + + /* Are we being given a process? */ + if (Process) + { + /* We're being given, check that's not a system one */ + ASSERT(Process != PsInitialSystemProcess); + + /* Decrease the process' quota usage */ + InterlockedExchangeAddSizeT(&Process->QuotaUsage[QuotaType], -(LONG_PTR)Amount); + } + + /* We're done, release the lock */ KeReleaseSpinLock(&PspQuotaLock, OldIrql); } /* FUNCTIONS ***************************************************************/ +/** + * @brief + * Initializes the quota system during boot + * phase of the system, which sets up the + * default quota block that is used across + * several processes. + * + * @return + * Nothing. + */ CODE_SEG("INIT") VOID NTAPI PsInitializeQuotaSystem(VOID) { + /* Initialize the default block */ RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock)); + + /* Assign the default quota limits */ PspDefaultQuotaBlock.QuotaEntry[PsNonPagedPool].Limit = (SIZE_T)-1; PspDefaultQuotaBlock.QuotaEntry[PsPagedPool].Limit = (SIZE_T)-1; PspDefaultQuotaBlock.QuotaEntry[PsPageFile].Limit = (SIZE_T)-1; + + /* + * Set up the count references as the + * default block will going to be used. + */ + PspDefaultQuotaBlock.ReferenceCount = 1; + PspDefaultQuotaBlock.ProcessCount = 1; + + /* Assign that block to initial process */ PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock; } +/** + * @brief + * Inherits the quota block to another newborn + * (child) process. If there's no parent + * process, the default quota block is + * assigned. + * + * @param[in] Process + * The child process which quota block + * is to be given. + * + * @param[in] ParentProcess + * The parent process. + * + * @return + * Nothing. + */ VOID NTAPI -PspInheritQuota(PEPROCESS Process, PEPROCESS ParentProcess) +PspInheritQuota( + _In_ PEPROCESS Process, + _In_opt_ PEPROCESS ParentProcess) { + PEPROCESS_QUOTA_BLOCK QuotaBlock; + if (ParentProcess != NULL) { - PEPROCESS_QUOTA_BLOCK QuotaBlock = ParentProcess->QuotaBlock; - - ASSERT(QuotaBlock != NULL); - - (void)InterlockedIncrementUL(&QuotaBlock->ReferenceCount); - - Process->QuotaBlock = QuotaBlock; + ASSERT(ParentProcess->QuotaBlock != NULL); + QuotaBlock = ParentProcess->QuotaBlock; } else - Process->QuotaBlock = &PspDefaultQuotaBlock; + { + QuotaBlock = &PspDefaultQuotaBlock; + } + + InterlockedIncrementSizeT(&QuotaBlock->ProcessCount); + InterlockedIncrementSizeT(&QuotaBlock->ReferenceCount); + + Process->QuotaBlock = QuotaBlock; } +/** + * @brief + * Inserts the new quota block into + * the quota list. + * + * @param[in] QuotaBlock + * The new quota block. + * + * @return + * Nothing. + */ VOID NTAPI PspInsertQuotaBlock( - PEPROCESS_QUOTA_BLOCK QuotaBlock) + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) { KIRQL OldIrql; @@ -143,45 +525,257 @@ PspInsertQuotaBlock( KeReleaseSpinLock(&PspQuotaLock, OldIrql); } +/** + * @brief + * De-references a quota block when quotas + * have been returned back because of an + * object de-allocation or when a process + * gets destroyed. If the last instance + * that held up the block gets de-referenced + * the function will perform a cleanup against + * that block and it'll free the quota block + * from memory. + * + * @param[in] Process + * A pointer to a process that de-references the + * quota block. + * + * @param[in] QuotaBlock + * A pointer to a quota block that is to be + * de-referenced. This block can come from a + * process that references it or an object. + * + * @return + * Nothing. + */ VOID NTAPI -PspDestroyQuotaBlock(PEPROCESS Process) +PspDereferenceQuotaBlock( + _In_opt_ PEPROCESS Process, + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock) { - PEPROCESS_QUOTA_BLOCK QuotaBlock = Process->QuotaBlock; + ULONG PsQuotaTypeIndex; KIRQL OldIrql; + /* Make sure the quota block is not trash */ + ASSERT(QuotaBlock); + + /* Iterate over the process quota types if we have a process */ + if (Process) + { + for (PsQuotaTypeIndex = PsNonPagedPool; PsQuotaTypeIndex < PsQuotaTypes; PsQuotaTypeIndex++) + { + /* + * We need to make sure that the quota usage + * uniquely associated with the process is 0 + * on that moment the process gets destroyed. + */ + ASSERT(Process->QuotaUsage[PsQuotaTypeIndex] == 0); + } + + /* As the process is now gone, decrement the process count */ + InterlockedDecrementUL(&QuotaBlock->ProcessCount); + } + + /* If no one is using this block, begin to destroy it */ if (QuotaBlock != &PspDefaultQuotaBlock && InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0) { + /* Acquire the quota lock */ KeAcquireSpinLock(&PspQuotaLock, &OldIrql); + + /* Return all the quotas back to Mm and remove the quota from list */ + PspReturnQuotasOnDestroy(QuotaBlock); RemoveEntryList(&QuotaBlock->QuotaList); + + /* Release the lock and free the block */ KeReleaseSpinLock(&PspQuotaLock, OldIrql); - ExFreePool(QuotaBlock); + ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK); } } -/* - * @implemented +/** + * @brief + * Returns the shared (paged and non paged) + * pool quotas. The function is used exclusively + * by the Object Manager to manage quota returns + * handling of objects. + * + * @param[in] QuotaBlock + * The quota block which quotas are to + * be returned. + * + * @param[in] AmountToReturnPaged + * The amount of paged quotas quotas to + * be returned. + * + * @param[in] AmountToReturnNonPaged + * The amount of non paged quotas to + * be returned. + * + * @return + * Nothing. + */ +VOID +NTAPI +PsReturnSharedPoolQuota( + _In_ PEPROCESS_QUOTA_BLOCK QuotaBlock, + _In_ SIZE_T AmountToReturnPaged, + _In_ SIZE_T AmountToReturnNonPaged) +{ + /* Sanity check */ + ASSERT(QuotaBlock); + + /* Return the pool quotas if there're any */ + if (AmountToReturnPaged != 0) + { + PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsPagedPool, AmountToReturnPaged); + } + + if (AmountToReturnNonPaged != 0) + { + PspReturnProcessQuotaSpecifiedPool(NULL, QuotaBlock, PsNonPagedPool, AmountToReturnNonPaged); + } + + DPRINT("PsReturnSharedPoolQuota(): Amount returned back (paged %lu -- non paged %lu)\n", AmountToReturnPaged, AmountToReturnNonPaged); + + /* Dereference the quota block */ + PspDereferenceQuotaBlock(NULL, QuotaBlock); +} + +/** + * @brief + * Charges the shared (paged and non paged) + * pool quotas. The function is used exclusively + * by the Object Manager to manage quota charges + * handling of objects. + * + * @param[in] Process + * The process which quotas are to + * be charged within its quota block. + * + * @param[in] AmountToChargePaged + * The amount of paged quotas quotas to + * be charged. + * + * @param[in] AmountToChargeNonPaged + * The amount of non paged quotas to + * be charged. + * + * @return + * Returns the charged quota block, which it'll + * be used by the Object Manager to attach + * the charged quotas information to the object. + * If the function fails to charge quotas, NULL + * is returned to the caller. + */ +PEPROCESS_QUOTA_BLOCK +NTAPI +PsChargeSharedPoolQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T AmountToChargePaged, + _In_ SIZE_T AmountToChargeNonPaged) +{ + NTSTATUS Status; + + /* Sanity checks */ + ASSERT(Process); + ASSERT(Process->QuotaBlock); + + /* Do we have some paged pool quota to charge? */ + if (AmountToChargePaged != 0) + { + /* We do, charge! */ + Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged); + if (!NT_SUCCESS(Status)) + { + DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx)\n", Status); + return NULL; + } + } + + /* Do we have some non paged pool quota to charge? */ + if (AmountToChargeNonPaged != 0) + { + /* We do, charge! */ + Status = PspChargeProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsNonPagedPool, AmountToChargeNonPaged); + if (!NT_SUCCESS(Status)) + { + DPRINT1("PsChargeSharedPoolQuota(): Failed to charge the shared pool quota (Status 0x%lx). Attempting to return some paged pool back...\n", Status); + PspReturnProcessQuotaSpecifiedPool(NULL, Process->QuotaBlock, PsPagedPool, AmountToChargePaged); + return NULL; + } + } + + /* We have charged the quotas of an object, increment the reference */ + InterlockedIncrementSizeT(&Process->QuotaBlock->ReferenceCount); + + DPRINT("PsChargeSharedPoolQuota(): Amount charged (paged %lu -- non paged %lu)\n", AmountToChargePaged, AmountToChargeNonPaged); + return Process->QuotaBlock; +} + +/** + * @brief + * Charges the process page file quota. + * The function is used internally by + * the kernel. + * + * @param[in] Process + * The process which page file quota is + * to be charged. + * + * @param[in] Amount + * The amount of page file quota to charge. + * + * @return + * Returns STATUS_SUCCESS if quota charging has + * been done with success, otherwise a NTSTATUS + * code of STATUS_PAGEFILE_QUOTA_EXCEEDED is + * returned. */ NTSTATUS NTAPI -PsChargeProcessPageFileQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsChargeProcessPageFileQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; - return PspChargeProcessQuotaSpecifiedPool(Process, PsPageFile, Amount); + return PspChargeProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount); } -/* - * @implemented +/** + * @brief + * Charges the pool quota of a given process. + * The kind of pool quota to charge is determined + * by the PoolType parameter. + * + * @param[in] Process + * The process which quota is to be + * charged. + * + * @param[in] PoolType + * The pool type to choose to charge quotas + * (e.g. PagedPool or NonPagedPool). + * + * @param[in] Amount + * The amount of quotas to charge into a process. + * + * @return + * Nothing. + * + * @remarks + * The function raises an exception if STATUS_QUOTA_EXCEEDED + * status code is returned. Callers are responsible on their + * own to handle the raised exception. */ VOID NTAPI -PsChargePoolQuota(IN PEPROCESS Process, - IN POOL_TYPE PoolType, - IN SIZE_T Amount) +PsChargePoolQuota( + _In_ PEPROCESS Process, + _In_ POOL_TYPE PoolType, + _In_ SIZE_T Amount) { NTSTATUS Status; ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); @@ -194,71 +788,156 @@ PsChargePoolQuota(IN PEPROCESS Process, if (!NT_SUCCESS(Status)) ExRaiseStatus(Status); } -/* - * @implemented +/** + * @brief + * Charges the non paged pool quota + * of a given process. + * + * @param[in] Process + * The process which non paged quota + * is to be charged. + * + * @param[in] Amount + * The amount of quotas to charge into a process. + * + * @return + * Returns STATUS_SUCCESS if quota charing has + * suceeded, STATUS_QUOTA_EXCEEDED is returned + * otherwise to indicate the caller attempted + * to charge quotas over the limits. */ NTSTATUS NTAPI -PsChargeProcessNonPagedPoolQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsChargeProcessNonPagedPoolQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Call the general function */ return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount); } -/* - * @implemented +/** + * @brief + * Charges the paged pool quota of a + * given process. + * + * @param[in] Process + * The process which paged quota + * is to be charged. + * + * @param[in] Amount + * The amount of quotas to charge into a process. + * + * @return + * Returns STATUS_SUCCESS if quota charing has + * suceeded, STATUS_QUOTA_EXCEEDED is returned + * otherwise to indicate the caller attempted + * to charge quotas over the limits. */ NTSTATUS NTAPI -PsChargeProcessPagedPoolQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsChargeProcessPagedPoolQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Call the general function */ return PsChargeProcessPoolQuota(Process, PagedPool, Amount); } -/* - * @implemented +/** + * @brief + * Charges the process' quota pool. + * The type of quota to be charged + * depends upon the PoolType parameter. + * + * @param[in] Process + * The process which quota is to + * be charged. + * + * @param[in] PoolType + * The type of quota pool to charge (e.g. + * PagedPool or NonPagedPool). + * + * @param[in] Amount + * The amount of quotas to charge into a process. + * + * @return + * Returns STATUS_SUCCESS if quota charing has + * suceeded, STATUS_QUOTA_EXCEEDED is returned + * otherwise to indicate the caller attempted + * to charge quotas over the limits. */ NTSTATUS NTAPI -PsChargeProcessPoolQuota(IN PEPROCESS Process, - IN POOL_TYPE PoolType, - IN SIZE_T Amount) +PsChargeProcessPoolQuota( + _In_ PEPROCESS Process, + _In_ POOL_TYPE PoolType, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; return PspChargeProcessQuotaSpecifiedPool(Process, + Process->QuotaBlock, (PoolType & PAGED_POOL_MASK), Amount); } -/* - * @implemented +/** + * @brief + * Returns the pool quota that the + * process was taking up. + * + * @param[in] Process + * The process which quota is to + * be returned. + * + * @param[in] PoolType + * The type of quota pool to return (e.g. + * PagedPool or NonPagedPool). + * + * @param[in] Amount + * The amount of quotas to return from a process. + * + * @return + * Nothing. */ VOID NTAPI -PsReturnPoolQuota(IN PEPROCESS Process, - IN POOL_TYPE PoolType, - IN SIZE_T Amount) +PsReturnPoolQuota( + _In_ PEPROCESS Process, + _In_ POOL_TYPE PoolType, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return; PspReturnProcessQuotaSpecifiedPool(Process, + Process->QuotaBlock, (PoolType & PAGED_POOL_MASK), Amount); } -/* - * @implemented +/** + * @brief + * Returns the non paged quota pool + * that the process was taking up. + * + * @param[in] Process + * The process which non paged quota + * is to be returned. + * + * @param[in] Amount + * The amount of quotas to return from a process. + * + * @return + * Nothing. */ VOID NTAPI -PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsReturnProcessNonPagedPoolQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return; @@ -266,13 +945,26 @@ PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process, PsReturnPoolQuota(Process, NonPagedPool, Amount); } -/* - * @implemented +/** + * @brief + * Returns the paged pool quota that + * the process was taking up. + * + * @param[in] Process + * The process which paged pool + * quota is to be returned. + * + * @param[in] Amount + * The amount of quotas to return from a process. + * + * @return + * Nothing. */ VOID NTAPI -PsReturnProcessPagedPoolQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsReturnProcessPagedPoolQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return; @@ -280,21 +972,74 @@ PsReturnProcessPagedPoolQuota(IN PEPROCESS Process, PsReturnPoolQuota(Process, PagedPool, Amount); } -/* - * @implemented +/** + * @brief + * Returns the page file quota that the + * process was taking up. The function + * is used exclusively by the kernel. + * + * @param[in] Process + * The process which pagefile quota is + * to be returned. + * + * @param[in] Amount + * The amount of quotas to return from a process. + * + * @return + * Returns STATUS_SUCCESS. */ NTSTATUS NTAPI -PsReturnProcessPageFileQuota(IN PEPROCESS Process, - IN SIZE_T Amount) +PsReturnProcessPageFileQuota( + _In_ PEPROCESS Process, + _In_ SIZE_T Amount) { /* Don't do anything for the system process */ if (Process == PsInitialSystemProcess) return STATUS_SUCCESS; - PspReturnProcessQuotaSpecifiedPool(Process, PsPageFile, Amount); + PspReturnProcessQuotaSpecifiedPool(Process, Process->QuotaBlock, PsPageFile, Amount); return STATUS_SUCCESS; } +/** + * @brief + * This function adjusts the working set limits + * of a process and sets up new quota limits + * when necessary. The function is used + * when the caller requests to set up + * new working set sizes. + * + * @param[in] Process + * The process which quota limits or working + * set sizes are to be changed. + * + * @param[in] Unused + * This parameter is unused. + * + * @param[in] QuotaLimits + * An arbitrary pointer that points to a quota + * limits structure, needed to determine on + * setting up new working set sizes. + * + * @param[in] QuotaLimitsLength + * The length of QuotaLimits buffer, which size + * is expressed in bytes. + * + * @param[in] PreviousMode + * The processor level access mode. + * + * @return + * Returns STATUS_SUCCESS if the function has completed + * successfully. STATUS_INVALID_PARAMETER is returned if + * the caller has given a quota limits structure with + * invalid data. STATUS_INFO_LENGTH_MISMATCH is returned + * if the length of QuotaLimits pointed by QuotaLimitsLength + * is not right. STATUS_PRIVILEGE_NOT_HELD is returned if + * the calling thread of the process doesn't hold the necessary + * right privilege to increase quotas. STATUS_NO_MEMORY is + * returned if a memory pool allocation has failed. A failure + * NTSTATUS code is returned otherwise. + */ NTSTATUS NTAPI PspSetQuotaLimits( @@ -458,5 +1203,4 @@ PspSetQuotaLimits( return Status; } - /* EOF */