The User-Mode REGHANDLE

A substantial development of Event Tracing for Windows (ETW) in Windows Vista is that an event provider registers its intention to write events and gets back a REGHANDLE to use when writing them. Kernel-mode providers register by calling the kernel export EtwRegister or EtwRegisterClassicProvider and get a kernel-mode REGHANDLE. User-mode providers register by calling the ADVAPI32 export EventRegister, forwarded to NTDLL as EtwEventRegister, and get a user-mode REGHANDLE. This note is concerned only with this user-mode registration handle.

Documentation Status

The REGHANDLE is documented as are most of the functions that either produce it or accept it as input. But it is opaque. Formally, it is just an unsigned 64-bit integer. Well-behaved programs do not interpret it.

Interpretation

Interpretation can, however, be useful to programmers when debugging and at any time to the reverse engineer. It is anyway instructive to know the type’s interpretation even without ever relying on it.

The point to the REGHANDLE is of course that it points in some abstracted way to some abstracted structure that represents the registration. In a naive implementation entirely in kernel mode, this representative structure might exist only in kernel mode and be formally an Object Manager object so that the user-mode REGHANDLE could be (or contain) an Object Manager HANDLE. Instead, each user-mode registration of an event provider has substantial support in user mode and so the representative structure is a creation of NTDLL (and in turn contains a HANDLE to access the underlying kernel-mode support). Microsoft’s name for this user-mode structure is known to be ETW_REGISTRATION_ENTRY. It has evident continuity through all versions but in no version is the user-mode REGHANDLE simply the address of the registration entry.

Original

Before Windows 8, the user-mode REGHANDLE doesn’t even contain the address of the registration entry. Instead, NTDLL keeps an array of pointers to the registration entries and an allocated REGHANDLE contains, among other things, a 0-based index into this array. Sometimes here, and in pages that are linked to from here, this array is referred to as the registration list because its name as an internal variable is known from public symbols for NTDLL to be EtwpRegList. The array’s capacity is hard-coded as 0x0400, which is thus a non-inclusive upper bound on the index in a valid REGHANDLE and is also the (documented) maximum number of user-mode registrations that any process can have at any one time.

The 8-byte registration handle is known to be interpreted internally as a structure:

Offset Definition Versions
0x00
struct {
    ULONG InUse : 16;
    unknown 16-bit sequence number
} RegSignature;
6.0 to 6.1
0x04 unknown 32-bit index into registration list 6.0 to 6.1

What little is known of this structural interpretation is from the WMITRACE.DLL debugger extension. For its obsolete !regtable command, it knows that the structure exists in the ETW_REGISTRATION_ENTRY as the RegistrationHandle and that InUse must be 1. Also known is that InUse is nested within something named RegSignature, which is merely thought above to be just the first dword. It would not surprise if the second dword is named RegIndex. (Support that InUse is a ULONG bit field is thin. WMITRACE reads it into four bytes but might read into just two if InUse were known to be defined more simply as a USHORT.)

The point to InUse is that although a registration entry is needed for each user-mode registration of an event provider, it is not necessarily created afresh. Unregistering merely takes the registration entry out of use, leaving it pointed to from its slot in the array and available for reuse. When a registration entry is created for a registration, its RegistrationHandle has 1 for InUse and 1 for its sequence number. While a registration entry is out of use, its RegistrationHandle has zero for InUse. When reused, its InUse is restored to 1 and its sequence number is incremented. See that this sequence number is independent for each index: it is a sequence number of the corresponding index number’s reuse.

The plain intention is that a valid REGHANDLE selects an in-use ETW_REGISTRATION_ENTRY whose own RegistrationHandle matches the REGHANDLE. The actual implementation is not quite so strict. A REGHANDLE is invalid unless all the following are true:

Note that although the RegSignature portion of the REGHANDLE, with its boolean InUse and its sequence number, may be adequate defence against a stale REGHANDLE, it still leaves the REGHANDLE as essentially an index, with conspicuously little entropy: the low word can only be 1, the second word is almost always 1; and the high dword is often small and never exceeds 1023.

Modern

The REGHANDLE has much more entropy in Windows 8 because it is essentially a pointer. Though the number of user-mode registrations and thus of ETW_REGISTRATION_ENTRY structures that a process can have at any one time is still limited (but now to the undocumented 2048), there is no index to use in the REGHANDLE. There is no longer an array to index. Instead, the registration entries are nodes in a red-black tree (sorted by GUID). A valid REGHANDLE is mostly the address of the corresponding registration entry.

But the REGHANDLE is not only a pointer to a registration entry. What versions 6.2 and higher have instead of the index is a sequence number, now truly counting the registration’s place in the order of all the process’s registrations.

Mask (x86) Mask (x64) Interpretation Versions
0x00000000`FFFFFFFF 0x0000FFFF`FFFFFFFF address of registration entry 6.2 and higher
0x0000FFFF`00000000 0xFFFF0000`00000000 sequence number 6.2 and higher

A REGHANDLE is valid only if all the following are true:

To be clear: a numerically even address from the REGHANDLE, including NULL, is treated as safe to inspect. The point to the sequence number, as far as concerns its use in the REGHANDLE, is a very particular defence. As with earlier versions, registration entries are not freed at un-registration. They are cached and reused. The defence is only against a stale REGHANDLE which retains the address of a registration entry that is now not in use or has been reused. The defence is not against a corrupt, random or mischievously manufacturerd REGHANDLE. Feed such a thing to an API function for ETW and the likely outcome is that you crash the process.