Geoff Chappell - Software Analyst
SKETCH OF HOW RESEARCH MIGHT CONTINUE AND RESULTS BE PRESENTED
This function registers one type or another of event provider, including types that are not supported through higher-level API functions.
ULONG EtwNotificationRegister ( LPCGUID Guid, ULONG Type, PETW_NOTIFICATION_CALLBACK Callback, PVOID Context, REGHANDLE *RegHandle);
The required Guid argument is the address of a GUID that represents the provider.
The Type argument specifies the type of notification. Though the argument is formally a ULONG, its supported values are those of the ETW_NOTIFICATION_TYPE enumeration.
The Callback argument is the address of a function that is to be called back in circumstances that are not presently understood. This argument can be NULL to mean that the caller does not want to be called back.
The Context argument is an arbitrary caller-defined value that is to be passed back to the caller as one of the arguments of the callback function. This argument is meaningful only if a callback function is supplied.
The required RegHandle argument is the address of a 64-bit variable that is to receive a registration handle to the registered provider.
The function returns zero for success, else a Win32 error code (which the function also sets as the thread’s last error).
The callback function has the prototype
typedef ULONG (*PETW_NOTIFICATION_CALLBACK) ( ETW_NOTIFICATION_HEADER *NotificationHeader, PVOID Context);
The EtwNotificationRegister function is exported by name from NTDLL in version 6.0 and higher.
As with many NTDLL exports, Microsoft does not document EtwNotificationRegister. Unlike many, no higher-level function corresponds roughly to it.
Microsoft has, however, published a C-language declaration in NTETW.H from the Enterprise edition of the Windows Driver Kit (WDK) for Windows 10 version 1511. This article reproduces Microsoft’s names for the function’s arguments. Some closely related names are known from public symbol files. For instance, the name ETW_NOTIFICATION_HEADER is known from as far back as Windows Vista because two modules that call this function supply callback routines that are written in C++ and their symbol files show the type in these routines’ decorated names.
Without a Guid for input and a RegHandle for output, the function can do nothing. If either is NULL, the function returns ERROR_INVALID_PARAMETER.
The function saves its inputs into an ETW_REGISTRATION_ENTRY which becomes the event provider’s user-mode representation. If the function cannot create a registration entry, it returns ERROR_OUTOFMEMORY. The failure can indeed be caused by insufficient memory but a cause that’s less obviously indicated by this error code is that the creation exceeds a hard-coded limit on how many registrations a process can have at any one time. This limit is 0x0400 before version 6.2 and 0x0800 since. Note that Microsoft’s documentation today, 24th December, 2018, repeats in multiple places, not just for EventRegister and RegisterTraceGuids but in general guidance such as Writing Manifest-based Events, that a “process can register up to 1,024 provider GUIDs”. (Put aside that Microsoft doubled the limit without updating the documentation. Why does Microsoft double a limit that is already orders of magnitude beyond advice that “you should limit the number of providers that your process registers to one or two”? Apparent inconsistencies in documentation have as their inevitable consequence a low take-up of new ETW functionality as programmers perceive not just a steep learning curve but a waste of their time in trial and error just to discover what’s true.)
Unless the Type is EtwNotificationTypeInProcSession (in version 6.3 and higher), registration also creates a kernel-mode representation for the event provider and even an Object Manager handle to an EtwRegistration object.
The essence of the function’s communication with the kernel to register a provider is NtTraceControl with 0x0F as its FunctionCode. (Few of Microsoft’s names for the function codes are known.)
In version 6.3 and higher, this communication with the kernel requires non-trivial preparation on the process’s first attempt to register any provider with the kernel. Significant elements include: creating an event for the kernel to signal when a notification is ready for retrieval; getting the thread pool to wait repeatedly for this signal, retrieve notifications and distribute them to the event providers; and telling the kernel of the event. If this preparation happens to be needed but fails, then so does the function, without being able to register the provider with the kernel.
On successful registration both within NTDLL and, usually, with the kerrnel, the function creates a REGHANDLE to return to the caller via the RegHandle argument. The REGHANDLE indirectly selects the user-mode ETW_REGISTRATION_ENTRY which in turn holds any HANDLE to the kernel’s EtwRegistration object. The REGHANDLE, which the caller should treat as opaque, then represents the whole registration for use with other functions, e.g., with EtwEventWrite to have the event provider write an event, until its use with EtwNotificationUnregister.