Skip to content

Commit 907e3c1

Browse files
committed
refactor(eventsource): Unified security telemetry logger session
Unified security telemetry session serves as a container for the events published by all ETW providers except the core NT Kernel Logger provider. By enabling all providers inside the same session, we can preserve event ordering and save extra resources allocated for the ETW session buffers.
1 parent 98e425f commit 907e3c1

File tree

10 files changed

+179
-93
lines changed

10 files changed

+179
-93
lines changed

internal/etw/source.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"github.com/rabbitstack/fibratus/pkg/sys/etw"
3333
"github.com/rabbitstack/fibratus/pkg/util/multierror"
3434
log "github.com/sirupsen/logrus"
35-
"golang.org/x/sys/windows"
3635
"golang.org/x/sys/windows/registry"
3736
"time"
3837
)
@@ -162,16 +161,34 @@ func (e *EventSource) Open(config *config.Config) error {
162161
}
163162
}
164163

165-
e.addTrace(etw.KernelLoggerSession, etw.KernelTraceControlGUID)
164+
// add the core NT Kernel Logger trace
165+
e.addTrace(NewKernelTrace(config))
166+
167+
// security telemetry trace hosts remaining ETW providers
168+
trace := NewTrace(etw.SecurityTelemetrySession, config)
166169

167170
if config.EventSource.EnableDNSEvents {
168-
e.addTrace(etw.DNSClientSession, etw.DNSClientGUID)
171+
trace.AddProvider(etw.DNSClientGUID, false)
169172
}
173+
170174
if config.EventSource.EnableAuditAPIEvents {
171-
e.addTrace(etw.KernelAuditAPICallsSession, etw.KernelAuditAPICallsGUID)
175+
trace.AddProvider(etw.KernelAuditAPICallsGUID, config.EventSource.StackEnrichment)
172176
}
177+
173178
if config.EventSource.EnableThreadpoolEvents {
174-
e.addTrace(etw.ThreadpoolSession, etw.ThreadpoolGUID)
179+
// thread pool provider must be configured with
180+
// stack extensions to activate stack walks events
181+
var stackexts *StackExtensions
182+
if e.config.EventSource.StackEnrichment {
183+
stackexts = NewStackExtensions(config.EventSource)
184+
stackexts.EnableThreadpoolCallstack()
185+
}
186+
trace.AddProvider(etw.ThreadpoolGUID, config.EventSource.StackEnrichment, WithStackExts(stackexts))
187+
}
188+
189+
if trace.HasProviders() {
190+
// add security telemetry trace
191+
e.addTrace(trace)
175192
}
176193

