https://git.reactos.org/?p=reactos.git;a=commitdiff;h=0c07eac5b40b05df5153a…
commit 0c07eac5b40b05df5153ac5d44d85e1952e09645
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 21:04:42 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:10 2022 +0100
[NTOS:OB] Charge/Return pool quotas of objects
As it currently stands the Object Manager doesn't charge any quotas when objects are created, nor it returns quotas when objects are de-allocated and freed from the objects namespace database. This alone can bring inconsistencies in the kernel as we simply don't know what is the amount charged in an object and thus we aren't keeping track of quotas flow.
Now with both PsReturnSharedPoolQuota and PsChargeSharedPoolQuota implemented, the Object Manager can now track the said flow of quotas every time an object is created or de-allocated, thus enforcing consistency with the use of quota resources.
---
ntoskrnl/ob/obhandle.c | 21 +++++++++++++--------
ntoskrnl/ob/oblife.c | 13 ++++++-------
2 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/ntoskrnl/ob/obhandle.c b/ntoskrnl/ob/obhandle.c
index cff2cbb80e9..bf9f1f09cb5 100644
--- a/ntoskrnl/ob/obhandle.c
+++ b/ntoskrnl/ob/obhandle.c
@@ -457,14 +457,19 @@ ObpChargeQuotaForObject(IN POBJECT_HEADER ObjectHeader,
NonPagedPoolCharge = ObjectType->TypeInfo.DefaultNonPagedPoolCharge;
}
- /* Charge the quota */
- ObjectHeader->QuotaBlockCharged = (PVOID)1;
- DPRINT("FIXME: Should charge: %lx %lx\n", PagedPoolCharge, NonPagedPoolCharge);
-#if 0
- PsChargeSharedPoolQuota(PsGetCurrentProcess(),
- PagedPoolCharge,
- NonPagedPoolCharge);
-#endif
+ /* Is this the system process? */
+ if (PsGetCurrentProcess() == PsInitialSystemProcess)
+ {
+ /* It is, don't do anything */
+ ObjectHeader->QuotaBlockCharged = OBP_SYSTEM_PROCESS_QUOTA;
+ }
+ else
+ {
+ /* Charge the quota */
+ ObjectHeader->QuotaBlockCharged = PsChargeSharedPoolQuota(PsGetCurrentProcess(),
+ PagedPoolCharge,
+ NonPagedPoolCharge);
+ }
/* Check if we don't have a quota block */
if (!ObjectHeader->QuotaBlockCharged) return STATUS_QUOTA_EXCEEDED;
diff --git a/ntoskrnl/ob/oblife.c b/ntoskrnl/ob/oblife.c
index 06c0d96e904..f46cb4098b3 100644
--- a/ntoskrnl/ob/oblife.c
+++ b/ntoskrnl/ob/oblife.c
@@ -110,13 +110,12 @@ ObpDeallocateObject(IN PVOID Object)
}
/* Return the quota */
- DPRINT("FIXME: Should return quotas: %lx %lx\n", PagedPoolCharge, NonPagedPoolCharge);
-#if 0
- PsReturnSharedPoolQuota(ObjectHeader->QuotaBlockCharged,
- PagedPoolCharge,
- NonPagedPoolCharge);
-#endif
-
+ if (Header->QuotaBlockCharged != OBP_SYSTEM_PROCESS_QUOTA)
+ {
+ PsReturnSharedPoolQuota(Header->QuotaBlockCharged,
+ PagedPoolCharge,
+ NonPagedPoolCharge);
+ }
}
}
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=ee697cfeef5dc1c2d6c0b…
commit ee697cfeef5dc1c2d6c0b3d07e6c8aaaf92d2fa1
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 21:03:42 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:10 2022 +0100
[NTOS:PS] Dereference the quota block during process cleanup
Ensure that when we're cleaning up the EPROCESS object, that we are dereferencing the quota block the process in question was using. The routine will automatically request a quota block cleanup if the process that dereferenced the quota block was the last.
---
ntoskrnl/ps/kill.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/ntoskrnl/ps/kill.c b/ntoskrnl/ps/kill.c
index 27d559ff510..115768687c4 100644
--- a/ntoskrnl/ps/kill.c
+++ b/ntoskrnl/ps/kill.c
@@ -381,8 +381,12 @@ PspDeleteProcess(IN PVOID ObjectBody)
/* Dereference the Device Map */
ObDereferenceDeviceMap(Process);
- /* Destroy the Quota Block */
- PspDestroyQuotaBlock(Process);
+ /*
+ * Dereference the quota block, the function
+ * will invoke a quota block cleanup if the
+ * block itself is no longer used by anybody.
+ */
+ PspDereferenceQuotaBlock(Process, Process->QuotaBlock);
}
VOID
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 */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=1649a89cfaa38a5843d82…
commit 1649a89cfaa38a5843d823db87b2291f57a5e213
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 21:00:52 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:09 2022 +0100
[NTOS:MM] Implement Raise/Return pool quota functions
This implements both MmRaisePoolQuota and MmReturnPoolQuota functions, which serve exclusively for quota pool management. The process manager communicates with the memory manager in a call of need to charge or return pool quota limits.
---
ntoskrnl/mm/ARM3/pool.c | 192 ++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 177 insertions(+), 15 deletions(-)
diff --git a/ntoskrnl/mm/ARM3/pool.c b/ntoskrnl/mm/ARM3/pool.c
index fe8d6cd1a74..8ab8ae293a4 100644
--- a/ntoskrnl/mm/ARM3/pool.c
+++ b/ntoskrnl/mm/ARM3/pool.c
@@ -24,6 +24,8 @@ PFN_NUMBER MiStartOfInitialPoolFrame, MiEndOfInitialPoolFrame;
KGUARDED_MUTEX MmPagedPoolMutex;
MM_PAGED_POOL_INFO MmPagedPoolInfo;
SIZE_T MmAllocatedNonPagedPool;
+SIZE_T MmTotalNonPagedPoolQuota;
+SIZE_T MmTotalPagedPoolQuota;
ULONG MmSpecialPoolTag;
ULONG MmConsumedPoolPercentage;
BOOLEAN MmProtectFreedNonPagedPool;
@@ -1269,21 +1271,6 @@ MiFreePoolPages(IN PVOID StartingVa)
return NumberOfPages;
}
-
-BOOLEAN
-NTAPI
-MiRaisePoolQuota(IN POOL_TYPE PoolType,
- IN ULONG CurrentMaxQuota,
- OUT PULONG NewMaxQuota)
-{
- //
- // Not implemented
- //
- UNIMPLEMENTED;
- *NewMaxQuota = CurrentMaxQuota + 65536;
- return TRUE;
-}
-
NTSTATUS
NTAPI
MiInitializeSessionPool(VOID)
@@ -1388,6 +1375,181 @@ MiInitializeSessionPool(VOID)
return STATUS_SUCCESS;
}
+/**
+ * @brief
+ * Raises the quota limit, depending on the given
+ * pool type of the quota in question. The routine
+ * is used exclusively by Process Manager for
+ * quota handling.
+ *
+ * @param[in] PoolType
+ * The type of quota pool which the quota in question
+ * has to be raised.
+ *
+ * @param[in] CurrentMaxQuota
+ * The current maximum limit of quota threshold.
+ *
+ * @param[out] NewMaxQuota
+ * The newly raised maximum limit of quota threshold,
+ * returned to the caller.
+ *
+ * @return
+ * Returns TRUE if quota raising procedure has succeeded
+ * without problems, FALSE otherwise.
+ *
+ * @remarks
+ * A spin lock must be held when raising the pool quota
+ * limit to avoid race occurences.
+ */
+_Requires_lock_held_(PspQuotaLock)
+BOOLEAN
+NTAPI
+MmRaisePoolQuota(
+ _In_ POOL_TYPE PoolType,
+ _In_ SIZE_T CurrentMaxQuota,
+ _Out_ PSIZE_T NewMaxQuota)
+{
+ /*
+ * We must be in dispatch level interrupt here
+ * as we should be under a spin lock at this point.
+ */
+ ASSERT_IRQL_EQUAL(DISPATCH_LEVEL);
+
+ switch (PoolType)
+ {
+ case NonPagedPool:
+ {
+ /*
+ * When concerning with a raise (charge) of quota
+ * in a non paged pool scenario, make sure that
+ * we've got at least 200 pages necessary to provide.
+ */
+ if (MmAvailablePages < MI_QUOTA_NON_PAGED_NEEDED_PAGES)
+ {
+ DPRINT1("MmRaisePoolQuota(): Not enough pages available (current pages -- %lu)\n", MmAvailablePages);
+ return FALSE;
+ }
+
+ /*
+ * Check if there's at least some space available
+ * in the non paged pool area.
+ */
+ if (MmMaximumNonPagedPoolInPages < (MmAllocatedNonPagedPool >> PAGE_SHIFT))
+ {
+ /* There's too much allocated space, bail out */
+ DPRINT1("MmRaisePoolQuota(): Failed to increase pool quota, not enough non paged pool space (current size -- %lu || allocated size -- %lu)\n",
+ MmMaximumNonPagedPoolInPages, MmAllocatedNonPagedPool);
+ return FALSE;
+ }
+
+ /* Do we have enough resident pages to increase our quota? */
+ if (MmResidentAvailablePages < MI_NON_PAGED_QUOTA_MIN_RESIDENT_PAGES)
+ {
+ DPRINT1("MmRaisePoolQuota(): Failed to increase pool quota, not enough resident pages available (current available pages -- %lu)\n",
+ MmResidentAvailablePages);
+ return FALSE;
+ }
+
+ /*
+ * Raise the non paged pool quota indicator and set
+ * up new maximum limit of quota for the process.
+ */
+ MmTotalNonPagedPoolQuota += MI_CHARGE_NON_PAGED_POOL_QUOTA;
+ *NewMaxQuota = CurrentMaxQuota + MI_CHARGE_NON_PAGED_POOL_QUOTA;
+ DPRINT("MmRaisePoolQuota(): Non paged pool quota increased (before -- %lu || after -- %lu)\n", CurrentMaxQuota, NewMaxQuota);
+ return TRUE;
+ }
+
+ case PagedPool:
+ {
+ /*
+ * Before raising the quota limit of a paged quota
+ * pool, make sure we've got enough space that is available.
+ * On Windows it seems it wants to check for at least 1 MB of space
+ * needed so that it would be possible to raise the paged pool quota.
+ */
+ if (MmSizeOfPagedPoolInPages < (MmPagedPoolInfo.AllocatedPagedPool >> PAGE_SHIFT))
+ {
+ /* We haven't gotten enough space, bail out */
+ DPRINT1("MmRaisePoolQuota(): Failed to increase pool quota, not enough paged pool space (current size -- %lu || allocated size -- %lu)\n",
+ MmSizeOfPagedPoolInPages, MmPagedPoolInfo.AllocatedPagedPool >> PAGE_SHIFT);
+ return FALSE;
+ }
+
+ /*
+ * Raise the paged pool quota indicator and set
+ * up new maximum limit of quota for the process.
+ */
+ MmTotalPagedPoolQuota += MI_CHARGE_PAGED_POOL_QUOTA;
+ *NewMaxQuota = CurrentMaxQuota + MI_CHARGE_PAGED_POOL_QUOTA;
+ DPRINT("MmRaisePoolQuota(): Paged pool quota increased (before -- %lu || after -- %lu)\n", CurrentMaxQuota, NewMaxQuota);
+ return TRUE;
+ }
+
+ /* Only NonPagedPool and PagedPool are used */
+ DEFAULT_UNREACHABLE;
+ }
+}
+
+/**
+ * @brief
+ * Returns the quota, depending on the given
+ * pool type of the quota in question. The routine
+ * is used exclusively by Process Manager for quota
+ * handling.
+ *
+ * @param[in] PoolType
+ * The type of quota pool which the quota in question
+ * has to be raised.
+ *
+ * @param[in] CurrentMaxQuota
+ * The current maximum limit of quota threshold.
+ *
+ * @return
+ * Nothing.
+ *
+ * @remarks
+ * A spin lock must be held when raising the pool quota
+ * limit to avoid race occurences.
+ */
+_Requires_lock_held_(PspQuotaLock)
+VOID
+NTAPI
+MmReturnPoolQuota(
+ _In_ POOL_TYPE PoolType,
+ _In_ SIZE_T QuotaToReturn)
+{
+ /*
+ * We must be in dispatch level interrupt here
+ * as we should be under a spin lock at this point.
+ */
+ ASSERT_IRQL_EQUAL(DISPATCH_LEVEL);
+
+ switch (PoolType)
+ {
+ case NonPagedPool:
+ {
+ /* This is a non paged pool type, decrease the non paged quota */
+ ASSERT(MmTotalNonPagedPoolQuota >= QuotaToReturn);
+ MmTotalNonPagedPoolQuota -= QuotaToReturn;
+ DPRINT("MmReturnPoolQuota(): Non paged pool quota returned (current size -- %lu)\n", MmTotalNonPagedPoolQuota);
+ break;
+ }
+
+ case PagedPool:
+ {
+ /* This is a paged pool type, decrease the paged quota */
+ ASSERT(MmTotalPagedPoolQuota >= QuotaToReturn);
+ MmTotalPagedPoolQuota -= QuotaToReturn;
+ DPRINT("MmReturnPoolQuota(): Paged pool quota returned (current size -- %lu)\n", MmTotalPagedPoolQuota);
+ break;
+ }
+
+ /* Only NonPagedPool and PagedPool are used */
+ DEFAULT_UNREACHABLE;
+ }
+}
+
/* PUBLIC FUNCTIONS ***********************************************************/
/*
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=32e9710fd107ed8112b48…
commit 32e9710fd107ed8112b48ed594056267762141c9
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 20:59:57 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:09 2022 +0100
[NTOS:OB] Add a system process quota block macro
OBP_SYSTEM_PROCESS_QUOTA is a macro that'll be used as a way to assign a dummy quota block to system processes, as we mustn't do anything to those in case the Object Manager is charging or returning pool quotas.
---
ntoskrnl/include/internal/ob.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/ntoskrnl/include/internal/ob.h b/ntoskrnl/include/internal/ob.h
index 8c3b82faf8a..4aad5a5e253 100644
--- a/ntoskrnl/include/internal/ob.h
+++ b/ntoskrnl/include/internal/ob.h
@@ -58,6 +58,11 @@
//
#define ObpAccessProtectCloseBit 0x02000000L
+//
+// System Process Quota Block
+//
+#define OBP_SYSTEM_PROCESS_QUOTA ((PEPROCESS_QUOTA_BLOCK)(ULONG_PTR)1)
+
//
// Identifies a Kernel Handle
//
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=13cbc7fbf9197dd787259…
commit 13cbc7fbf9197dd787259d32c679049f243c13c8
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Thu Dec 30 20:57:14 2021 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:08 2022 +0100
[NTOS:MM] Add the pool quota prototypes and some definitions
Declare the MmRaisePoolQuota and MmReturnPoolQuota prototypes in the header and add some definitions related to pool quotas, namely MmTotalNonPagedPoolQuota and MmTotalPagedPoolQuota. These variables are used internally by the kernel as sort of "containers" (for the lack of a better term)
which uphold the amount of quotas that the Process Manager is requesting the Memory Manager to raise or return the pool quota limit. In addition to that, add some definitions needed for both of these functions.
The definitions, MI_CHARGE_PAGED_POOL_QUOTA and MI_CHARGE_NON_PAGED_POOL_QUOTA respectively, bear some interesting aspect. Seemingly the 0x80000 and 0x10000 values (that would denote to 524288 and 65536 specifically) are used as quota "limits" or in other words, thresholds that the kernel
uses. So for example if one would want to raise the quota limit charge, MmRaisePoolQuota will raise it so based on this formula -- NewMaxQuota = CurrentQuota + LIMIT_VALUE. LIMIT_VALUE can be either MI_CHARGE_PAGED_POOL_QUOTA or MI_CHARGE_NON_PAGED_POOL_QUOTA, depending a per quota pool basis.
What's more interesting is that these values are pervasive in Process Manager even. This is when quotas are to be returned back and trim the limit of the quota block if needed, the kernel would either take the amount provided by the caller of quotas to return or the threshold (paged or not paged)
if the amount to return exceeds the said threshold in question.
---
ntoskrnl/include/internal/mm.h | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/ntoskrnl/include/internal/mm.h b/ntoskrnl/include/internal/mm.h
index 654f23c6ee0..5c28d27806b 100644
--- a/ntoskrnl/include/internal/mm.h
+++ b/ntoskrnl/include/internal/mm.h
@@ -28,6 +28,9 @@ extern KMUTANT MmSystemLoadLock;
extern ULONG MmNumberOfPagingFiles;
+extern SIZE_T MmTotalNonPagedPoolQuota;
+extern SIZE_T MmTotalPagedPoolQuota;
+
extern PVOID MmUnloadedDrivers;
extern PVOID MmLastUnloadedDrivers;
extern PVOID MmTriageActionTaken;
@@ -53,6 +56,14 @@ struct _EPROCESS;
struct _MM_RMAP_ENTRY;
typedef ULONG_PTR SWAPENTRY;
+//
+// Pool Quota values
+//
+#define MI_QUOTA_NON_PAGED_NEEDED_PAGES 64
+#define MI_NON_PAGED_QUOTA_MIN_RESIDENT_PAGES 200
+#define MI_CHARGE_PAGED_POOL_QUOTA 0x80000
+#define MI_CHARGE_NON_PAGED_POOL_QUOTA 0x10000
+
//
// Special IRQL value (found in assertions)
//
@@ -646,12 +657,21 @@ MiFreePoolPages(
/* pool.c *******************************************************************/
+_Requires_lock_held_(PspQuotaLock)
BOOLEAN
NTAPI
-MiRaisePoolQuota(
- IN POOL_TYPE PoolType,
- IN ULONG CurrentMaxQuota,
- OUT PULONG NewMaxQuota
+MmRaisePoolQuota(
+ _In_ POOL_TYPE PoolType,
+ _In_ SIZE_T CurrentMaxQuota,
+ _Out_ PSIZE_T NewMaxQuota
+);
+
+_Requires_lock_held_(PspQuotaLock)
+VOID
+NTAPI
+MmReturnPoolQuota(
+ _In_ POOL_TYPE PoolType,
+ _In_ SIZE_T QuotaToReturn
);
/* mdl.c *********************************************************************/
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=abe89d7cdecd173628f6f…
commit abe89d7cdecd173628f6fa67819547887a085664
Author: George Bișoc <george.bisoc(a)reactos.org>
AuthorDate: Sat Jan 1 20:23:28 2022 +0100
Commit: George Bișoc <george.bisoc(a)reactos.org>
CommitDate: Tue Jan 11 10:11:08 2022 +0100
[NTOS:FSRTL] Assign the buffer length to ThisBufferLength field
This fixes an issue where ReactOS would assert on QuotaUsage == 0 as the process was still taking up quotas during a quota block de-reference with root cause of ThisBufferLength member field being 0 which made process quota charging/returning flow unbalanced.
In addition to that, on FsRtlCancelNotify routine API all we must ensure that if PsChargePoolQuota or ExAllocatePoolWithTag function fails we have to handle the raised exceptions accordingly and return the charged quota back (if we actually charged quotas that is). With said, wrap that part of code with SEH.
=== DOCUMENTATION REMARKS ===
The cause of the assert is due to the fact ThisBufferLength was being handled wrongly ever since, until this commit. When FsRtl of the Executive has to filter reported changes (with logic algorithm implemented in FsRtlNotifyFilterReportChange function), the said function will charge the quota of a given process
with an amount that is represented as the buffer length whose size is expressed in bytes. This length buffer is preserved in a local variable called NumberOfBytes, which is initialized from BufferLength member field of notification structure or from the length from stack parameters pointed from an IRP.
As it currently stands, the code is implemented in such a way that FsRtlNotifyFilterReportChange will charge quotas to a process but it doesn't assign the buffer length to ThisBufferLength. On the first glimpse ThisBufferLength and BufferLength are very similar members that serve exact same purpose but in reality there's a subtle distinction between the two.
BufferLength is a member whose length size is given by FSDs (filesystem drivers) during a notification dispatching. Whenever FsRtl receives the notification structure packed with data from the filesystem, the length pointed by BufferLength gets passed to ThisBufferLength and from now on the kernel has to use this member for the whole time of its task to accomplish
whatever request it's been given by the filesystem. In other words, BufferLength is strictly used only to pass length size data to the kernel by initializing ThisBufferLength based on that length and unequivocally the kernel uses this member field. What we're doing is that ThisBufferLength never receives the length from BufferLength therefore whenever FsRtl component
has to return quotas back it'll return an amount of 0 (which means no amount to return) and that's a bug in the kernel.
---
ntoskrnl/fsrtl/notify.c | 49 +++++++++++++++++++++++++++++++++++++++----------
1 file changed, 39 insertions(+), 10 deletions(-)
diff --git a/ntoskrnl/fsrtl/notify.c b/ntoskrnl/fsrtl/notify.c
index e7b73771232..f69475c9984 100644
--- a/ntoskrnl/fsrtl/notify.c
+++ b/ntoskrnl/fsrtl/notify.c
@@ -85,6 +85,7 @@ FsRtlCancelNotify(IN PDEVICE_OBJECT DeviceObject,
PIO_STACK_LOCATION Stack;
PNOTIFY_CHANGE NotifyChange;
PREAL_NOTIFY_SYNC RealNotifySync;
+ BOOLEAN PoolQuotaCharged;
PSECURITY_SUBJECT_CONTEXT _SEH2_VOLATILE SubjectContext = NULL;
/* Get the NOTIFY_CHANGE struct and reset it */
@@ -165,15 +166,38 @@ FsRtlCancelNotify(IN PDEVICE_OBJECT DeviceObject,
/* If we have a buffer length, but no buffer then allocate one */
if (Buffer == NULL)
{
- PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, BufferLength);
- Buffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, BufferLength, TAG_FS_NOTIFICATIONS);
- NotifyChange->AllocatedBuffer = Buffer;
- }
+ /* Assume we haven't charged quotas */
+ PoolQuotaCharged = FALSE;
- /* Copy data in that buffer */
- RtlCopyMemory(Buffer, NotifyChange->Buffer, NotifyChange->DataLength);
- NotifyChange->ThisBufferLength = BufferLength;
- NotifyChange->Buffer = Buffer;
+ _SEH2_TRY
+ {
+ /* Charge quotas */
+ PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, BufferLength);
+ PoolQuotaCharged = TRUE;
+
+ /* Allocate buffer */
+ Buffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, BufferLength, TAG_FS_NOTIFICATIONS);
+ NotifyChange->AllocatedBuffer = Buffer;
+
+ /* Copy data in that buffer */
+ RtlCopyMemory(Buffer, NotifyChange->Buffer, NotifyChange->DataLength);
+ NotifyChange->ThisBufferLength = BufferLength;
+ NotifyChange->Buffer = Buffer;
+ }
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ /* Something went wrong, have we charged quotas? */
+ if (PoolQuotaCharged)
+ {
+ /* We did, return quotas */
+ PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, BufferLength);
+ }
+
+ /* And notify immediately */
+ NotifyChange->Flags |= NOTIFY_IMMEDIATELY;
+ }
+ _SEH2_END;
+ }
}
/* If we have to notify immediately, ensure that any buffer is 0-ed out */
@@ -1322,10 +1346,15 @@ FsRtlNotifyFilterReportChange(IN PNOTIFY_SYNC NotifySync,
/* If we couldn't find one, then allocate one */
if (NotifyChange->Buffer == NULL)
{
+ /* Assign the length buffer */
+ NotifyChange->ThisBufferLength = NumberOfBytes;
+
+ /* Assume we have not charged quotas */
PoolQuotaCharged = FALSE;
_SEH2_TRY
{
- PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, NumberOfBytes);
+ /* And charge quotas */
+ PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, NotifyChange->ThisBufferLength);
PoolQuotaCharged = TRUE;
OutputBuffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
NumberOfBytes, TAG_FS_NOTIFICATIONS);
@@ -1337,7 +1366,7 @@ FsRtlNotifyFilterReportChange(IN PNOTIFY_SYNC NotifySync,
{
if (PoolQuotaCharged)
{
- PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NumberOfBytes);
+ PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NotifyChange->ThisBufferLength);
}
NotifyChange->Flags |= NOTIFY_IMMEDIATELY;
}