x-pack/libbeat/reader/etw/event.go (233 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
//go:build windows
package etw
import (
"errors"
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
// propertyParser is used for parsing properties from raw EVENT_RECORD structures.
type propertyParser struct {
r *EventRecord
info *TraceEventInfo
data []byte
ptrSize uint32
}
// GetEventProperties extracts and returns properties from an ETW event record.
func GetEventProperties(r *EventRecord) (map[string]interface{}, error) {
// Handle the case where the event only contains a string.
if r.EventHeader.Flags == EVENT_HEADER_FLAG_STRING_ONLY {
userDataPtr := (*uint16)(unsafe.Pointer(r.UserData))
return map[string]interface{}{
"_": utf16AtOffsetToString(uintptr(unsafe.Pointer(userDataPtr)), 0), // Convert the user data from UTF16 to string.
}, nil
}
// Initialize a new property parser for the event record.
p, err := newPropertyParser(r)
if err != nil {
return nil, fmt.Errorf("failed to parse event properties: %w", err)
}
// Iterate through each property of the event and format it
properties := make(map[string]interface{}, int(p.info.TopLevelPropertyCount))
for i := 0; i < int(p.info.TopLevelPropertyCount); i++ {
name := p.getPropertyName(i)
value, err := p.getPropertyValue(i)
if err != nil {
return nil, fmt.Errorf("failed to parse %q value: %w", name, err)
}
properties[name] = value
}
return properties, nil
}
// newPropertyParser initializes a new property parser for a given event record.
func newPropertyParser(r *EventRecord) (*propertyParser, error) {
info, err := getEventInformation(r)
if err != nil {
return nil, fmt.Errorf("failed to get event information: %w", err)
}
ptrSize := r.pointerSize()
// Return a new propertyParser instance initialized with event record data and metadata.
return &propertyParser{
r: r,
info: info,
ptrSize: ptrSize,
data: unsafe.Slice((*uint8)(unsafe.Pointer(r.UserData)), r.UserDataLength),
}, nil
}
// getEventPropertyInfoAtIndex looks for the EventPropertyInfo object at a specified index.
func (info *TraceEventInfo) getEventPropertyInfoAtIndex(i uint32) *EventPropertyInfo {
if i < info.PropertyCount {
// Calculate the address of the first element in EventPropertyInfoArray.
eventPropertyInfoPtr := uintptr(unsafe.Pointer(&info.EventPropertyInfoArray[0]))
// Adjust the pointer to point to the i-th EventPropertyInfo element.
eventPropertyInfoPtr += uintptr(i) * unsafe.Sizeof(EventPropertyInfo{})
return ((*EventPropertyInfo)(unsafe.Pointer(eventPropertyInfoPtr)))
}
return nil
}
// getEventInformation retrieves detailed metadata about an event record.
func getEventInformation(r *EventRecord) (info *TraceEventInfo, err error) {
// Initially call TdhGetEventInformation to get the required buffer size.
var bufSize uint32
if err = _TdhGetEventInformation(r, 0, nil, nil, &bufSize); errors.Is(err, ERROR_INSUFFICIENT_BUFFER) {
// Allocate enough memory for TRACE_EVENT_INFO based on the required size.
buff := make([]byte, bufSize)
info = ((*TraceEventInfo)(unsafe.Pointer(&buff[0])))
// Retrieve the event information into the allocated buffer.
err = _TdhGetEventInformation(r, 0, nil, info, &bufSize)
}
// Check for errors in retrieving the event information.
if err != nil {
return nil, fmt.Errorf("TdhGetEventInformation failed: %w", err)
}
return info, nil
}
// getPropertyName retrieves the name of the i-th event property in the event record.
func (p *propertyParser) getPropertyName(i int) string {
// Convert the UTF16 property name to a Go string.
namePtr := readPropertyName(p, i)
return windows.UTF16PtrToString((*uint16)(namePtr))
}
// readPropertyName gets the pointer to the property name in the event information structure.
func readPropertyName(p *propertyParser, i int) unsafe.Pointer {
// Calculate the pointer to the property name using its offset in the event property array.
return unsafe.Add(unsafe.Pointer(p.info), p.info.getEventPropertyInfoAtIndex(uint32(i)).NameOffset)
}
// getPropertyValue retrieves the value of a specified event property.
func (p *propertyParser) getPropertyValue(i int) (interface{}, error) {
propertyInfo := p.info.getEventPropertyInfoAtIndex(uint32(i))
// Determine the size of the property array.
arraySize, err := p.getArraySize(*propertyInfo)
if err != nil {
return nil, fmt.Errorf("failed to get array size: %w", err)
}
// Initialize a slice to hold the property values.
result := make([]interface{}, arraySize)
for j := 0; j < int(arraySize); j++ {
var (
value interface{}
err error
)
// Parse the property value based on its type (simple or structured).
if (propertyInfo.Flags & PropertyStruct) == PropertyStruct {
value, err = p.parseStruct(*propertyInfo)
} else {
value, err = p.parseSimpleType(*propertyInfo)
}
if err != nil {
return nil, err
}
result[j] = value
}
// Return the entire result set or the single value, based on the property count.
if ((propertyInfo.Flags & PropertyParamCount) == PropertyParamCount) ||
(propertyInfo.count() > 1) {
return result, nil
}
return result[0], nil
}
// getArraySize calculates the size of an array property within an event.
func (p *propertyParser) getArraySize(propertyInfo EventPropertyInfo) (uint32, error) {
// Check if the property's count is specified by another property.
if (propertyInfo.Flags & PropertyParamCount) == PropertyParamCount {
var dataDescriptor PropertyDataDescriptor
// Locate the property containing the array size using the countPropertyIndex.
dataDescriptor.PropertyName = readPropertyName(p, int(propertyInfo.count()))
dataDescriptor.ArrayIndex = 0xFFFFFFFF
// Retrieve the length of the array from the specified property.
return getLengthFromProperty(p.r, &dataDescriptor)
} else {
// If the array size is directly specified, return it.
return uint32(propertyInfo.count()), nil
}
}
// getLengthFromProperty retrieves the length of a property from an event record.
func getLengthFromProperty(r *EventRecord, dataDescriptor *PropertyDataDescriptor) (uint32, error) {
var length uint32
// Call TdhGetProperty to get the length of the property specified by the dataDescriptor.
err := _TdhGetProperty(
r,
0,
nil,
1,
dataDescriptor,
uint32(unsafe.Sizeof(length)),
(*byte)(unsafe.Pointer(&length)),
)
if err != nil {
return 0, err
}
return length, nil
}
// parseStruct extracts and returns the fields from an embedded structure within a property.
func (p *propertyParser) parseStruct(propertyInfo EventPropertyInfo) (map[string]interface{}, error) {
// Determine the start and end indexes of the structure members within the property info.
startIndex := propertyInfo.structStartIndex()
lastIndex := startIndex + propertyInfo.numOfStructMembers()
// Initialize a map to hold the structure's fields.
structure := make(map[string]interface{}, (lastIndex - startIndex))
// Iterate through each member of the structure.
for j := startIndex; j < lastIndex; j++ {
name := p.getPropertyName(int(j))
value, err := p.getPropertyValue(int(j))
if err != nil {
return nil, fmt.Errorf("failed parse field '%s' of complex property type: %w", name, err)
}
structure[name] = value // Add the field to the structure map.
}
return structure, nil
}
// parseSimpleType parses a simple property type using TdhFormatProperty.
func (p *propertyParser) parseSimpleType(propertyInfo EventPropertyInfo) (string, error) {
var mapInfo *EventMapInfo
if propertyInfo.mapNameOffset() > 0 {
// If failed retrieving the map information, returns on error
var err error
mapInfo, err = p.getMapInfo(propertyInfo)
if err != nil {
return "", fmt.Errorf("failed to get map information due to: %w", err)
}
}
// Get the length of the property.
propertyLength, err := p.getPropertyLength(propertyInfo)
if err != nil {
return "", fmt.Errorf("failed to get property length due to: %w", err)
}
var userDataConsumed uint16
// Set a default buffer size for formatted data.
formattedDataSize := uint32(DEFAULT_PROPERTY_BUFFER_SIZE)
formattedData := make([]byte, int(formattedDataSize))
// Retry loop to handle buffer size adjustments.
retryLoop:
for {
var dataPtr *uint8
if len(p.data) > 0 {
dataPtr = &p.data[0]
}
err := _TdhFormatProperty(
p.info,
mapInfo,
p.ptrSize,
propertyInfo.inType(),
propertyInfo.outType(),
uint16(propertyLength),
uint16(len(p.data)),
dataPtr,
&formattedDataSize,
&formattedData[0],
&userDataConsumed,
)
switch {
case err == nil:
// If formatting is successful, break out of the loop.
break retryLoop
case errors.Is(err, ERROR_INSUFFICIENT_BUFFER):
// Increase the buffer size if it's insufficient.
formattedData = make([]byte, formattedDataSize)
continue
case errors.Is(err, ERROR_EVT_INVALID_EVENT_DATA):
// Handle invalid event data error.
// Discarding MapInfo allows us to access
// at least the non-interpreted data.
if mapInfo != nil {
mapInfo = nil
continue
}
return "", fmt.Errorf("TdhFormatProperty failed: %w", err) // Handle unknown error
default:
return "", fmt.Errorf("TdhFormatProperty failed: %w", err)
}
}
// Update the data slice to account for consumed data.
p.data = p.data[userDataConsumed:]
// Convert the formatted data to string and return.
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(&formattedData[0]))), nil
}
// getMapInfo retrieves mapping information for a given property.
func (p *propertyParser) getMapInfo(propertyInfo EventPropertyInfo) (*EventMapInfo, error) {
var mapSize uint32
// Get the name of the map from the property info.
mapName := (*uint16)(unsafe.Add(unsafe.Pointer(p.info), propertyInfo.mapNameOffset()))
// First call to get the required size of the map info.
err := _TdhGetEventMapInformation(p.r, mapName, nil, &mapSize)
switch {
case errors.Is(err, ERROR_NOT_FOUND):
// No mapping information available. This is not an error.
return nil, nil
case errors.Is(err, ERROR_INSUFFICIENT_BUFFER):
// Resize the buffer and try again.
default:
return nil, fmt.Errorf("TdhGetEventMapInformation failed to get size: %w", err)
}
// Allocate buffer and retrieve the actual map information.
buff := make([]byte, int(mapSize))
mapInfo := ((*EventMapInfo)(unsafe.Pointer(&buff[0])))
err = _TdhGetEventMapInformation(p.r, mapName, mapInfo, &mapSize)
if err != nil {
return nil, fmt.Errorf("TdhGetEventMapInformation failed: %w", err)
}
if mapInfo.EntryCount == 0 {
return nil, nil // No entries in the map.
}
return mapInfo, nil
}
// getPropertyLength returns the length of a specific property within TraceEventInfo.
func (p *propertyParser) getPropertyLength(propertyInfo EventPropertyInfo) (uint32, error) {
// Check if the length of the property is defined by another property.
if (propertyInfo.Flags & PropertyParamLength) == PropertyParamLength {
var dataDescriptor PropertyDataDescriptor
// Read the property name that contains the length information.
dataDescriptor.PropertyName = readPropertyName(p, int(propertyInfo.length()))
dataDescriptor.ArrayIndex = 0xFFFFFFFF
// Retrieve the length from the specified property.
return getLengthFromProperty(p.r, &dataDescriptor)
}
inType := propertyInfo.inType()
outType := propertyInfo.outType()
// Special handling for properties representing IPv6 addresses.
// https://docs.microsoft.com/en-us/windows/win32/api/tdh/nf-tdh-tdhformatproperty#remarks
if TdhIntypeBinary == inType && TdhOuttypeIpv6 == outType {
// Return the fixed size of an IPv6 address.
return 16, nil
}
// Default case: return the length as defined in the property info.
// Note: A length of 0 can indicate a variable-length field (e.g., structure, string).
return uint32(propertyInfo.length()), nil
}