reactos/ntoskrnl/ps/quota.c
2017-12-20 05:23:06 +01:00

457 lines
14 KiB
C

/*
* 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
*/
/* INCLUDES **************************************************************/
#include <ntoskrnl.h>
#include <ntintsafe.h>
#define NDEBUG
#include <debug.h>
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 | \
QUOTA_LIMITS_HARDWS_MAX_DISABLE)
/* PRIVATE FUNCTIONS *******************************************************/
/*
* Private helper to charge the specified process quota.
* ReturnsSTATUS_QUOTA_EXCEEDED on quota limit check failure.
* Updates QuotaPeak as needed for specified PoolIndex.
* TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
* to replace UCHAR for 'PoolIndex'.
* Notes: Conceptually translation unit local/private.
*/
NTSTATUS
NTAPI
PspChargeProcessQuotaSpecifiedPool(IN PEPROCESS Process,
IN UCHAR PoolIndex,
IN SIZE_T Amount)
{
ASSERT(Process);
ASSERT(Process != PsInitialSystemProcess);
ASSERT(PoolIndex <= 2);
ASSERT(Process->QuotaBlock);
/* Note: Race warning. TODO: Needs to add/use lock for this */
if (Process->QuotaUsage[PoolIndex] + Amount >
Process->QuotaBlock->QuotaEntry[PoolIndex].Limit)
{
DPRINT1("Quota exceeded, but ROS will let it slide...\n");
return STATUS_SUCCESS;
//return STATUS_QUOTA_EXCEEDED; /* caller raises the exception */
}
InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex], Amount);
/* Note: Race warning. TODO: Needs to add/use lock for this */
if (Process->QuotaPeak[PoolIndex] < Process->QuotaUsage[PoolIndex])
{
Process->QuotaPeak[PoolIndex] = Process->QuotaUsage[PoolIndex];
}
return STATUS_SUCCESS;
}
/*
* Private helper to remove quota charge from the specified process quota.
* TODO: Research and possibly add (the undocumented) enum type PS_QUOTA_TYPE
* to replace UCHAR for 'PoolIndex'.
* Notes: Conceptually translation unit local/private.
*/
VOID
NTAPI
PspReturnProcessQuotaSpecifiedPool(IN PEPROCESS Process,
IN UCHAR PoolIndex,
IN SIZE_T Amount)
{
ASSERT(Process);
ASSERT(Process != PsInitialSystemProcess);
ASSERT(PoolIndex <= 2);
ASSERT(!(Amount & 0x80000000)); /* we need to be able to negate it */
if (Process->QuotaUsage[PoolIndex] < Amount)
{
DPRINT1("WARNING: Process->QuotaUsage sanity check failed.\n");
}
else
{
InterlockedExchangeAdd((LONG*)&Process->QuotaUsage[PoolIndex],
-(LONG)Amount);
}
}
/* FUNCTIONS ***************************************************************/
VOID
NTAPI
INIT_FUNCTION
PsInitializeQuotaSystem(VOID)
{
RtlZeroMemory(&PspDefaultQuotaBlock, sizeof(PspDefaultQuotaBlock));
PspDefaultQuotaBlock.QuotaEntry[PagedPool].Limit = (SIZE_T)-1;
PspDefaultQuotaBlock.QuotaEntry[NonPagedPool].Limit = (SIZE_T)-1;
PspDefaultQuotaBlock.QuotaEntry[2].Limit = (SIZE_T)-1; /* Page file */
PsGetCurrentProcess()->QuotaBlock = &PspDefaultQuotaBlock;
}
VOID
NTAPI
PspInheritQuota(PEPROCESS Process, PEPROCESS ParentProcess)
{
if (ParentProcess != NULL)
{
PEPROCESS_QUOTA_BLOCK QuotaBlock = ParentProcess->QuotaBlock;
ASSERT(QuotaBlock != NULL);
(void)InterlockedIncrementUL(&QuotaBlock->ReferenceCount);
Process->QuotaBlock = QuotaBlock;
}
else
Process->QuotaBlock = &PspDefaultQuotaBlock;
}
VOID
NTAPI
PspInsertQuotaBlock(
PEPROCESS_QUOTA_BLOCK QuotaBlock)
{
KIRQL OldIrql;
KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
InsertTailList(&PspQuotaBlockList, &QuotaBlock->QuotaList);
KeReleaseSpinLock(&PspQuotaLock, OldIrql);
}
VOID
NTAPI
PspDestroyQuotaBlock(PEPROCESS Process)
{
PEPROCESS_QUOTA_BLOCK QuotaBlock = Process->QuotaBlock;
KIRQL OldIrql;
if (QuotaBlock != &PspDefaultQuotaBlock &&
InterlockedDecrementUL(&QuotaBlock->ReferenceCount) == 0)
{
KeAcquireSpinLock(&PspQuotaLock, &OldIrql);
RemoveEntryList(&QuotaBlock->QuotaList);
KeReleaseSpinLock(&PspQuotaLock, OldIrql);
ExFreePool(QuotaBlock);
}
}
/*
* @implemented
*/
NTSTATUS
NTAPI
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, 2, Amount);
}
/*
* @implemented
*/
VOID
NTAPI
PsChargePoolQuota(IN PEPROCESS Process,
IN POOL_TYPE PoolType,
IN SIZE_T Amount)
{
NTSTATUS Status;
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
/* Don't do anything for the system process */
if (Process == PsInitialSystemProcess) return;
/* Charge the usage */
Status = PsChargeProcessPoolQuota(Process, PoolType, Amount);
if (!NT_SUCCESS(Status)) ExRaiseStatus(Status);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
PsChargeProcessNonPagedPoolQuota(IN PEPROCESS Process,
IN SIZE_T Amount)
{
/* Call the general function */
return PsChargeProcessPoolQuota(Process, NonPagedPool, Amount);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
PsChargeProcessPagedPoolQuota(IN PEPROCESS Process,
IN SIZE_T Amount)
{
/* Call the general function */
return PsChargeProcessPoolQuota(Process, PagedPool, Amount);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
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,
(PoolType & PAGED_POOL_MASK),
Amount);
}
/*
* @implemented
*/
VOID
NTAPI
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,
(PoolType & PAGED_POOL_MASK),
Amount);
}
/*
* @implemented
*/
VOID
NTAPI
PsReturnProcessNonPagedPoolQuota(IN PEPROCESS Process,
IN SIZE_T Amount)
{
/* Don't do anything for the system process */
if (Process == PsInitialSystemProcess) return;
PsReturnPoolQuota(Process, NonPagedPool, Amount);
}
/*
* @implemented
*/
VOID
NTAPI
PsReturnProcessPagedPoolQuota(IN PEPROCESS Process,
IN SIZE_T Amount)
{
/* Don't do anything for the system process */
if (Process == PsInitialSystemProcess) return;
PsReturnPoolQuota(Process, PagedPool, Amount);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
PsReturnProcessPageFileQuota(IN PEPROCESS Process,
IN SIZE_T Amount)
{
/* Don't do anything for the system process */
if (Process == PsInitialSystemProcess) return STATUS_SUCCESS;
PspReturnProcessQuotaSpecifiedPool(Process, 2, Amount);
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
PspSetQuotaLimits(
_In_ PEPROCESS Process,
_In_ ULONG Unused,
_In_ PVOID QuotaLimits,
_In_ ULONG QuotaLimitsLength,
_In_ KPROCESSOR_MODE PreviousMode)
{
QUOTA_LIMITS_EX CapturedQuotaLimits;
PEPROCESS_QUOTA_BLOCK QuotaBlock, OldQuotaBlock;
BOOLEAN IncreaseOkay;
KAPC_STATE SavedApcState;
NTSTATUS Status;
UNREFERENCED_PARAMETER(Unused);
_SEH2_TRY
{
ProbeForRead(QuotaLimits, QuotaLimitsLength, sizeof(ULONG));
/* Check if we have the basic or extended structure */
if (QuotaLimitsLength == sizeof(QUOTA_LIMITS))
{
/* Copy the basic structure, zero init the remaining fields */
RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS));
CapturedQuotaLimits.WorkingSetLimit = 0;
CapturedQuotaLimits.Reserved2 = 0;
CapturedQuotaLimits.Reserved3 = 0;
CapturedQuotaLimits.Reserved4 = 0;
CapturedQuotaLimits.CpuRateLimit.RateData = 0;
CapturedQuotaLimits.Flags = 0;
}
else if (QuotaLimitsLength == sizeof(QUOTA_LIMITS_EX))
{
/* Copy the full structure */
RtlCopyMemory(&CapturedQuotaLimits, QuotaLimits, sizeof(QUOTA_LIMITS_EX));
/* Verify that the caller passed valid flags */
if ((CapturedQuotaLimits.Flags & ~VALID_QUOTA_FLAGS) ||
((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_ENABLE) &&
(CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MIN_DISABLE)) ||
((CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_ENABLE) &&
(CapturedQuotaLimits.Flags & QUOTA_LIMITS_HARDWS_MAX_DISABLE)))
{
DPRINT1("Invalid quota flags: 0x%lx\n", CapturedQuotaLimits.Flags);
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
}
/* Verify that the caller didn't pass reserved values */
if ((CapturedQuotaLimits.WorkingSetLimit != 0) ||
(CapturedQuotaLimits.Reserved2 != 0) ||
(CapturedQuotaLimits.Reserved3 != 0) ||
(CapturedQuotaLimits.Reserved4 != 0) ||
(CapturedQuotaLimits.CpuRateLimit.RateData != 0))
{
DPRINT1("Invalid value: (%lx,%lx,%lx,%lx,%lx)\n",
CapturedQuotaLimits.WorkingSetLimit,
CapturedQuotaLimits.Reserved2,
CapturedQuotaLimits.Reserved3,
CapturedQuotaLimits.Reserved4,
CapturedQuotaLimits.CpuRateLimit.RateData);
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
}
}
else
{
DPRINT1("Invalid quota size: 0x%lx\n", QuotaLimitsLength);
_SEH2_YIELD(return STATUS_INFO_LENGTH_MISMATCH);
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
DPRINT1("Exception while copying data\n");
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
/* Check the caller changes the working set size limits */
if ((CapturedQuotaLimits.MinimumWorkingSetSize != 0) &&
(CapturedQuotaLimits.MaximumWorkingSetSize != 0))
{
/* Check for special case: trimming the WS */
if ((CapturedQuotaLimits.MinimumWorkingSetSize == SIZE_T_MAX) &&
(CapturedQuotaLimits.MaximumWorkingSetSize == SIZE_T_MAX))
{
/* No increase allowed */
IncreaseOkay = FALSE;
}
else
{
/* Check if the caller has the required privilege */
IncreaseOkay = SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege,
PreviousMode);
}
/* Attach to the target process and disable APCs */
KeStackAttachProcess(&Process->Pcb, &SavedApcState);
KeEnterGuardedRegion();
/* Call Mm to adjust the process' working set size */
Status = MmAdjustWorkingSetSize(CapturedQuotaLimits.MinimumWorkingSetSize,
CapturedQuotaLimits.MaximumWorkingSetSize,
0,
IncreaseOkay);
/* Bring back APCs and detach from the process */
KeLeaveGuardedRegion();
KeUnstackDetachProcess(&SavedApcState);
}
else if (Process->QuotaBlock == &PspDefaultQuotaBlock)
{
/* Check if the caller has the required privilege */
if (!SeSinglePrivilegeCheck(SeIncreaseQuotaPrivilege, PreviousMode))
{
return STATUS_PRIVILEGE_NOT_HELD;
}
/* Allocate a new quota block */
QuotaBlock = ExAllocatePoolWithTag(NonPagedPool,
sizeof(EPROCESS_QUOTA_BLOCK),
TAG_QUOTA_BLOCK);
if (QuotaBlock == NULL)
{
ObDereferenceObject(Process);
return STATUS_NO_MEMORY;
}
/* Initialize the quota block */
QuotaBlock->ReferenceCount = 1;
QuotaBlock->ProcessCount = 1;
QuotaBlock->QuotaEntry[0].Peak = Process->QuotaPeak[0];
QuotaBlock->QuotaEntry[1].Peak = Process->QuotaPeak[1];
QuotaBlock->QuotaEntry[2].Peak = Process->QuotaPeak[2];
QuotaBlock->QuotaEntry[0].Limit = PspDefaultQuotaBlock.QuotaEntry[0].Limit;
QuotaBlock->QuotaEntry[1].Limit = PspDefaultQuotaBlock.QuotaEntry[1].Limit;
QuotaBlock->QuotaEntry[2].Limit = PspDefaultQuotaBlock.QuotaEntry[2].Limit;
/* Try to exchange the quota block, if that failed, just drop it */
OldQuotaBlock = InterlockedCompareExchangePointer((PVOID*)&Process->QuotaBlock,
QuotaBlock,
&PspDefaultQuotaBlock);
if (OldQuotaBlock == &PspDefaultQuotaBlock)
{
/* Success, insert the new quota block */
PspInsertQuotaBlock(QuotaBlock);
}
else
{
/* Failed, free the quota block and ignore it */
ExFreePoolWithTag(QuotaBlock, TAG_QUOTA_BLOCK);
}
Status = STATUS_SUCCESS;
}
else
{
Status = STATUS_SUCCESS;
}
return Status;
}
/* EOF */