Skip to content

Commit 47564c2

Browse files
committed
feat(filter): Thread pool filter fields
Provides the implementation of the thread pool field accessor.
1 parent 97e1faa commit 47564c2

File tree

6 files changed

+221
-41
lines changed

6 files changed

+221
-41
lines changed

pkg/filter/accessor.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,18 @@ func (k *kevtAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error)
145145
// referenced in the bound field.
146146
func (f *filter) narrowAccessors() {
147147
var (
148-
removeKevtAccessor = true
149-
removePsAccessor = true
150-
removeThreadAccessor = true
151-
removeImageAccessor = true
152-
removeFileAccessor = true
153-
removeRegistryAccessor = true
154-
removeNetworkAccessor = true
155-
removeHandleAccessor = true
156-
removePEAccessor = true
157-
removeMemAccessor = true
158-
removeDNSAccessor = true
148+
removeKevtAccessor = true
149+
removePsAccessor = true
150+
removeThreadAccessor = true
151+
removeImageAccessor = true
152+
removeFileAccessor = true
153+
removeRegistryAccessor = true
154+
removeNetworkAccessor = true
155+
removeHandleAccessor = true
156+
removePEAccessor = true
157+
removeMemAccessor = true
158+
removeDNSAccessor = true
159+
removeThreadpoolAccessor = true
159160
)
160161

161162
for _, field := range f.fields {
@@ -182,6 +183,8 @@ func (f *filter) narrowAccessors() {
182183
removeMemAccessor = false
183184
case field.Name.IsDNSField():
184185
removeDNSAccessor = false
186+
case field.Name.IsThreadpoolField():
187+
removeThreadpoolAccessor = false
185188
}
186189
}
187190

@@ -218,6 +221,9 @@ func (f *filter) narrowAccessors() {
218221
if removeDNSAccessor {
219222
f.removeAccessor(&dnsAccessor{})
220223
}
224+
if removeThreadpoolAccessor {
225+
f.removeAccessor(&threadpoolAccessor{})
226+
}
221227

222228
for _, accessor := range f.accessors {
223229
accessor.SetFields(f.fields)

pkg/filter/accessor_windows.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func GetAccessors() []Accessor {
6565
newHandleAccessor(),
6666
newNetworkAccessor(),
6767
newRegistryAccessor(),
68+
newThreadpoolAccessor(),
6869
}
6970
}
7071

@@ -1333,3 +1334,55 @@ func (*dnsAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) {
13331334

13341335
return nil, nil
13351336
}
1337+
1338+
// threadpoolAccessor extracts values from thread pool events
1339+
type threadpoolAccessor struct{}
1340+
1341+
func (threadpoolAccessor) SetFields([]Field) {}
1342+
func (threadpoolAccessor) SetSegments([]fields.Segment) {}
1343+
func (threadpoolAccessor) IsFieldAccessible(e *kevent.Kevent) bool {
1344+
return e.Category == ktypes.Threadpool
1345+
}
1346+
1347+
func newThreadpoolAccessor() Accessor {
1348+
return &threadpoolAccessor{}
1349+
}
1350+
1351+
func (*threadpoolAccessor) Get(f Field, e *kevent.Kevent) (kparams.Value, error) {
1352+
switch f.Name {
1353+
case fields.ThreadpoolPoolID:
1354+
return e.GetParamAsString(kparams.ThreadpoolPoolID), nil
1355+
case fields.ThreadpoolTaskID:
1356+
return e.GetParamAsString(kparams.ThreadpoolTaskID), nil
1357+
case fields.ThreadpoolCallbackAddress:
1358+
return e.GetParamAsString(kparams.ThreadpoolCallback), nil
1359+
case fields.ThreadpoolCallbackSymbol:
1360+
return e.GetParamAsString(kparams.ThreadpoolCallbackSymbol), nil
1361+
case fields.ThreadpoolCallbackModule:
1362+
return e.GetParamAsString(kparams.ThreadpoolCallbackModule), nil
1363+
case fields.ThreadpoolCallbackContext:
1364+
return e.GetParamAsString(kparams.ThreadpoolContext), nil
1365+
case fields.ThreadpoolCallbackContextRip:
1366+
return e.GetParamAsString(kparams.ThreadpoolContextRip), nil
1367+
case fields.ThreadpoolCallbackContextRipSymbol:
1368+
return e.GetParamAsString(kparams.ThreadpoolContextRipSymbol), nil
1369+
case fields.ThreadpoolCallbackContextRipModule:
1370+
return e.GetParamAsString(kparams.ThreadpoolContextRipModule), nil
1371+
case fields.ThreadpoolSubprocessTag:
1372+
return e.GetParamAsString(kparams.ThreadpoolSubprocessTag), nil
1373+
case fields.ThreadpoolTimer:
1374+
return e.GetParamAsString(kparams.ThreadpoolTimer), nil
1375+
case fields.ThreadpoolTimerSubqueue:
1376+
return e.GetParamAsString(kparams.ThreadpoolTimerSubqueue), nil
1377+
case fields.ThreadpoolTimerDuetime:
1378+
return e.Kparams.GetUint64(kparams.ThreadpoolTimerDuetime)
1379+
case fields.ThreadpoolTimerPeriod:
1380+
return e.Kparams.GetUint32(kparams.ThreadpoolTimerPeriod)
1381+
case fields.ThreadpoolTimerWindow:
1382+
return e.Kparams.GetUint32(kparams.ThreadpoolTimerWindow)
1383+
case fields.ThreadpoolTimerAbsolute:
1384+
return e.Kparams.GetBool(kparams.ThreadpoolTimerAbsolute)
1385+
}
1386+
1387+
return nil, nil
1388+
}

pkg/filter/fields/fields_windows.go

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -508,22 +508,56 @@ const (
508508
DNSAnswers Field = "dns.answers"
509509
// DNSRcode identifies the field that represents the DNS response code
510510
DNSRcode Field = "dns.rcode"
511+
512+
// ThreadpoolPoolID identifies the field that represents the thread pool identifier
513+
ThreadpoolPoolID = "threadpool.id"
514+
// ThreadpoolTaskID identifies the field that represents the thread pool task identifier
515+
ThreadpoolTaskID = "threadpool.task.id"
516+
// ThreadpoolCallbackAddress identifies the field that represents the address of the callback function
517+
ThreadpoolCallbackAddress = "threadpool.callback.address"
518+
// ThreadpoolCallbackSymbol identifies the field that represents the callback symbol
519+
ThreadpoolCallbackSymbol = "threadpool.callback.symbol"
520+
// ThreadpoolCallbackModule identifies the field that represents the module containing the callback symbol
521+
ThreadpoolCallbackModule = "threadpool.callback.module"
522+
// ThreadpoolCallbackContext identifies the field that represents the address of the callback context
523+
ThreadpoolCallbackContext = "threadpool.callback.context"
524+
// ThreadpoolCallbackContextRip identifies the field that represents the value of instruction pointer contained in the callback context
525+
ThreadpoolCallbackContextRip = "threadpool.callback.context.rip"
526+
// ThreadpoolCallbackContextRipSymbol identifies the field that represents the symbol name associated with the instruction pointer in callback context
527+
ThreadpoolCallbackContextRipSymbol = "threadpool.callback.context.rip.symbol"
528+
// ThreadpoolCallbackContextRipModule identifies the field that represents the module name associated with the instruction pointer in callback context
529+
ThreadpoolCallbackContextRipModule = "threadpool.callback.context.rip.module"
530+
// ThreadpoolSubprocessTag identifies the field that represents the service identifier associated with the thread pool
531+
ThreadpoolSubprocessTag = "threadpool.subprocess_tag"
532+
// ThreadpoolTimerDuetime identifies the field that represents the timer due time
533+
ThreadpoolTimerDuetime = "threadpool.timer.duetime"
534+
// ThreadpoolTimerSubqueue identifies the field that represents the memory address of the timer subqueue
535+
ThreadpoolTimerSubqueue = "threadpool.timer.subqueue"
536+
// ThreadpoolTimer identifies the field that represents the memory address of the timer object
537+
ThreadpoolTimer = "threadpool.timer.address"
538+
// ThreadpoolTimerPeriod identifies the field that represents the period of the timer
539+
ThreadpoolTimerPeriod = "threadpool.timer.period"
540+
// ThreadpoolTimerWindow identifies the field that represents the timer tolerate period
541+
ThreadpoolTimerWindow = "threadpool.timer.window"
542+
// ThreadpoolTimerAbsolute identifies the field that indicates if the timer is absolute or relative
543+
ThreadpoolTimerAbsolute = "threadpool.timer.is_absolute"
511544
)
512545

513546
// String casts the field type to string.
514547
func (f Field) String() string { return string(f) }
515548

516-
func (f Field) IsPsField() bool { return strings.HasPrefix(string(f), "ps.") }
517-
func (f Field) IsKevtField() bool { return strings.HasPrefix(string(f), "kevt.") }
518-
func (f Field) IsThreadField() bool { return strings.HasPrefix(string(f), "thread.") }
519-
func (f Field) IsImageField() bool { return strings.HasPrefix(string(f), "image.") }
520-
func (f Field) IsFileField() bool { return strings.HasPrefix(string(f), "file.") }
521-
func (f Field) IsRegistryField() bool { return strings.HasPrefix(string(f), "registry.") }
522-
func (f Field) IsNetworkField() bool { return strings.HasPrefix(string(f), "net.") }
523-
func (f Field) IsHandleField() bool { return strings.HasPrefix(string(f), "handle.") }
524-
func (f Field) IsPeField() bool { return strings.HasPrefix(string(f), "pe.") || f == PsChildPeFilename }
525-
func (f Field) IsMemField() bool { return strings.HasPrefix(string(f), "mem.") }
526-
func (f Field) IsDNSField() bool { return strings.HasPrefix(string(f), "dns.") }
549+
func (f Field) IsPsField() bool { return strings.HasPrefix(string(f), "ps.") }
550+
func (f Field) IsKevtField() bool { return strings.HasPrefix(string(f), "kevt.") }
551+
func (f Field) IsThreadField() bool { return strings.HasPrefix(string(f), "thread.") }
552+
func (f Field) IsImageField() bool { return strings.HasPrefix(string(f), "image.") }
553+
func (f Field) IsFileField() bool { return strings.HasPrefix(string(f), "file.") }
554+
func (f Field) IsRegistryField() bool { return strings.HasPrefix(string(f), "registry.") }
555+
func (f Field) IsNetworkField() bool { return strings.HasPrefix(string(f), "net.") }
556+
func (f Field) IsHandleField() bool { return strings.HasPrefix(string(f), "handle.") }
557+
func (f Field) IsPeField() bool { return strings.HasPrefix(string(f), "pe.") || f == PsChildPeFilename }
558+
func (f Field) IsMemField() bool { return strings.HasPrefix(string(f), "mem.") }
559+
func (f Field) IsDNSField() bool { return strings.HasPrefix(string(f), "dns.") }
560+
func (f Field) IsThreadpoolField() bool { return strings.HasPrefix(string(f), "threadpool.") }
527561

528562
func (f Field) IsPeSection() bool { return f == PeNumSections }
529563
func (f Field) IsPeSymbol() bool { return f == PeSymbols || f == PeNumSymbols || f == PeImports }
@@ -966,6 +1000,23 @@ var fields = map[Field]FieldInfo{
9661000
DNSOptions: {DNSOptions, "dns query options", kparams.Flags64, []string{"dns.options in ('ADDRCONFIG', 'DUAL_ADDR')"}, nil, nil},
9671001
DNSRcode: {DNSRR, "dns response status", kparams.AnsiString, []string{"dns.rcode = 'NXDOMAIN'"}, nil, nil},
9681002
DNSAnswers: {DNSAnswers, "dns response answers", kparams.Slice, []string{"dns.answers in ('o.lencr.edgesuite.net', 'a1887.dscq.akamai.net')"}, nil, nil},
1003+
1004+
ThreadpoolPoolID: {ThreadpoolPoolID, "thread pool identifier", kparams.Address, []string{"threadpool.id = '20f5fc02440'"}, nil, nil},
1005+
ThreadpoolTaskID: {ThreadpoolTaskID, "thread pool task identifier", kparams.Address, []string{"threadpool.task.id = '20f7ecd21f8'"}, nil, nil},
1006+
ThreadpoolCallbackAddress: {ThreadpoolCallbackAddress, "thread pool callback address", kparams.Address, []string{"threadpool.callback.address = '7ff868739ed0'"}, nil, nil},
1007+
ThreadpoolCallbackSymbol: {ThreadpoolCallbackSymbol, "thread pool callback symbol", kparams.UnicodeString, []string{"threadpool.callback.symbol = 'RtlDestroyQueryDebugBuffer'"}, nil, nil},
1008+
ThreadpoolCallbackModule: {ThreadpoolCallbackModule, "thread pool module containing the callback symbol", kparams.UnicodeString, []string{"threadpool.callback.module contains 'ntdll.dll'"}, nil, nil},
1009+
ThreadpoolCallbackContext: {ThreadpoolCallbackContext, "thread pool callback context address", kparams.Address, []string{"threadpool.callback.context = '1df41e07bd0'"}, nil, nil},
1010+
ThreadpoolCallbackContextRip: {ThreadpoolCallbackContextRip, "thread pool callback thread context instruction pointer", kparams.Address, []string{"threadpool.callback.context.rip = '1df42ffc1f8'"}, nil, nil},
1011+
ThreadpoolCallbackContextRipSymbol: {ThreadpoolCallbackContextRipSymbol, "thread pool callback thread context instruction pointer symbol", kparams.UnicodeString, []string{"threadpool.callback.context.rip.symbol = 'VirtualProtect'"}, nil, nil},
1012+
ThreadpoolCallbackContextRipModule: {ThreadpoolCallbackContextRipModule, "thread pool callback thread context instruction pointer symbol module", kparams.UnicodeString, []string{"threadpool.callback.context.rip.module contains 'ntdll.dll'"}, nil, nil},
1013+
ThreadpoolSubprocessTag: {ThreadpoolSubprocessTag, "thread pool service identifier", kparams.Address, []string{"threadpool.subprocess_tag = '10d'"}, nil, nil},
1014+
ThreadpoolTimerDuetime: {ThreadpoolTimerDuetime, "thread pool timer due time", kparams.Uint64, []string{"threadpool.timer.duetime > 10"}, nil, nil},
1015+
ThreadpoolTimerSubqueue: {ThreadpoolTimerSubqueue, "thread pool timer subqueue address", kparams.Address, []string{"threadpool.timer.subqueue = '1db401703e8'"}, nil, nil},
1016+
ThreadpoolTimer: {ThreadpoolTimer, "thread pool timer address", kparams.Address, []string{"threadpool.timer.address = '3e8'"}, nil, nil},
1017+
ThreadpoolTimerPeriod: {ThreadpoolTimerPeriod, "thread pool timer period", kparams.Uint32, []string{"threadpool.timer.period = 0'"}, nil, nil},
1018+
ThreadpoolTimerWindow: {ThreadpoolTimerWindow, "thread pool timer tolerate period", kparams.Uint32, []string{"threadpool.timer.window = 0'"}, nil, nil},
1019+
ThreadpoolTimerAbsolute: {ThreadpoolTimerAbsolute, "indicates if the thread pool timer is absolute or relative", kparams.Bool, []string{"threadpool.timer.is_absolute = true'"}, nil, nil},
9691020
}
9701021

9711022
// ArgumentOf returns argument data for the specified field.

pkg/filter/filter_test.go

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ import (
4747

4848
var cfg = &config.Config{
4949
Kstream: config.KstreamConfig{
50-
EnableHandleKevents: true,
51-
EnableNetKevents: true,
52-
EnableRegistryKevents: true,
53-
EnableFileIOKevents: true,
54-
EnableImageKevents: true,
55-
EnableThreadKevents: true,
56-
EnableMemKevents: true,
57-
EnableDNSEvents: true,
50+
EnableHandleKevents: true,
51+
EnableNetKevents: true,
52+
EnableRegistryKevents: true,
53+
EnableFileIOKevents: true,
54+
EnableImageKevents: true,
55+
EnableThreadKevents: true,
56+
EnableMemKevents: true,
57+
EnableDNSEvents: true,
58+
EnableThreadpoolEvents: true,
5859
},
5960
Filters: &config.Filters{},
6061
PE: pe.Config{Enabled: true},
@@ -1221,6 +1222,71 @@ func TestDNSFilter(t *testing.T) {
12211222
}
12221223
}
12231224

1225+
func TestThreadpoolFilter(t *testing.T) {
1226+
e := &kevent.Kevent{
1227+
Type: ktypes.SubmitThreadpoolCallback,
1228+
Tid: 2484,
1229+
PID: 1023,
1230+
CPU: 1,
1231+
Seq: 2,
1232+
Name: "SubmitThreadpoolCallback",
1233+
Timestamp: time.Now(),
1234+
Category: ktypes.Threadpool,
1235+
Kparams: kevent.Kparams{
1236+
kparams.ThreadpoolPoolID: {Name: kparams.ThreadpoolPoolID, Type: kparams.Address, Value: uint64(0x20f5fc02440)},
1237+
kparams.ThreadpoolTaskID: {Name: kparams.ThreadpoolTaskID, Type: kparams.Address, Value: uint64(0x20f7ecd21f8)},
1238+
kparams.ThreadpoolCallback: {Name: kparams.ThreadpoolCallback, Type: kparams.Address, Value: uint64(0x7ffb3138592e)},
1239+
kparams.ThreadpoolContext: {Name: kparams.ThreadpoolContext, Type: kparams.Address, Value: uint64(0x14d0d16fed8)},
1240+
kparams.ThreadpoolContextRip: {Name: kparams.ThreadpoolContextRip, Type: kparams.Address, Value: uint64(0x143c9b07bd0)},
1241+
kparams.ThreadpoolSubprocessTag: {Name: kparams.ThreadpoolSubprocessTag, Type: kparams.Address, Value: uint64(0x10d)},
1242+
kparams.ThreadpoolContextRipSymbol: {Name: kparams.ThreadpoolContextRipSymbol, Type: kparams.UnicodeString, Value: "VirtualProtect"},
1243+
kparams.ThreadpoolContextRipModule: {Name: kparams.ThreadpoolContextRipModule, Type: kparams.UnicodeString, Value: "C:\\Windows\\System32\\kernelbase.dll"},
1244+
kparams.ThreadpoolCallbackSymbol: {Name: kparams.ThreadpoolCallbackSymbol, Type: kparams.UnicodeString, Value: "RtlDestroyQueryDebugBuffer"},
1245+
kparams.ThreadpoolCallbackModule: {Name: kparams.ThreadpoolCallbackModule, Type: kparams.UnicodeString, Value: "C:\\Windows\\System32\\ntdll.dll"},
1246+
kparams.ThreadpoolTimerSubqueue: {Name: kparams.ThreadpoolTimerSubqueue, Type: kparams.Address, Value: uint64(0x1db401703e8)},
1247+
kparams.ThreadpoolTimerDuetime: {Name: kparams.ThreadpoolTimerDuetime, Type: kparams.Uint64, Value: uint64(18446744073699551616)},
1248+
kparams.ThreadpoolTimer: {Name: kparams.ThreadpoolTimer, Type: kparams.Address, Value: uint64(0x3e8)},
1249+
kparams.ThreadpoolTimerPeriod: {Name: kparams.ThreadpoolTimerPeriod, Type: kparams.Uint32, Value: uint32(100)},
1250+
kparams.ThreadpoolTimerWindow: {Name: kparams.ThreadpoolTimerWindow, Type: kparams.Uint32, Value: uint32(50)},
1251+
kparams.ThreadpoolTimerAbsolute: {Name: kparams.ThreadpoolTimerAbsolute, Type: kparams.Bool, Value: true},
1252+
},
1253+
}
1254+
1255+
var tests = []struct {
1256+
filter string
1257+
matches bool
1258+
}{
1259+
1260+
{`threadpool.id = '20f5fc02440'`, true},
1261+
{`threadpool.task.id = '20f7ecd21f8'`, true},
1262+
{`threadpool.callback.address = '7ffb3138592e'`, true},
1263+
{`threadpool.callback.symbol = 'RtlDestroyQueryDebugBuffer'`, true},
1264+
{`threadpool.callback.module = 'C:\\Windows\\System32\\ntdll.dll'`, true},
1265+
{`threadpool.callback.context = '14d0d16fed8'`, true},
1266+
{`threadpool.callback.context.rip = '143c9b07bd0'`, true},
1267+
{`threadpool.callback.context.rip.symbol = 'VirtualProtect'`, true},
1268+
{`threadpool.callback.context.rip.module = 'C:\\Windows\\System32\\kernelbase.dll'`, true},
1269+
{`threadpool.timer.address = '3e8'`, true},
1270+
{`threadpool.timer.subqueue = '1db401703e8'`, true},
1271+
{`threadpool.timer.duetime = 18446744073699551616`, true},
1272+
{`threadpool.timer.period = 100`, true},
1273+
{`threadpool.timer.window = 50`, true},
1274+
{`threadpool.timer.is_absolute = true`, true},
1275+
}
1276+
1277+
for i, tt := range tests {
1278+
f := New(tt.filter, cfg)
1279+
err := f.Compile()
1280+
if err != nil {
1281+
t.Fatal(err)
1282+
}
1283+
matches := f.Run(e)
1284+
if matches != tt.matches {
1285+
t.Errorf("%d. %q threadpool filter mismatch: exp=%t got=%t", i, tt.filter, tt.matches, matches)
1286+
}
1287+
}
1288+
}
1289+
12241290
func TestInterpolateFields(t *testing.T) {
12251291
var tests = []struct {
12261292
original string

pkg/filter/filter_windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ func New(expr string, config *config.Config, options ...Option) Filter {
8484
if kconfig.EnableDNSEvents {
8585
accessors = append(accessors, newDNSAccessor())
8686
}
87+
if kconfig.EnableThreadpoolEvents {
88+
accessors = append(accessors, newThreadpoolAccessor())
89+
}
8790

8891
var parser *ql.Parser
8992
if fconfig.HasMacros() {

pkg/filter/ql/function.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -297,17 +297,18 @@ func (f *Foreach) Desc() functions.FunctionDesc {
297297
e := args[2] // expression
298298

299299
var reserved = map[string]bool{ // reserved bound variable names
300-
"$ps": true,
301-
"$pe": true,
302-
"$file": true,
303-
"$image": true,
304-
"$thread": true,
305-
"$registry": true,
306-
"$net": true,
307-
"$mem": true,
308-
"$handle": true,
309-
"$dns": true,
310-
"$kevt": true,
300+
"$ps": true,
301+
"$pe": true,
302+
"$file": true,
303+
"$image": true,
304+
"$thread": true,
305+
"$threadpool": true,
306+
"$registry": true,
307+
"$net": true,
308+
"$mem": true,
309+
"$handle": true,
310+
"$dns": true,
311+
"$kevt": true,
311312
}
312313

313314
if reserved[v] {

0 commit comments

Comments
 (0)