diff --git a/internal/etw/eventmetadata.go b/internal/etw/eventmetadata.go index 31ad41a..cdcbfc8 100644 --- a/internal/etw/eventmetadata.go +++ b/internal/etw/eventmetadata.go @@ -114,13 +114,24 @@ func (em *EventMetadata) writeField(f field) { } } +// writeTags writes out the tags value to the event metadata. Tags is a 28-bit +// value, interpreted as bit flags, which are only relevant to the event +// consumer. The event consumer may choose to attribute special meaning to tags +// (e.g. 0x4 could mean the field contains PII). Tags are written as a series of +// bytes, each containing 7 bits of tag value, with the high bit set if there is +// more tag data in the following byte. This allows for a more compact +// representation when not all of the tag bits are needed. func (em *EventMetadata) writeTags(tags uint32) { + // Only use the top 28 bits of the tags value. tags &= 0xfffffff for { + // Tags are written with the most significant bits (e.g. 21-27) first. val := tags >> 21 if tags&0x1fffff == 0 { + // If there is no more data to write after this, write this value + // without the high bit set, and return. em.buffer.WriteByte(byte(val & 0x7f)) return } diff --git a/internal/etw/provider.go b/internal/etw/provider.go index 5e6f65d..880f17e 100644 --- a/internal/etw/provider.go +++ b/internal/etw/provider.go @@ -2,7 +2,9 @@ package etw import ( "bytes" + "crypto/sha1" "encoding/binary" + "strings" "sync" "unsafe" @@ -21,6 +23,7 @@ const ( // name and ID (GUID), which should always have a 1:1 mapping to each other // (e.g. don't use multiple provider names with the same ID, or vice versa). type Provider struct { + ID *windows.GUID handle providerHandle metadata []byte callback EnableCallback @@ -138,17 +141,57 @@ func providerCallbackAdapter(sourceID *windows.GUID, state uintptr, level uintpt return 0 } +// providerIDFromName generates a provider ID based on the provider name. It +// uses the same algorithm as used by .NET's EventSource class, which is based +// on RFC 4122. More information on the algorithm can be found here: +// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +// The algorithm is roughly: +// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be()) +// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122 +func providerIDFromName(name string) (*windows.GUID, error) { + namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB} + buffer := &bytes.Buffer{} + buffer.Write(namespace) + + nameUTF16, err := windows.UTF16FromString(strings.ToUpper(name)) + if err != nil { + return nil, err + } + // nameUTF16 includes a null terminator, which we don't want included in the + // hash. + binary.Write(buffer, binary.BigEndian, nameUTF16[:len(nameUTF16)-1]) + + sum := sha1.Sum(buffer.Bytes()) + sum[7] = (sum[7] & 0xf) | 0x50 + + return &windows.GUID{ + Data1: (uint32(sum[3]) << 24) | (uint32(sum[2]) << 16) | (uint32(sum[1]) << 8) | uint32(sum[0]), + Data2: (uint16(sum[5]) << 8) | uint16(sum[4]), + Data3: (uint16(sum[7]) << 8) | uint16(sum[6]), + Data4: [8]byte{sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]}, + }, nil +} + +func NewProvider(name string, callback EnableCallback) (provider *Provider, err error) { + id, err := providerIDFromName(name) + if err != nil { + return nil, err + } + return NewProviderWithID(name, id, callback) +} + // NewProvider creates and registers a new provider. -func NewProvider(name string, id *windows.GUID, callback EnableCallback) (provider *Provider, err error) { +func NewProviderWithID(name string, id *windows.GUID, callback EnableCallback) (provider *Provider, err error) { provider = providers.newProvider() defer func() { if err != nil { providers.removeProvider(provider) } }() + provider.ID = id provider.callback = callback - if err := eventRegister(id, windows.NewCallback(providerCallbackAdapter), uintptr(provider.index), &provider.handle); err != nil { + if err := eventRegister(provider.ID, windows.NewCallback(providerCallbackAdapter), uintptr(provider.index), &provider.handle); err != nil { return nil, err } diff --git a/internal/etw/sample/sample.go b/internal/etw/sample/sample.go index f42f5ea..1b9d002 100644 --- a/internal/etw/sample/sample.go +++ b/internal/etw/sample/sample.go @@ -3,10 +3,12 @@ package main import ( "bufio" + "encoding/binary" + "encoding/hex" "fmt" "os" - "github.com/Microsoft/go-winio/pkg/etw" + "github.com/Microsoft/go-winio/internal/etw" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" @@ -16,10 +18,25 @@ func callback(sourceID *windows.GUID, state etw.ProviderState, level etw.Level, fmt.Printf("Callback: isEnabled=%d, level=%d, matchAnyKeyword=%d\n", state, level, matchAnyKeyword) } +func guidToString(guid *windows.GUID) string { + data1 := make([]byte, 4) + binary.BigEndian.PutUint32(data1, guid.Data1) + data2 := make([]byte, 2) + binary.BigEndian.PutUint16(data2, guid.Data2) + data3 := make([]byte, 2) + binary.BigEndian.PutUint16(data3, guid.Data3) + return fmt.Sprintf( + "%s-%s-%s-%s-%s", + hex.EncodeToString(data1), + hex.EncodeToString(data2), + hex.EncodeToString(data3), + hex.EncodeToString(guid.Data4[:2]), + hex.EncodeToString(guid.Data4[2:])) +} + func main() { - providerID := windows.GUID{0xdd2062c6, 0x5d1b, 0x4a0f, [8]uint8{0xbd, 0xb9, 0x22, 0x28, 0xbc, 0xb1, 0x07, 0x7c}} + provider, err := etw.NewProvider("TestProvider", callback) - provider, err := etw.NewProvider("TestProvider", &providerID, callback) if err != nil { logrus.Error(err) return @@ -30,15 +47,17 @@ func main() { } }() + fmt.Println("Provider ID:", guidToString(provider.ID)) + reader := bufio.NewReader(os.Stdin) fmt.Println("Press enter to log an event") reader.ReadString('\n') event := etw.NewEvent("TestEvent", etw.NewEventDescriptor()) - event.Metadata.AddField("TestField", etw.InTypeAnsiString) + event.Metadata.AddField("TestField", etw.InTypeANSIString) event.Data.AddString("Foo") - event.Metadata.AddField("TestField2", etw.InTypeAnsiString) + event.Metadata.AddField("TestField2", etw.InTypeANSIString) event.Data.AddString("Bar") if err := provider.WriteEvent(event); err != nil { diff --git a/pkg/etwlogrus/hook.go b/pkg/etwlogrus/hook.go index 9a3c966..294835a 100644 --- a/pkg/etwlogrus/hook.go +++ b/pkg/etwlogrus/hook.go @@ -6,8 +6,6 @@ import ( "github.com/Microsoft/go-winio/internal/etw" "github.com/sirupsen/logrus" - - "golang.org/x/sys/windows" ) // Hook is a Logrus hook which logs received events to ETW. @@ -16,10 +14,10 @@ type Hook struct { } // NewHook registers a new ETW provider and returns a hook to log from it. -func NewHook(providerName string, providerID *windows.GUID) (*Hook, error) { +func NewHook(providerName string) (*Hook, error) { hook := Hook{} - provider, err := etw.NewProvider(providerName, providerID, nil) + provider, err := etw.NewProvider(providerName, nil) if err != nil { return nil, err }