PsChargePoolQuota

Declaration

Parameters

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.

Return Value

That this function returns at all means success. To fail, it raises an exception.

Nowadays, the explicitly expected 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 implicitly cause other exceptions
as unhandled faults. See notes below.

Availability

The PsChargePoolQuota function is exported by name
from the kernel in version 3.10 and higher.

Documentation Status

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.

Documentation Errors

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). In version 6.0 and higher,
these last two—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 either of these last two—or, indeed, 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 the function. If the undefined behaviour is
specifically an access violation by the function (rather than corruption that
isn’t noticed until possibly much later), then the fault will show as an
exception and plausibly not get
distinguished from failure.

Behaviour

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.

Pool Type

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.

This page was created on 17th
October 2017 and was last modified
on 13th November 2017.