https://git.reactos.org/?p=reactos.git;a=commitdiff;h=b22eefac8886d9a40bdbf…
commit b22eefac8886d9a40bdbf3f40f67405d7ee9c121
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 21:02:51 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:09 2022 +0100
[NTOS:PS] Process Quota Overhaul
-- Rewrite PspChargeProcessQuotaSpecifiedPool and PspReturnProcessQuotaSpecifiedPool
private kernel routines, with the goal to implement the algorithms necessary to manage the
fields of quota blocks (Usage, Return, Limit and Peak).
-- Invoke the Mm if quota limit raising or returning has to be done
-- When destroying a quota block, make sure that we're giving back all the rest of
non-returned quotas to Memory Mm
-- Crash the system with QUOTA_UNDERFLOW if someone is returning way too much quota
than it was previously charged
-- When a process exits, ensure that it doesn't hold up any charged quotas in
QuotaUsage field of the process object, that way we're enforcing proper kernel
consistency
-- Implement PsChargeSharedPoolQuota and PsChargeProcessPageFileQuota functions, used
exclusively by the Object Manager. These routines are used to charge or return amount of
quotas of a newly created object.
-- On PspInheritQuota, when assigning to process the default quota block if no parent
process is given, we must increment the reference counts as we're using it
-- Handle the ProcessCount reference field, as it wasn't used
-- Annotate the functions with SAL
-- Document the code
=== REMARKS ===
Windows LogOn (Winlogon) is responsible for setting up a different quota block for all
the processes within an interactive session, which is what we don't do. What we're
currently doing instead is we're using the default block, PspDefaultQuotaBlock, for
all the processes
across the system. The default block contains the default limits of -1 (which would
imply no limits). By definition, the kernel won't ever return STATUS_QUOTA_EXCEEDED as
we literally don't set up a definite limit for regular processes. This situation has
to be tackled
in the future.
=== TODO FOR FUTURE ===
Most of the code in PspChargeProcessQuotaSpecifiedPool and
PspReturnProcessQuotaSpecifiedPool private routines must be refactored in order to reduce
the usage of the quota spin lock, possibly wrapping such code in a loop and whatnot.
CORE-17784
---
ntoskrnl/include/internal/tag.h | 1 +
ntoskrnl/ps/quota.c | 942 +++++++++++++++++++++++++++++++++++-----
2 files changed, 844 insertions(+), 99 deletions(-)
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(a)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 <alex(a)relsoft.net>
+ * Copyright 2007 Mike Nordell
+ * Copyright 2021 George Bișoc <george.bisoc(a)reactos.org>
*/
/* 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);
+ SIZE_T ReturnedQuotas;
+ SIZE_T UpdatedLimit;
+
+ /* Sanity checks */
ASSERT(QuotaType < PsQuotaTypes);
- ASSERT(Process->QuotaBlock);
+ ASSERT((SSIZE_T)Amount >= 0);
- /* Guard our quota in a spin lock */
+ /* 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);
+ SIZE_T ReturnThreshold;
+ SIZE_T AmountToReturn = 0;
+
+ /* Sanity checks */
ASSERT(QuotaType < PsQuotaTypes);
- ASSERT(!(Amount & 0x80000000)); /* we need to be able to negate it */
+ ASSERT((SSIZE_T)Amount >= 0);
- /* Guard our quota in a spin lock */
+ /* 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");
+ /* 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);
}
- else
+
+ /* 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))
{
- InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[QuotaType],
- -(LONG)Amount);
+ /*
+ * 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 */