Skip to content

Commit e4771c0

Browse files
authored
perf: Eventset masks (#241)
1 parent e3ca65e commit e4771c0

File tree

9 files changed

+214
-25
lines changed

9 files changed

+214
-25
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ require (
4141
)
4242

4343
require (
44+
github.com/bits-and-blooms/bitset v1.13.0 // indirect
4445
github.com/rivo/uniseg v0.4.2 // indirect
4546
github.com/rogpeppe/go-internal v1.11.0 // indirect
4647
golang.org/x/arch v0.6.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
2626
github.com/aws/aws-sdk-go v1.34.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
2727
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
2828
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
29+
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
30+
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
2931
github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw=
3032
github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
3133
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=

pkg/config/kstream.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
package config
2323

2424
import (
25+
"golang.org/x/sys/windows"
2526
"runtime"
2627
"time"
2728

@@ -95,7 +96,8 @@ type KstreamConfig struct {
9596
// ExcludedImages are process image names that will be rejected if they generate a kernel event.
9697
ExcludedImages []string `json:"blacklist.images" yaml:"blacklist.images"`
9798

98-
excludedKtypes map[ktypes.Ktype]bool
99+
eventsetMasks ktypes.EventsetMasks
100+
99101
excludedImages map[string]bool
100102
}
101103

@@ -117,12 +119,11 @@ func (c *KstreamConfig) initFromViper(v *viper.Viper) {
117119
c.ExcludedKevents = v.GetStringSlice(excludedEvents)
118120
c.ExcludedImages = v.GetStringSlice(excludedImages)
119121

120-
c.excludedKtypes = make(map[ktypes.Ktype]bool)
121122
c.excludedImages = make(map[string]bool)
122123

123124
for _, name := range c.ExcludedKevents {
124125
if ktype := ktypes.KeventNameToKtype(name); ktype != ktypes.UnknownKtype {
125-
c.excludedKtypes[ktype] = true
126+
c.eventsetMasks.Set(ktype)
126127
}
127128
}
128129
for _, name := range c.ExcludedImages {
@@ -132,12 +133,11 @@ func (c *KstreamConfig) initFromViper(v *viper.Viper) {
132133

133134
// Init is an exported method to allow initializing exclusion maps from external modules.
134135
func (c *KstreamConfig) Init() {
135-
c.excludedKtypes = make(map[ktypes.Ktype]bool)
136136
c.excludedImages = make(map[string]bool)
137137
for _, name := range c.ExcludedKevents {
138138
for _, ktype := range ktypes.KeventNameToKtypes(name) {
139139
if ktype != ktypes.UnknownKtype {
140-
c.excludedKtypes[ktype] = true
140+
c.eventsetMasks.Set(ktype)
141141
}
142142
}
143143
}
@@ -146,10 +146,10 @@ func (c *KstreamConfig) Init() {
146146
}
147147
}
148148

149-
// ExcludeKevent determines whether the supplied event type is present in the list of
150-
// excluded event types.
151-
func (c *KstreamConfig) ExcludeKevent(ktype ktypes.Ktype) bool {
152-
return c.excludedKtypes[ktype]
149+
// ExcludeKevent determines whether the supplied provider GUID
150+
// and the hook identifier are in the bitset of excluded events.
151+
func (c *KstreamConfig) ExcludeKevent(guid windows.GUID, hookID uint16) bool {
152+
return c.eventsetMasks.Test(guid, hookID)
153153
}
154154

155155
// ExcludeImage determines whether the process generating event is present in the

pkg/config/kstream_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func TestKstreamConfig(t *testing.T) {
5656
assert.False(t, c.Kstream.EnableImageKevents)
5757
assert.False(t, c.Kstream.EnableFileIOKevents)
5858

59-
assert.True(t, c.Kstream.ExcludeKevent(ktypes.CloseHandle))
60-
assert.False(t, c.Kstream.ExcludeKevent(ktypes.CreateProcess))
59+
assert.True(t, c.Kstream.ExcludeKevent(ktypes.CloseHandle.GUID(), ktypes.CloseHandle.HookID()))
60+
assert.False(t, c.Kstream.ExcludeKevent(ktypes.CreateProcess.GUID(), ktypes.CreateProcess.HookID()))
6161

6262
assert.True(t, c.Kstream.ExcludeImage(&pstypes.PS{Name: "svchost.exe"}))
6363
assert.False(t, c.Kstream.ExcludeImage(&pstypes.PS{Name: "explorer.exe"}))

pkg/kevent/ktypes/eventset.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2021-2022 by Nedim Sabic Sabic
3+
* https://www.fibratus.io
4+
* All Rights Reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package ktypes
20+
21+
import (
22+
"github.com/bits-and-blooms/bitset"
23+
"golang.org/x/sys/windows"
24+
)
25+
26+
// EventsetMasks allows efficient testing
27+
// of a group of bitsets containing event
28+
// hook identifiers. For each provider
29+
// is represented by a GUID, a dedicated
30+
// bitset is defined.
31+
type EventsetMasks struct {
32+
masks [ProvidersCount]bitset.BitSet
33+
}
34+
35+
// Set puts a new event type into the bitset.
36+
func (e *EventsetMasks) Set(ktype Ktype) {
37+
i := e.bitsetIndex(ktype.GUID())
38+
if i < 0 {
39+
panic("invalid bitset index")
40+
}
41+
e.masks[i].Set(uint(ktype.HookID()))
42+
}
43+
44+
// Test checks if the given provider GUID and
45+
// hook identifier are present in the bitset.
46+
func (e *EventsetMasks) Test(guid windows.GUID, hookID uint16) bool {
47+
i := e.bitsetIndex(guid)
48+
if i < 0 {
49+
return false
50+
}
51+
return e.masks[i].Test(uint(hookID))
52+
}
53+
54+
// Clear clears the bitset for a given provider GUID.
55+
func (e *EventsetMasks) Clear(guid windows.GUID) {
56+
i := e.bitsetIndex(guid)
57+
if i < 0 {
58+
panic("invalid bitset index")
59+
}
60+
e.masks[i].ClearAll()
61+
}
62+
63+
func (e *EventsetMasks) bitsetIndex(guid windows.GUID) int {
64+
switch guid {
65+
case ProcessEventGUID:
66+
return 0
67+
case ThreadEventGUID:
68+
return 1
69+
case ImageEventGUID:
70+
return 2
71+
case FileEventGUID:
72+
return 3
73+
case RegistryEventGUID:
74+
return 4
75+
case NetworkEventGUID:
76+
return 5
77+
case HandleEventGUID:
78+
return 6
79+
case MemEventGUID:
80+
return 7
81+
case AuditAPIEventGUID:
82+
return 8
83+
case DNSEventGUID:
84+
return 9
85+
}
86+
return -1
87+
}

pkg/kevent/ktypes/eventset_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2021-2022 by Nedim Sabic Sabic
3+
* https://www.fibratus.io
4+
* All Rights Reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package ktypes
20+
21+
import (
22+
"github.com/rabbitstack/fibratus/pkg/sys/etw"
23+
"github.com/stretchr/testify/require"
24+
"testing"
25+
)
26+
27+
func TestEventsetMasks(t *testing.T) {
28+
var masks EventsetMasks
29+
masks.Set(TerminateThread)
30+
masks.Set(CreateThread)
31+
masks.Set(TerminateProcess)
32+
33+
require.True(t, masks.Test(ThreadEventGUID, TerminateThread.HookID()))
34+
require.True(t, masks.Test(ThreadEventGUID, CreateThread.HookID()))
35+
require.False(t, masks.Test(ThreadEventGUID, ThreadRundown.HookID()))
36+
require.True(t, masks.Test(ProcessEventGUID, TerminateProcess.HookID()))
37+
require.False(t, masks.Test(ProcessEventGUID, CreateProcess.HookID()))
38+
39+
masks.Clear(ThreadEventGUID)
40+
41+
require.False(t, masks.Test(ThreadEventGUID, TerminateThread.HookID()))
42+
require.False(t, masks.Test(ThreadEventGUID, CreateThread.HookID()))
43+
}
44+
45+
func BenchmarkEventsetMasks(b *testing.B) {
46+
b.ReportAllocs()
47+
48+
var masks EventsetMasks
49+
masks.Set(TerminateThread)
50+
masks.Set(CreateThread)
51+
masks.Set(TerminateProcess)
52+
masks.Set(CreateFile)
53+
54+
evt := etw.EventRecord{Header: etw.EventHeader{ProviderID: ThreadEventGUID, EventDescriptor: etw.EventDescriptor{Opcode: 2}}}
55+
56+
for i := 0; i < b.N; i++ {
57+
if !masks.Test(evt.Header.ProviderID, uint16(evt.Header.EventDescriptor.Opcode)) {
58+
panic("mask should be present")
59+
}
60+
}
61+
}
62+
63+
func BenchmarkStdlibMap(b *testing.B) {
64+
b.ReportAllocs()
65+
66+
evts := make(map[Ktype]bool)
67+
evts[TerminateThread] = true
68+
evts[CreateThread] = true
69+
evts[TerminateProcess] = true
70+
evts[CreateFile] = true
71+
72+
evt := etw.EventRecord{Header: etw.EventHeader{ProviderID: ThreadEventGUID, EventDescriptor: etw.EventDescriptor{Opcode: 2}}}
73+
kt := NewFromEventRecord(&evt)
74+
75+
for i := 0; i < b.N; i++ {
76+
if !evts[kt] {
77+
panic("event should be present")
78+
}
79+
}
80+
}

pkg/kevent/ktypes/ktypes_windows.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,34 @@ import (
2525
"golang.org/x/sys/windows"
2626
)
2727

28+
// ProvidersCount designates the number of interesting providers.
29+
// Remember to increment if a new event source is introduced.
30+
const ProvidersCount = 10
31+
2832
// Ktype identifies an event type. It comprises the event GUID + hook ID to uniquely identify the event
2933
type Ktype [18]byte
3034

3135
var (
32-
// ProcessEventGUID represents process event GUID
36+
// ProcessEventGUID represents process provider event GUID
3337
ProcessEventGUID = windows.GUID{Data1: 0x3d6fa8d0, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}
34-
// ThreadEventGUID represents thread evens GUID
38+
// ThreadEventGUID represents thread provider event GUID
3539
ThreadEventGUID = windows.GUID{Data1: 0x3d6fa8d1, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}
36-
// FileEventGUID represents file event GUID
40+
// ImageEventGUID represents image provider event GUID
41+
ImageEventGUID = windows.GUID{Data1: 0x2cb15d1d, Data2: 0x5fc1, Data3: 0x11d2, Data4: [8]byte{0xab, 0xe1, 0x0, 0xa0, 0xc9, 0x11, 0xf5, 0x18}}
42+
// FileEventGUID represents file provider event GUID
3743
FileEventGUID = windows.GUID{Data1: 0x90cbdc39, Data2: 0x4a3e, Data3: 0x11d1, Data4: [8]byte{0x84, 0xf4, 0x0, 0x0, 0xf8, 0x04, 0x64, 0xe3}}
38-
// RegistryEventGUID represents registry event GUID
44+
// RegistryEventGUID represents registry provider event GUID
3945
RegistryEventGUID = windows.GUID{Data1: 0xae53722e, Data2: 0xc863, Data3: 0x11d2, Data4: [8]byte{0x86, 0x59, 0x0, 0xc0, 0x4f, 0xa3, 0x21, 0xa1}}
46+
// NetworkEventGUID represents network provider event GUID
47+
NetworkEventGUID = windows.GUID{Data1: 0x9a280ac0, Data2: 0xc8e0, Data3: 0x11d1, Data4: [8]byte{0x84, 0xe2, 0x0, 0xc0, 0x4f, 0xb9, 0x98, 0xa2}}
48+
// HandleEventGUID represents handle provider event GUID
49+
HandleEventGUID = windows.GUID{Data1: 0x89497f50, Data2: 0xeffe, Data3: 0x4440, Data4: [8]byte{0x8c, 0xf2, 0xce, 0x6b, 0x1c, 0xdc, 0xac, 0xa7}}
50+
// MemEventGUID represents memory provider event GUID
51+
MemEventGUID = windows.GUID{Data1: 0x3d6fa8d3, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}
52+
// AuditAPIEventGUID represents audit API calls event GUID
53+
AuditAPIEventGUID = windows.GUID{Data1: 0xe02a841c, Data2: 0x75a3, Data3: 0x4fa7, Data4: [8]byte{0xaf, 0xc8, 0xae, 0x09, 0xcf, 0x9b, 0x7f, 0x23}}
54+
// DNSEventGUID represents DNS provider event GUID
55+
DNSEventGUID = windows.GUID{Data1: 0x1c95126e, Data2: 0x7eea, Data3: 0x49a9, Data4: [8]byte{0xa3, 0xfe, 0xa3, 0x78, 0xb0, 0x3d, 0xdb, 0x4d}}
4056
)
4157

4258
var (
@@ -183,12 +199,7 @@ var (
183199

184200
// NewFromEventRecord creates a new event type from ETW event record.
185201
func NewFromEventRecord(ev *etw.EventRecord) Ktype {
186-
switch ev.Header.ProviderID {
187-
case etw.KernelAuditAPICallsGUID, etw.DNSClientGUID:
188-
return pack(ev.Header.ProviderID, ev.Header.EventDescriptor.ID)
189-
default:
190-
return pack(ev.Header.ProviderID, uint16(ev.Header.EventDescriptor.Opcode))
191-
}
202+
return pack(ev.Header.ProviderID, ev.HookID())
192203
}
193204

194205
// String returns the string representation of the event type. Returns an empty string

pkg/kstream/consumer_windows.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ func (k *consumer) processEvent(ev *etw.EventRecord) error {
201201
if kevent.IsCurrentProcDropped(ev.Header.ProcessID) {
202202
return nil
203203
}
204+
if k.config.Kstream.ExcludeKevent(ev.Header.ProviderID, ev.HookID()) {
205+
excludedKevents.Add(1)
206+
return nil
207+
}
204208
ktype := ktypes.NewFromEventRecord(ev)
205209
if !ktype.Exists() {
206210
keventsUnknown.Add(1)
207211
return nil
208212
}
209-
if k.config.Kstream.ExcludeKevent(ktype) {
210-
excludedKevents.Add(1)
211-
return nil
212-
}
213213
keventsProcessed.Add(1)
214214
evt := kevent.New(k.sequencer.Get(), ktype, ev)
215215
// Dispatch each event to the processor chain.

pkg/sys/etw/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,14 @@ func (e *EventRecord) Version() uint8 {
554554
return e.Header.EventDescriptor.Version
555555
}
556556

557+
// HookID returns either the opcode or the event ID.
558+
func (e *EventRecord) HookID() uint16 {
559+
if e.Header.EventDescriptor.Opcode > 0 {
560+
return uint16(e.Header.EventDescriptor.Opcode)
561+
}
562+
return e.Header.EventDescriptor.ID
563+
}
564+
557565
// ReadByte reads the byte from the buffer at the specified offset.
558566
func (e *EventRecord) ReadByte(offset uint16) byte {
559567
if offset > e.BufferLen {

0 commit comments

Comments
 (0)