Geoff Chappell, Software Analyst
When given 0x1E as its FunctionCode argument, the NtTraceControl function in version 10.0 and higher set traits for a provider. Microsoft’s name for this function code is not known. This note deals only with the function’s behaviour that is specific to this function code. The function’s general behaviour is here taken as assumed knowledge.
Well-behaved user-mode software does not call NtTraceControl. The documented user-mode API for reaching this functionality is EventSetInformation, which is exported by name from ADVAPI32.DLL in version 6.2 and higher, though only ever as a forward to the undocumened NTDLL function EtwEventSetInformation. These higher-level functions vary their behaviour according to an InformationClass argument. The case that sets provider traits is EventProviderSetTraits (2). Before version 10.0, this case is not supported.
Kernel-mode software does not call NtTraceControl to set provider traits. The documented API is more efficient (and behaves a little differently). It is EtwSetInformation, which is exported by name from the kernel in version 10.0 and higher. Microsoft declares it in WDM.H but does not document it. The kernel-mode function takes the same InformationClass argument. For EventProviderSetTraits, it cuts through to a similar internal routine as does NtTraceControl from user mode. Kernel-mode access through NtTraceControl is not just unwanted but is even unexpected: the handle that is expected in the input (see below) has its access checked as if for a user-mode caller and the traits information that is pointed to from this input is required to be in user-mode address space.
If the input buffer does not provide exactly 0x18 bytes or the output buffer does not allow for between 0x78 and 0x00010000 bytes inclusive, the function returns STATUS_INVALID_PARAMETER.
Though Microsoft surely has a structure for the expected input, its name is not known. It has the same size in both 32-bit and 64-bit Windows because its handle and pointer are padded to 64 bits:
|0x00||handle to event registration object||10.0 and higher|
|0x08||address of traits information||10.0 and higher|
|0x10||16-bit size of traits information||10.0 and higher|
Traits information is required. If the address is NULL or the size is zero, the function returns STATUS_INVALID_PARAMETER. The handle must refer to an event registration object, i.e., an ETW_REG_ENTRY, that grants TRACELOG_REGISTER_GUIDS access to user-mode callers. Failure to reference an event registration object from the supposed handle is failure for the function. Provider traits are new functionality which is not back-fitted to classic providers. if the registration is for a classic provider, the function returns STATUS_INVALID_PARAMETER. Traits can be set only once for each registration. (This is even documented in almost exactly those words.) If the registration already has traits, the function returns STATUS_UNSUCCESSFUL.
The traits information must be in user-mode address space, else the function returns STATUS_ACCESS_VIOLATION. All the function’s access to the traits information is subject to exception handling. The traits information is eventually to be saved in paged pool pointed to by the Traits member of the ETW_REG_ENTRY. The form is an ETW_PROVIDER_TRAITS structure as the fixed-size header to a copy of the given traits information. If the function cannot get this memory for the header and copy, it returns STATUS_INSUFFICIENT_RESOURCES.
The traits information begins with a 16-bit size and a null-terminated string of single-byte characters. There may then be any number of descriptors of individual traits. Each has its own 16-bit size and 8-bit type (from the ETW_PROVIDER_TRAIT_TYPE enumeration), to be followed immediately by type-specific data. The function returns STATUS_FILE_CORRUPT_ERROR if any of the following are true:
When the same traits are set for multiple provider registrations, the kernel keeps just the one ETW_PROVIDER_TRAITS. The kernel keeps a tree of these structures for different traits. If it turns out that the traits are already in the tree, then the existing structure is used instead: its reference count is incremented and the newly created structure is freed. Whichever structure is used, its address goes into the registration as the Traits member. If this has got set meanwhile, the function returns STATUS_UNSUCCESSFUL.
In the original Windows 10, the only provider trait that yet matters to the kernel is that which assigns the registration to a provider group. For this, the 8-bit type is EtwProviderTraitTypeGroup (1) and the type-specific data is the GUID of the group, making a total size of 0x13 bytes for the trait. Adding a registration to a provider group is non-trivial and is left to be presented elsewhere. Failure to add the registration to the given provider group is failure for the function.
If the function succeeds, even at setting trivial traits, it marks the registration as reliably using the Type member in its EVENT_DATA_DESCRIPTOR structures when writing events.