SKETCH OF HOW RESEARCH MIGHT CONTINUE AND RESULTS BE PRESENTED

EPROCESS_QUOTA_BLOCK

The EPROCESS_QUOTA_BLOCK is the primary structure in which the kernel keeps information about the use that a set of processes make of various resources that are subject to quotas.

The kernel keeps a default quota block 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.

Documentation Status

The EPROCESS_QUOTA_BLOCK is not documented. Its name has long been known from type information in symbol files because the EPROCESS structure has a member, named QuotaBlock, whose type is an EPROCESS_QUOTA_BLOCK pointer. Offsets, types and Microsoft’s names for members of the EPROCESS_QUOTA_BLOCK are known from type information for the structure itself in symbol files for the kernel starting with Windows 2000 SP3 but only before Windows Vista. 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.

Variability

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.

Layout

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 changing value PsQuotaTypes counts the current possibilities). Another two quota types were introduced for version 6.0: one for the working set; and another for the CPU rate, but this does not survive to the next version.

Offset (x86) Offset (x64) Definition Versions Remarks
0x00 0x00
EPROCESS_QUOTA_ENTRY QuotaEntry [PsQuotaTypes];
5.1 to 5.2  
EPROCESS_QUOTA_ENTRY QuotaEntry [PsQuotaTypes - 1];
6.0 only  
EPROCESS_QUOTA_ENTRY QuotaEntry [PsQuotaTypes];
6.1 and higher  
0x60 (6.0) 0xC0 (6.0) unknown structure 6.0 only  
0x0200 (6.1) 0x0200 (6.1)
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)
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  

For version 6.0 the layout above glosses over an irregularity in the QuotaEntry array. Some routines that work their way through the array plainly allow for 5 quota types but understand that there is no entry for the CPU rate. Where that entry would be, there is instead a larger structure that looks as if it originated as an EPROCESS_QUOTA_ENTRY in the array but got modified. Microsoft’s name for this structure is not known. Though EPROCESS_CPU_QUOTA_ENTRY would seem at least a reasonable guess, another might be that this structure is instead an early form of the PS_CPU_QUOTA_BLOCK (that version 6.1 keeps a pointer to).

Versions 5.1 and 5.2 allow for quota expansion by maintaining a global list of EPROCESS_QUOTA_BLOCK structures linked through their QuotaList members. Version 6.0 changed to keeping a separate list for each of the resource types that allow for expansion, namely the two pool types, and each list is then of EPROCESS_QUOTA_ENTRY structures for the applicable resource type.

The sequenced list in version 6.0 is of PSP_RATE_APC structures.