Geoff Chappell, Software Analyst
This function charges a process for its use of memory from either type of pool.
VOID PsChargeProcessPoolQuota ( EPROCESS *Process, POOL_TYPE PoolType, SIZE_T Amount);
The Process argument is the address of the process that is to be charged.
The PoolType is either NonPagedPool (0) or PagedPool (1), to denote the type of pool usage to charge the process for. Or so seems to be the intention nowadays and the assumption originally. See notes below.
The Amount argument is the amount, in bytes, to charge.
That this function returns at all means success. To fail, it raises an exception.
Nowadays, the explicitly raised exception code for failure, in contrast to faulting, is STATUS_QUOTA_EXCEEDED (0xC0000044). If given suitable input, versions 5.1 and 5.2 may explicitly raise STATUS_PAGEFILE_QUOTA_EXCEEDED (0xC000012C). Also depending on input, these and older versions may cause other exceptions as unhandled faults. See notes below.
The PsChargePoolQuota function is exported by name from the kernel in all versions, i.e., 3.10 and higher.
The first known documentation of PsChargePoolQuota is in the Installable File System (IFS) Kit for Windows 2000. Microsoft’s documentation does not date the function’s availability. Since at least the Windows Driver Kit (WDK) for Windows Vista, the function’s declaration in NTIFS.H is in a conditional compilation block that restricts the function’s availability to Windows 2000 and higher.
Even today, 17th October 2017, Microsoft’s documentation states plainly that the PoolType can be not just NonPagedPool (0) or PagedPool (1) but also NonPagedPoolCacheAligned (4) or PagedPoolCacheAligned (5). It compounds this misinformation by specifying two more, NonPagedPoolMustSucceed (2) and NonPagedPoolCacheAlignedMustS (6), as obsolete and thus as having once worked even if they “should no longer be used”. In version 6.0 and higher, all these higher values—and, indeed, all values other than PagedPool—select the non-paged pool. Though this may charge the amount against the wrong quota, i.e., non-paged when paged was intended, at least it’s not immediately harmful. Before version 6.0, calling the function with anything other than NonPagedPool or PagedPool can cause undefined behaviour.
It’s entirely possible that these quirks never have been seen in real-world practice, simply because nobody ever does take the documentation at its word and call the function with pool types other than NonPagedPool and PagedPool. Some of the undefined behaviour that can result from “bad” input before version 6.0 would anyway have been hidden by the arguably poor design of raising an exception to show failure. If the undefined behaviour is an access violation while the function executes, in contrast to corruption that isn’t noticed until possibly much later, then the fault will show as an exception and plausibly not get distinguished from failure.
In version 5.1 and higher, the PsChargePoolQuota function is superseded by PsChargeProcessPoolQuota or would surely be said to be except that the latter is not documented. The old function calls the new with the same arguments but with the one extra step that a negative NTSTATUS from the new function is not returned as an error code but is instead raised as an exception.
Conspicuously, Microsoft’s AFD.SYS and MSFS.SYS drivers call the documented PsChargePoolQuota in version 5.0 but the undocumented PsChargeProcessPoolQuota or even PsChargeProcessPagedPoolQuota in version 5.1 and ever since. Indeed, no later Microsoft driver that charges a pool quota, as does HTTP.SYS for instance, uses the documented function. If avoiding the exception on failure is useful for Microsoft’s driver programmers, why is it kept from other programmers?
For versions 5.1 and higher, description of the old PsChargePoolQuota just by reference to the new PsChargeProcessPoolQuota might pass as complete. But it’s as well to collect here, with the old but documented function, the different interpretations of the PoolType argument through the whole history.
Versions 3.10 to 5.0 simply assume that PoolType is NonPagedPool or PagedPool. The function operates on EPROCESS and EPROCESS_QUOTA_BLOCK members such as QuotaPoolUsage which are arrays with only two elements, one for each pool type. When called with higher values for PoolType, the function will read or write beyond one or another such array. This access may be at an invalid address and cause an exception immediately, but it may instead corrupt memory, with effects that may not be apparent until long after the functions returns.
Versions 5.1 and 5.2 interpret PoolType as if from the PS_QUOTA_TYPE enumeration and simply assume that it is one of PsNonPagedPool (0), PsPagedPool (1) or PsPageFile (2). In these versions, EPROCESS members such as QuotaPoolUsage are extended to three elements, one for each quota type, and the EPROCESS_QUOTA_BLOCK has a QuotaEntry member that is an array of three EPROCESS_QUOTA_ENTRY structures. When called with a higher PoolType, the function will read or write beyond one or another of these arrays, again with generally unpredictable consquences.
Defence against higher values of PoolType eventually came with version 6.0. As noted above, higher values are not rejected but everything other than PagedPool is treated as NonPagedPool. How Microsoft eventually limited the PoolType without making some corresponding change in the documentation of the function may forever be a mystery.