Skip to content

Commit 458925b

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 458925b

10 files changed

Lines changed: 165 additions & 90 deletions

File tree

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, etw.NoKeywords, false)
169172
}
173+
170174
if config.EventSource.EnableAuditAPIEvents {
171-
e.addTrace(etw.KernelAuditAPICallsSession, etw.KernelAuditAPICallsGUID)
175+
trace.AddProvider(etw.KernelAuditAPICallsGUID, etw.NoKeywords, 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, etw.NoKeywords, 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 110 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,59 @@ 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+
}
151+
152+
// Option represents the option for the trace.
153+
type Option func(o *opts)
154+
155+
// WithStackExts sets the stack extensions.
156+
func WithStackExts(stackexts *StackExtensions) Option {
157+
return func(o *opts) {
158+
o.stackexts = stackexts
159+
}
160+
}
161+
162+
// NewKernelTrace creates a new NT Kernel Logger trace.
163+
func NewKernelTrace(config *config.Config) *Trace {
164+
t := &Trace{Name: etw.KernelLoggerSession, GUID: etw.KernelTraceControlGUID, stackExtensions: NewStackExtensions(config.EventSource), config: config}
126165
t.enableCallstacks()
127166
return t
128167
}
129168

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

138209
t.stackExtensions.EnableMemoryCallstack()
139210
}
140-
141-
if t.IsThreadpoolTrace() {
142-
t.stackExtensions.EnableThreadpoolCallstack()
143-
}
144211
}
145212

146213
// Start registers and starts an event tracing session.
@@ -151,6 +218,11 @@ func (t *Trace) Start() error {
151218
if len(t.Name) > maxLoggerNameSize {
152219
return fmt.Errorf("trace name [%s] is too long", t.Name)
153220
}
221+
222+
if !t.IsGUIDEmpty() && t.HasProviders() {
223+
return fmt.Errorf("%s trace has the root GUID set but providers are not empty", t.Name)
224+
}
225+
154226
cfg := t.config.EventSource
155227
props := initEventTraceProps(cfg)
156228
flags := t.enableFlagsDynamically(cfg)
@@ -212,21 +284,30 @@ func (t *Trace) Start() error {
212284
return etw.SetTraceSystemFlags(handle, sysTraceFlags)
213285
}
214286

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)
287+
// For each provider in multi trace, the call to etw.EnableTrace is
288+
// needed to configure how an ETW provider publishes events to the
289+
// trace session.
290+
// For instance, if stack enrichment is enabled, it is necessary to
291+
// instruct the provider to emit stack addresses in the extended
292+
// data item section when writing events to the session buffers
293+
for _, provider := range t.Providers {
294+
switch {
295+
case provider.EnableStacks && provider.HasStackExtensions():
296+
if err := etw.EnableStackTracing(t.startHandle, provider.stackExtensions.EventIds()); err != nil {
297+
return fmt.Errorf("fail to enable provider callstack tracing: %v", err)
298+
}
299+
if err := etw.EnableTrace(provider.GUID, t.startHandle, provider.Keywords); err != nil {
300+
return err
301+
}
302+
case provider.EnableStacks:
303+
opts := etw.EnableTraceOpts{WithStacktrace: true}
304+
if err := etw.EnableTraceWithOpts(provider.GUID, t.startHandle, provider.Keywords, opts); err != nil {
305+
return err
306+
}
226307
}
227308
}
228309

229-
return etw.EnableTrace(t.GUID, t.startHandle, t.Keywords)
310+
return nil
230311
}
231312

232313
// IsStarted indicates if the trace is started successfully.
@@ -317,9 +398,6 @@ func (t *Trace) Close() error {
317398
// IsKernelTrace determines if this is the system logger trace.
318399
func (t *Trace) IsKernelTrace() bool { return t.GUID == etw.KernelTraceControlGUID }
319400

320-
// IsThreadpoolTrace determines if this is the thread pool logger trace.
321-
func (t *Trace) IsThreadpoolTrace() bool { return t.GUID == etw.ThreadpoolGUID }
322-
323401
// enableFlagsDynamically crafts the system logger event mask
324402
// depending on the compiled rules result or the config state.
325403
// 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-
}

pkg/filter/ql/literal.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package ql
2121
import (
2222
"github.com/rabbitstack/fibratus/pkg/event"
2323
"github.com/rabbitstack/fibratus/pkg/filter/fields"
24-
"golang.org/x/sys/windows"
2524
"net"
2625
"reflect"
2726
"strconv"
@@ -379,24 +378,19 @@ func (s Sequence) IsConstrained() bool {
379378
}
380379

381380
func (s *Sequence) init() {
382-
// determine if the sequence references
383-
// an event type that can arrive out-of-order.
384-
// The edge case is for unordered events emitted
385-
// by the same provider where the temporal order
386-
// is guaranteed
387-
guids := make(map[windows.GUID]bool)
381+
// determine if the sequence references an event type
382+
// that can arrive out-of-order. This happens if the
383+
// expressions in the sequence reference event types
384+
// from different event sources
385+
sources := make(map[event.Source]bool)
386+
388387
for _, expr := range s.Expressions {
389388
for _, etype := range expr.types {
390-
if etype.CanArriveOutOfOrder() {
391-
s.IsUnordered = true
392-
}
393-
guids[etype.GUID()] = true
389+
sources[etype.Source()] = true
394390
}
395391
}
396392

397-
if s.IsUnordered && len(guids) == 1 {
398-
s.IsUnordered = false
399-
}
393+
s.IsUnordered = len(sources) > 1
400394
}
401395

402396
func (s Sequence) impairBy() bool {

0 commit comments

Comments
 (0)