177194
for _, trace := range e.traces {
@@ -305,6 +322,6 @@ func (e *EventSource) RegisterEventListener(lis event.Listener) {
305322
e.listeners = append(e.listeners, lis)
306323
}
307324

308-
func (e *EventSource) addTrace(name string, guid windows.GUID) {
309-
e.traces = append(e.traces, NewTrace(name, guid, 0x0, e.config))
325+
func (e *EventSource) addTrace(trace *Trace) {
326+
e.traces = append(e.traces, trace)
310327
}

internal/etw/source_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func TestEventSourceStartTraces(t *testing.T) {
101101
1,
102102
[]etw.EventTraceFlags{0x6018203, 0},
103103
},
104-
{"start kernel logger and audit api sessions",
104+
{"start kernel and security telemetry logger sessions",
105105
&config.Config{
106106
EventSource: config.EventSourceConfig{
107107
EnableThreadEvents: true,

internal/etw/stackext.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ func NewStackExtensions(config config.EventSourceConfig) *StackExtensions {
3838
}
3939

4040
// AddStackTracing enables stack tracing for the specified event type.
41-
func (s *StackExtensions) AddStackTracing(Type event.Type) {
42-
if !s.config.TestDropMask(Type) {
43-
s.ids = append(s.ids, etw.NewClassicEventID(Type.GUID(), Type.HookID()))
41+
func (s *StackExtensions) AddStackTracing(typ event.Type) {
42+
if !s.config.TestDropMask(typ) {
43+
s.ids = append(s.ids, etw.NewClassicEventID(typ.GUID(), typ.HookID()))
4444
}
4545
}
4646

@@ -54,6 +54,9 @@ func (s *StackExtensions) AddStackTracingWith(guid windows.GUID, hookID uint16)
5454
// EventIds returns all event types eligible for stack tracing.
5555
func (s *StackExtensions) EventIds() []etw.ClassicEventID { return s.ids }
5656

57+
// Empty determines if this stack extensions has registered event identifiers.
58+
func (s *StackExtensions) Empty() bool { return len(s.ids) == 0 }
59+
5760
// EnableProcessCallstack populates the stack identifiers
5861
// with event types eligible for emitting stack walk events
5962
// related to process telemetry, such as creating a process,

internal/etw/trace.go

Lines changed: 123 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,10 @@ func initEventTraceProps(c config.EventSourceConfig) etw.EventTraceProperties {
7272
}
7373
}
7474

75-
// Trace is the essential building block for controlling
76-
// trace sessions and configuring event consumers. Such
77-
// operations include starting, stopping, and flushing
78-
// trace sessions, and opening the trace for processing
79-
// and event consumption.
80-
type Trace struct {
81-
// Name represents the unique tracing session name.
82-
Name string
75+
// ProviderInfo describes ETW provider metadata.
76+
type ProviderInfo struct {
8377
// GUID is the globally unique identifier for the
84-
// ETW provider.
78+
// ETW provider for which the session is started.
8579
GUID windows.GUID
8680
// Keywords is the bitmask of keywords that determine
8781
// the categories of events for the provider to emit.
@@ -91,11 +85,42 @@ type Trace struct {
9185
// for providers that are enabled via etw.EnableProvider
9286
// API.
9387
Keywords uint64
94-
88+
// EnableStacks indicates if callstacks are enabled for
89+
// this provider.
90+
EnableStacks bool
9591
// stackExtensions manager stack tracing enablement.
9692
// For each event present in the stack identifiers,
9793
// the StackWalk event is published by the provider.
9894
stackExtensions *StackExtensions
95+
}
96+
97+
func (p *ProviderInfo) HasStackExtensions() bool {
98+
return p.stackExtensions != nil && !p.stackExtensions.Empty()
99+
}
100+
101+
// Trace is the essential building block for controlling
102+
// trace sessions and configuring event consumers. Such
103+
// operations include starting, stopping, and flushing
104+
// trace sessions, and opening the trace for processing
105+
// and event consumption. Trace can be configured to
106+
// operate a single ETW provider, or it can act as a
107+
// container for multiple provider sessions.
108+
type Trace struct {
109+
// Name represents the unique tracing session name.
110+
Name string
111+
// GUID is the globally unique identifier for the
112+
// ETW provider for which the session is started.
113+
GUID windows.GUID
114+
115+
// Providers is the list of providers to be run inside
116+
// the tracing session. For each provider, the GUID,
117+
// keywords and other parameters can be specified.
118+
Providers []ProviderInfo
119+
120+
// stackExtensions manages stack tracing enablement.
121+
// For each event present in the stack identifiers,
122+
// the StackWalk event is published by the provider.
123+
stackExtensions *StackExtensions
99124

100125
// startHandle is the session handle returned by the
101126
// etw.StartTrace function. This handle is
@@ -120,13 +145,68 @@ type Trace struct {
120145
errs chan error
121146
}
122147

123-
// NewTrace creates a new trace with specified name, provider GUID, and keywords.
124-
func NewTrace(name string, guid windows.GUID, keywords uint64, config *config.Config) *Trace {
125-
t := &Trace{Name: name, GUID: guid, Keywords: keywords, stackExtensions: NewStackExtensions(config.EventSource), config: config}
148+
type opts struct {
149+
stackexts *StackExtensions
150+
keywords uint64
151+
}
152+
153+
// Option represents the option for the trace.
154+
type Option func(o *opts)
155+
156+
// WithStackExts sets the stack extensions.
157+
func WithStackExts(stackexts *StackExtensions) Option {
158+
return func(o *opts) {
159+
o.stackexts = stackexts
160+
}
161+
}
162+
163+
// WithKeywords sets the bitmask of keywords that determine
164+
// the categories of events for the provider to emit.
165+
func WithKeywords(keywords uint64) Option {
166+
return func(o *opts) {
167+
o.keywords = keywords
168+
}
169+
}
170+
171+
// NewKernelTrace creates a new NT Kernel Logger trace.
172+
func NewKernelTrace(config *config.Config) *Trace {
173+
t := &Trace{Name: etw.KernelLoggerSession, GUID: etw.KernelTraceControlGUID, stackExtensions: NewStackExtensions(config.EventSource), config: config}
126174
t.enableCallstacks()
127175
return t
128176
}
129177

178+
// NewTrace creates a new trace that can host various ETW provider sessions.
179+
// The providers to be run inside the session can be given in the last argument
180+
// or added by the AddProvider method.
181+
func NewTrace(name string, config *config.Config, providers ...ProviderInfo) *Trace {
182+
t := &Trace{Name: name, config: config, Providers: make([]ProviderInfo, 0)}
183+
t.Providers = providers
184+
return t
185+
}
186+
187+
// AddProvider adds a new provider to the multi trace session
188+
// with optional parameters that influence the provider.
189+
func (t *Trace) AddProvider(guid windows.GUID, enableStacks bool, options ...Option) {
190+
var opts opts
191+
192+
for _, opt := range options {
193+
opt(&opts)
194+
}
195+
196+
t.Providers = append(t.Providers, ProviderInfo{GUID: guid, Keywords: opts.keywords, EnableStacks: enableStacks, stackExtensions: opts.stackexts})
197+
}
198+
199+
// HasProviders determines if this trace contains providers.
200+
func (t *Trace) HasProviders() bool { return len(t.Providers) > 0 }
201+
202+
// IsGUIDEmpty determines if the provider GUID is empty.
203+
func (t *Trace) IsGUIDEmpty() bool {
204+
return t.GUID.Data1 == 0 &&
205+
t.GUID.Data2 == 0 &&
206+
t.GUID.Data3 == 0 &&
207+
t.GUID.Data4 == [8]byte{}
208+
}
209+
130210
func (t *Trace) enableCallstacks() {
131211
if t.IsKernelTrace() {
132212
t.stackExtensions.EnableProcessCallstack()
@@ -137,10 +217,6 @@ func (t *Trace) enableCallstacks() {
137217

138218
t.stackExtensions.EnableMemoryCallstack()
139219
}
140-
141-
if t.IsThreadpoolTrace() {
142-
t.stackExtensions.EnableThreadpoolCallstack()
143-
}
144220
}
145221

146222
// Start registers and starts an event tracing session.
@@ -151,6 +227,11 @@ func (t *Trace) Start() error {
151227
if len(t.Name) > maxLoggerNameSize {
152228
return fmt.Errorf("trace name [%s] is too long", t.Name)
153229
}
230+
231+
if !t.IsGUIDEmpty() && t.HasProviders() {
232+
return fmt.Errorf("%s trace has the root GUID set but providers are not empty", t.Name)
233+
}
234+
154235
cfg := t.config.EventSource
155236
props := initEventTraceProps(cfg)
156237
flags := t.enableFlagsDynamically(cfg)
@@ -212,21 +293,34 @@ func (t *Trace) Start() error {
212293
return etw.SetTraceSystemFlags(handle, sysTraceFlags)
213294
}
214295

215-
// if we're starting a trace for non-system logger, the call
216-
// to etw.EnableTrace is needed to configure how an ETW provider
217-
// publishes events to the trace session. For instance, if stack
218-
// enrichment is enabled, it is necessary to instruct the provider
219-
// to emit stack addresses in the extended data item section when
220-
// writing events to the session buffers
221-
if cfg.StackEnrichment && !t.IsThreadpoolTrace() {
222-
return etw.EnableTraceWithOpts(t.GUID, t.startHandle, t.Keywords, etw.EnableTraceOpts{WithStacktrace: true})
223-
} else if cfg.StackEnrichment && len(t.stackExtensions.EventIds()) > 0 {
224-
if err := etw.EnableStackTracing(t.startHandle, t.stackExtensions.EventIds()); err != nil {
225-
return fmt.Errorf("fail to enable system events callstack tracing: %v", err)
296+
// For each provider in multi trace, the call to etw.EnableTrace is
297+
// needed to configure how an ETW provider publishes events to the
298+
// trace session.
299+
// For instance, if stack enrichment is enabled, it is necessary to
300+
// instruct the provider to emit stack addresses in the extended
301+
// data item section when writing events to the session buffers
302+
for _, provider := range t.Providers {
303+
switch {
304+
case provider.EnableStacks && provider.HasStackExtensions():
305+
if err := etw.EnableStackTracing(t.startHandle, provider.stackExtensions.EventIds()); err != nil {
306+
return fmt.Errorf("fail to enable provider callstack tracing: %v", err)
307+
}
308+
if err := etw.EnableTrace(provider.GUID, t.startHandle, provider.Keywords); err != nil {
309+
return err
310+
}
311+
case provider.EnableStacks:
312+
opts := etw.EnableTraceOpts{WithStacktrace: true}
313+
if err := etw.EnableTraceWithOpts(provider.GUID, t.startHandle, provider.Keywords, opts); err != nil {
314+
return err
315+
}
316+
default:
317+
if err := etw.EnableTrace(provider.GUID, t.startHandle, provider.Keywords); err != nil {
318+
return err
319+
}
226320
}
227321
}
228322

229-
return etw.EnableTrace(t.GUID, t.startHandle, t.Keywords)
323+
return nil
230324
}
231325

232326
// IsStarted indicates if the trace is started successfully.
@@ -317,9 +411,6 @@ func (t *Trace) Close() error {
317411
// IsKernelTrace determines if this is the system logger trace.
318412
func (t *Trace) IsKernelTrace() bool { return t.GUID == etw.KernelTraceControlGUID }
319413

320-
// IsThreadpoolTrace determines if this is the thread pool logger trace.
321-
func (t *Trace) IsThreadpoolTrace() bool { return t.GUID == etw.ThreadpoolGUID }
322-
323414
// enableFlagsDynamically crafts the system logger event mask
324415
// depending on the compiled rules result or the config state.
325416
// System logger flags is a bitmask that indicates which kernel events

internal/etw/trace_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package etw
2020

2121
import (
2222
"github.com/rabbitstack/fibratus/pkg/config"
23-
"github.com/rabbitstack/fibratus/pkg/sys/etw"
2423
"github.com/stretchr/testify/require"
2524
"testing"
2625
"time"
@@ -37,7 +36,7 @@ func TestStartTrace(t *testing.T) {
3736
},
3837
}
3938

40-
trace := NewTrace(etw.KernelLoggerSession, etw.KernelTraceControlGUID, 0, cfg)
39+
trace := NewKernelTrace(cfg)
4140
require.NoError(t, trace.Start())
4241
require.True(t, trace.IsStarted())
4342
defer trace.Stop()

pkg/event/types_windows.go

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,10 @@ type Source uint8
3535
const (
3636
// SystemLogger event is emitted by the system provider
3737
SystemLogger Source = iota
38-
// AuditAPICallsLogger event is emitted by Audit API calls provider
39-
AuditAPICallsLogger
40-
// DNSLogger event is emitted by DNS provider
41-
DNSLogger
42-
// ThreadpoolLogger event is emitted by thread pool provider
43-
ThreadpoolLogger
38+
// SecurityTelemetryLogger event is emitted by the combination of multiple providers.
39+
// Most notably, DNS, thread pool, and kernel audit API providers are in charge of
40+
// publishing the events.
41+
SecurityTelemetryLogger
4442
)
4543

4644
// Type identifies an event type. It comprises the event GUID + hook ID to uniquely identify the event
@@ -578,27 +576,15 @@ func (t *Type) HookID() uint16 {
578576
// Source designates the provenance of this event type.
579577
func (t Type) Source() Source {
580578
switch t {
581-
case OpenProcess, OpenThread, SetThreadContext, CreateSymbolicLinkObject:
582-
return AuditAPICallsLogger
583-
case QueryDNS, ReplyDNS:
584-
return DNSLogger
585-
case SubmitThreadpoolWork, SubmitThreadpoolCallback, SetThreadpoolTimer:
586-
return ThreadpoolLogger
579+
case OpenProcess, OpenThread, SetThreadContext, CreateSymbolicLinkObject,
580+
QueryDNS, ReplyDNS, SubmitThreadpoolWork, SubmitThreadpoolCallback,
581+
SetThreadpoolTimer:
582+
return SecurityTelemetryLogger
587583
default:
588584
return SystemLogger
589585
}
590586
}
591587

592-
// CanArriveOutOfOrder indicates if the event can be
593-
// emitted by the provider in out-of-order fashion, i.e.
594-
// its timestamp is perfectly aligned in relation to other
595-
// events, but it appears first on the consumer callback
596-
// before other events published before it.
597-
func (t Type) CanArriveOutOfOrder() bool {
598-
return t.Category() == Threadpool || t.Subcategory() == DNS ||
599-
t == OpenProcess || t == OpenThread || t == SetThreadContext || t == CreateSymbolicLinkObject
600-
}
601-
602588
// TypeFromParts builds the event type from provider GUID and hook ID.
603589
func TypeFromParts(g windows.GUID, id uint16) Type { return pack(g, id) }
604590

pkg/event/types_windows_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,3 @@ func TestGUIDAndHookIDFromEventType(t *testing.T) {
128128
})
129129
}
130130
}
131-
132-
func TestCanArriveOutOfOrder(t *testing.T) {
133-
assert.False(t, RegSetValue.CanArriveOutOfOrder())
134-
assert.False(t, VirtualAlloc.CanArriveOutOfOrder())
135-
assert.True(t, OpenProcess.CanArriveOutOfOrder())
136-
}

0 commit comments

Comments
 (0)