Geoff Chappell, Software Analyst
The EPROCESS_QUOTA_BLOCK is the primary structure in which the kernel keeps information about the use that a set of processes makes of various resources that are subject to quotas.
The kernel keeps a default quota block as an internal variable. Version 6.0 adds a system quota block, again as an internal variable. A quota block is created for a process by the NtSetInformationProcess and ZwSetInformationProcess functions when first given the information class ProcessQuotaLimits (0x01) with input that sets no size for the process’s working set. A newly created process inherits its parent’s quota block.
The applicable quota block for a process’s use of resources is pointed to by the QuotaBlock member of that process’s EPROCESS. The process’s own current and peak usage of each resource is tracked in the EPROCESS, e.g., in ProcessQuotaUsage and ProcessQuotaPeak. What the quota block tracks is the current and peak usage of each resource by the totality of all processes that share the quota block. Crucially, the quota block also has the limit on total usage by these processes.
Though the EPROCESS_QUOTA_BLOCK is not documented, its name is in public symbol files starting from Windows 2000 SP3 because, as noted already, the EPROCESS structure contains a pointer to an EPROCESS_QUOTA_BLOCK. Before Windows Vista, these same public symbol files also show offsets, types and Microsoft’s names for members of the EPROCESS_QUOTA_BLOCK. The names and offsets were also disclosed publicly in the output of the !dso command as implemented by the USEREXTS debugger extension that Microsoft supplied with the Device Driver Kit (DDK) for Windows NT 4.0 and Windows 2000, and in the output of the !strct command as implemented by the KDEX2X86 debugger extension for Windows 2000.
Being internal to the kernel, the EPROCESS_QUOTA_BLOCK is subject to variation between versions. It changed significantly for Windows XP, but it then has a stability that is not obvious from the following table of changing sizes:
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 to 5.0 | 0x2C | |
5.1 to 5.2 | 0x40 | 0x78 |
6.0 | 0xA8 | 0x0120 |
6.1 and higher | 0x0240 | 0x0240 |
The expansion for version 6.0 is almost entirely due to the management of two more types of resource. The very large expansion for version 6.1 is mostly explained by cache alignment.
What’s known of the EPROCESS_QUOTA_BLOCK before version 4.0 is entirely from matching the kernel’s treatment of the structure in these versions with the later versions for which the members are known from debugger extensions. The original EPROCESS_QUOTA_BLOCK apparently served well enough without change until version 5.0, but it was then reworked so much that only one member survives.
Offset (x86) | Definition | Versions |
---|---|---|
0x00 (3.10 to 5.0) |
KSPIN_LOCK QuotaLock; |
3.10 to 5.0 |
0x04 (3.10 to 5.0) |
ULONG ReferenceCount; |
3.10 to 5.0 |
0x08 (3.10 to 5.0) |
ULONG QuotaPeakPoolUsage [2]; |
3.10 to 5.0 |
0x10 (3.10 to 5.0) |
ULONG QuotaPoolUsage [2]; |
3.10 to 5.0 |
0x18 (3.10 to 5.0) |
ULONG QuotaPoolLimit [2]; |
3.10 to 5.0 |
0x20 (3.10 to 5.0) |
ULONG PeakPagefileUsage; |
3.10 to 5.0 |
0x24 (3.10 to 5.0) |
ULONG PagefileUsage; |
3.10 to 5.0 |
0x28 (3.10 to 5.0) |
ULONG PagefileLimit; |
3.10 to 5.0 |
Note the repeating arrangement of peak usage, (current) usage and limit first in pairs for non-paged and paged pool and then separately for the pagefile. A large part of the reworking for Windows XP was to put the two pool types and the pagefile on equal footing as quota types each of whose peak usage, current usage and limit are gathered into an EPROCESS_QUOTA_ENTRY. The EPROCESS_QUOTA_BLOCK then gets one EPROCESS_QUOTA_ENTRY for each quota type, organised as an array indexed by the PS_QUOTA_TYPE enumeration (whose last value, PsQuotaTypes, counts the current possibilities).
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
EPROCESS_QUOTA_ENTRY QuotaEntry [PsQuotaTypes]; |
5.1 to 5.2 | |
PSP_QUOTA_ENTRY QuotaEntry [4]; |
6.0 to 6.2 | |||
PSP_QUOTA_ENTRY QuotaEntry [PsQuotaTypes]; |
6.3 and higher | |||
0x60 (6.0); 0x0200 (6.1) |
0xC0 (6.0); 0x0200 (6.1) |
<unknown-type> RateEntry [1]; |
6.0 only | |
PS_CPU_QUOTA_BLOCK *CpuQuotaBlock; |
6.1 only | |||
0x30 (5.1 to 5.2); |
0x60 (5.2) |
LIST_ENTRY QuotaList; |
5.1 to 5.2 | next at 0x98 and 0x0100 |
0x38 (5.1 to 5.2); 0x90 (6.0); 0x0204 (6.1); 0x0200 |
0x70 (5.2); 0xF8 (6.0); 0x0208 (6.1); 0x0200 |
ULONG ReferenceCount; |
5.1 and higher | |
0x3C (5.1 to 5.2); 0x94 (6.0); 0x0208 (6.1); 0x0204 |
0x74 (5.2); 0xFC (6.0); 0x020C (6.1); 0x0204 |
ULONG ProcessCount; |
5.1 and higher | |
0x98 (6.0); 0x020C (6.1); 0x0208 |
0x0100 (6.0); 0x0210 (6.1); 0x0208 |
LIST_ENTRY QuotaList; |
6.0 and higher | previously at 0x30 and 0x60 |
0xA0 (6.0) | 0x0110 (6.0) | unknown SLIST_HEAD | 6.0 only |
Most of what is known of the structure in version 6.0 and higher is again from matching its treatment by the kernel, though now with the earlier versions for which type information is available in the public symbol files. Some developments are new enough to have no correspondence. A little insight into Microsoft’s names in version 6.0 and higher can be gleaned from the KDEXTS debugger extension’s !quota command. Among the debugger support in the Windows Driver Kit (WDK), this appears first in the WDK for Windows 7. It has two small points of distinction. One is that is does no version checking. The other is that even its -? switch (for help) requires type information that is not present in the public symbol files. The types that it looks for are the slimmest of pickings for Microsoft’s names but seem to be the most that is publicly available.
The !quota command tells of a break from continuity: the EPROCESS_QUOTA_ENTRY changes name, if not in version 6.0, then certainly by version 6.1. The command assumes that the EPROCESS_QUOTA_BLOCK starts with an array of four PSP_QUOTA_ENTRY structures. That the array continues to be named QuotaEntry is just an assumption.
The layout above glosses over an irregularity about quota types. Version 6.0 added two: one for the working set; and another for the CPU rate; bringing PsQuotaTypes to 5. Versions 6.0 to 6.2 plainly allow for 5 quota types, notably while reading quota limits from registry values. Some routines in these versions even allow for 5 quota types while working their way through the QuotaEntry array but understand that there are only 4 entries. In version 6.0, where there might be an entry for the CPU rate as the fifth quota type, there is instead a different structure. Microsoft’s name for this structure is not known. Microsoft’s name for the member is a confident inference from the !quota command’s seeking of a field named RateEntry[0].RateLimit.RateData. This fits with inspection of the kernel: the unknown structure has a member (at offset 0x18 in both x86 and x64 builds) that is consistent with being a RATE_QUOTA_LIMIT, which is defined in NTDDK.H as having a member named RateData.
The sequenced list at the end of the quota block in version 6.0 is a cache of PSP_RATE_APC structures.