From c5294c98c0dfa1d7ff15fac9b5881de7e5d90194 Mon Sep 17 00:00:00 2001 From: Kevin Parsons Date: Wed, 10 Jul 2019 15:03:44 -0700 Subject: [PATCH 1/3] guid: Improve version and variant handling Signed-off-by: Kevin Parsons --- pkg/guid/guid.go | 30 ++++++++++++++++-- pkg/guid/guid_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/pkg/guid/guid.go b/pkg/guid/guid.go index d0595f66..06ccb979 100644 --- a/pkg/guid/guid.go +++ b/pkg/guid/guid.go @@ -52,10 +52,11 @@ func NewV4() (GUID, error) { return GUID{}, err } - b[6] = (b[6] & 0x0f) | 0x40 // Version 4 (randomly generated) - b[8] = (b[8] & 0x3f) | 0x80 // RFC4122 variant + g := FromArray(b) + g.setVersion(4) // Version 4 means randomly generated. + g.setVariant(VariantRFC4122) - return FromArray(b), nil + return g, nil } func fromArray(b [16]byte, order binary.ByteOrder) GUID { @@ -150,6 +151,25 @@ func FromString(s string) (GUID, error) { return g, nil } +func (g *GUID) setVariant(v Variant) { + d := g.Data4[0] + switch v { + case VariantNCS: + d = (d & 0x7f) + case VariantRFC4122: + d = (d & 0x3f) | 0x80 + case VariantMicrosoft: + d = (d & 0x1f) | 0xc0 + case VariantFuture: + d = (d & 0x0f) | 0xe0 + case VariantUnknown: + fallthrough + default: + panic(fmt.Sprintf("invalid variant: %d", v)) + } + g.Data4[0] = d +} + // Variant returns the GUID variant, as defined in RFC 4122. func (g GUID) Variant() Variant { b := g.Data4[0] @@ -165,6 +185,10 @@ func (g GUID) Variant() Variant { return VariantUnknown } +func (g *GUID) setVersion(v Version) { + g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) +} + // Version returns the GUID version, as defined in RFC 4122. func (g GUID) Version() Version { return Version((g.Data3 & 0xF000) >> 12) diff --git a/pkg/guid/guid_test.go b/pkg/guid/guid_test.go index b229cc6c..b84bc41f 100644 --- a/pkg/guid/guid_test.go +++ b/pkg/guid/guid_test.go @@ -22,6 +22,78 @@ func mustFromString(t *testing.T, s string) GUID { return g } +func Test_Variant(t *testing.T) { + type testCase struct { + g GUID + v Variant + } + testCases := []testCase{ + {mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), VariantNCS}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-7fdd-b6761fc7dcc0"), VariantNCS}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0"), VariantRFC4122}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-9fdd-b6761fc7dcc0"), VariantRFC4122}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-cfdd-b6761fc7dcc0"), VariantMicrosoft}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-dfdd-b6761fc7dcc0"), VariantMicrosoft}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-efdd-b6761fc7dcc0"), VariantFuture}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-ffdd-b6761fc7dcc0"), VariantFuture}, + } + for _, tc := range testCases { + actualVariant := tc.g.Variant() + if actualVariant != tc.v { + t.Fatalf("Variant is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVariant, tc.g) + } + } +} + +func Test_SetVariant(t *testing.T) { + testCases := []Variant{ + VariantNCS, + VariantRFC4122, + VariantMicrosoft, + VariantFuture, + } + g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") + for _, tc := range testCases { + t.Logf("Test case: %d", tc) + g.setVariant(tc) + if g.Variant() != tc { + t.Fatalf("Variant is incorrect.\nExpected: %d\nActual: %d", tc, g.Variant()) + } + } +} + +func Test_Version(t *testing.T) { + type testCase struct { + g GUID + v Version + } + testCases := []testCase{ + {mustFromString(t, "f5cbc1a9-4cba-15a0-0fdd-b6761fc7dcc0"), 1}, + {mustFromString(t, "f5cbc1a9-4cba-25a0-0fdd-b6761fc7dcc0"), 2}, + {mustFromString(t, "f5cbc1a9-4cba-35a0-0fdd-b6761fc7dcc0"), 3}, + {mustFromString(t, "f5cbc1a9-4cba-45a0-0fdd-b6761fc7dcc0"), 4}, + {mustFromString(t, "f5cbc1a9-4cba-55a0-0fdd-b6761fc7dcc0"), 5}, + } + for _, tc := range testCases { + actualVersion := tc.g.Version() + if actualVersion != tc.v { + t.Fatalf("Version is not correct.\nExpected: %d\nActual: %d\nGUID: %s", tc.v, actualVersion, tc.g) + } + } +} + +func Test_SetVersion(t *testing.T) { + g := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") + for tc := 0; tc < 16; tc++ { + t.Logf("Test case: %d", tc) + v := Version(tc) + g.setVersion(v) + if g.Version() != v { + t.Fatalf("Version is incorrect.\nExpected: %d\nActual: %d", v, g.Version()) + } + } +} + func Test_NewV4IsUnique(t *testing.T) { g := mustNewV4(t) g2 := mustNewV4(t) From 177d2ec7ea591265c65f379610c4f8e54ac3df01 Mon Sep 17 00:00:00 2001 From: Kevin Parsons Date: Wed, 10 Jul 2019 15:08:22 -0700 Subject: [PATCH 2/3] guid: Add V5 GUID support Signed-off-by: Kevin Parsons --- pkg/guid/guid.go | 24 +++++++++++++++++++ pkg/guid/guid_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/pkg/guid/guid.go b/pkg/guid/guid.go index 06ccb979..58640657 100644 --- a/pkg/guid/guid.go +++ b/pkg/guid/guid.go @@ -7,6 +7,7 @@ package guid import ( "crypto/rand" + "crypto/sha1" "encoding" "encoding/binary" "fmt" @@ -59,6 +60,29 @@ func NewV4() (GUID, error) { return g, nil } +// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) +// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, +// and the sample code treats it as a series of bytes, so we do the same here. +// +// Some implementations, such as those found on Windows, treat the name as a +// big-endian UTF16 stream of bytes. If that is desired, the string can be +// encoded as such before being passed to this function. +func NewV5(namespace GUID, name []byte) (GUID, error) { + b := sha1.New() + namespaceBytes := namespace.ToArray() + b.Write(namespaceBytes[:]) + b.Write(name) + + a := [16]byte{} + copy(a[:], b.Sum(nil)) + + g := FromArray(a) + g.setVersion(5) // Version 5 means generated from a string. + g.setVariant(VariantRFC4122) + + return g, nil +} + func fromArray(b [16]byte, order binary.ByteOrder) GUID { var g GUID g.Data1 = order.Uint32(b[0:4]) diff --git a/pkg/guid/guid_test.go b/pkg/guid/guid_test.go index b84bc41f..d7e21241 100644 --- a/pkg/guid/guid_test.go +++ b/pkg/guid/guid_test.go @@ -14,6 +14,14 @@ func mustNewV4(t *testing.T) GUID { return g } +func mustNewV5(t *testing.T, namespace GUID, name []byte) GUID { + g, err := NewV5(namespace, name) + if err != nil { + t.Fatal(err) + } + return g +} + func mustFromString(t *testing.T, s string) GUID { g, err := FromString(s) if err != nil { @@ -112,6 +120,53 @@ func Test_V4HasCorrectVersionAndVariant(t *testing.T) { } } +func Test_V5HasCorrectVersionAndVariant(t *testing.T) { + namespace := mustFromString(t, "f5cbc1a9-4cba-45a0-bfdd-b6761fc7dcc0") + g := mustNewV5(t, namespace, []byte("Foo")) + if g.Version() != 5 { + t.Fatalf("Version is not 5: %s", g) + } + if g.Variant() != VariantRFC4122 { + t.Fatalf("Variant is not RFC4122: %s", g) + } +} + +func Test_V5KnownValues(t *testing.T) { + type testCase struct { + ns GUID + name string + g GUID + } + testCases := []testCase{ + { + mustFromString(t, "6ba7b810-9dad-11d1-80b4-00c04fd430c8"), + "www.sample.com", + mustFromString(t, "4e4463eb-b0e8-54fa-8c28-12d1ab1d45b3"), + }, + { + mustFromString(t, "6ba7b811-9dad-11d1-80b4-00c04fd430c8"), + "https://www.sample.com/test", + mustFromString(t, "9e44625a-0d85-5e0a-99bc-8e8a77df5ea2"), + }, + { + mustFromString(t, "6ba7b812-9dad-11d1-80b4-00c04fd430c8"), + "1.3.6.1.4.1.343", + mustFromString(t, "6aab0456-7392-582a-b92a-ba5a7096945d"), + }, + { + mustFromString(t, "6ba7b814-9dad-11d1-80b4-00c04fd430c8"), + "CN=John Smith, ou=People, o=FakeCorp, L=Seattle, S=Washington, C=US", + mustFromString(t, "badff8dd-c869-5b64-a260-00092e66be00"), + }, + } + for _, tc := range testCases { + g := mustNewV5(t, tc.ns, []byte(tc.name)) + if g != tc.g { + t.Fatalf("GUIDs are not equal.\nExpected: %s\nActual: %s", tc.g, g) + } + } +} + func Test_ToArray(t *testing.T) { g := mustFromString(t, "73c39589-192e-4c64-9acf-6c5d0aa18528") b := g.ToArray() From c20fb68775894e92ce4e028ce7442b074c5e73d9 Mon Sep 17 00:00:00 2001 From: Kevin Parsons Date: Wed, 10 Jul 2019 15:12:01 -0700 Subject: [PATCH 3/3] etw: Add test for provider name to GUID conversion Also simplified the name to GUID conversion code, and improved the comments. Signed-off-by: Kevin Parsons --- pkg/etw/provider.go | 27 ++++++++++++++------------- pkg/etw/provider_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 pkg/etw/provider_test.go diff --git a/pkg/etw/provider.go b/pkg/etw/provider.go index 28db0d59..23696018 100644 --- a/pkg/etw/provider.go +++ b/pkg/etw/provider.go @@ -94,26 +94,27 @@ func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, // 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 +// +// The algorithm is roughly the RFC 4122 algorithm for a V5 UUID, but differs in +// the following ways: +// - The input name is first upper-cased, UTF16-encoded, and converted to +// big-endian. +// - No variant is set on the result UUID. +// - The result UUID is treated as being in little-endian format, rather than +// big-endian. func providerIDFromName(name string) guid.GUID { buffer := sha1.New() - - namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB} - buffer.Write(namespace) - + namespace := guid.GUID{0x482C2DB2, 0xC390, 0x47C8, [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}} + namespaceBytes := namespace.ToArray() + buffer.Write(namespaceBytes[:]) binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name)))) sum := buffer.Sum(nil) sum[7] = (sum[7] & 0xf) | 0x50 - return guid.GUID{ - Data1: binary.LittleEndian.Uint32(sum[0:4]), - Data2: binary.LittleEndian.Uint16(sum[4:6]), - Data3: binary.LittleEndian.Uint16(sum[6:8]), - Data4: [8]byte{sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]}, - } + a := [16]byte{} + copy(a[:], sum) + return guid.FromWindowsArray(a) } // NewProvider creates and registers a new ETW provider. The provider ID is diff --git a/pkg/etw/provider_test.go b/pkg/etw/provider_test.go new file mode 100644 index 00000000..69d9910c --- /dev/null +++ b/pkg/etw/provider_test.go @@ -0,0 +1,34 @@ +package etw + +import ( + "testing" + + "github.com/Microsoft/go-winio/pkg/guid" +) + +func mustGUIDFromString(t *testing.T, s string) guid.GUID { + g, err := guid.FromString(s) + if err != nil { + t.Fatal(err) + } + return g +} + +func Test_ProviderIDFromName(t *testing.T) { + type testCase struct { + name string + g guid.GUID + } + testCases := []testCase{ + {"wincni", mustGUIDFromString(t, "c822b598-f4cc-5a72-7933-ce2a816d033f")}, + {"Moby", mustGUIDFromString(t, "6996f090-c5de-5082-a81e-5841acc3a635")}, + {"ContainerD", mustGUIDFromString(t, "2acb92c0-eb9b-571a-69cf-8f3410f383ad")}, + {"Microsoft.Virtualization.RunHCS", mustGUIDFromString(t, "0B52781F-B24D-5685-DDF6-69830ED40EC3")}, + } + for _, tc := range testCases { + g := providerIDFromName(tc.name) + if g != tc.g { + t.Fatalf("Incorrect provider GUID.\nExpected: %s\nActual: %s", tc.g, g) + } + } +}