From b000910b9479ea8238a0856b56a3cda468048a8e Mon Sep 17 00:00:00 2001 From: Ron Federman Date: Fri, 22 Aug 2025 23:54:34 +0300 Subject: [PATCH] generalaize process details filters and use them for env prefix and exe path filtering in user space --- detector.go | 117 +++++++++++++++++++++++++++++++---------------- detector_test.go | 14 +++--- test/script.sh | 12 +++++ 3 files changed, 96 insertions(+), 47 deletions(-) create mode 100755 test/script.sh diff --git a/detector.go b/detector.go index 79e8dfb..6a87b74 100644 --- a/detector.go +++ b/detector.go @@ -19,19 +19,22 @@ import ( const defaultMinDuration = (1 * time.Second) type Detector struct { - p *probe.Probe - filters []common.ProcessesFilter - l *slog.Logger - procEvents chan common.PIDEvent - output chan<- ProcessEvent - envKeys map[string]struct{} - envPrefixFilter string + p *probe.Probe + filters []common.ProcessesFilter + l *slog.Logger + procEvents chan common.PIDEvent + output chan<- ProcessEvent + envKeys map[string]struct{} + envPrefixFilter string + exePathsToFilter map[string]struct{} // before the detector passes the process to the output channel, - // it will filter out processes that do not have the environment variables with the specified prefix. - // this map will be used to avoid sending exit events for processes that do not have the prefix. - pidsFilteredByEnvPrefix map[int]struct{} - exePathsToFilter map[string]struct{} + // it will filter out processes based on their details, using the functions in this slice. + // if one of the functions returns true, the process will be filtered out. + detailsFilters []detailsFilterFn + // filteredPIDs is a map of PIDs that were filtered out based on their details, + // used to filter out their exit events. + filteredPIDs map[int]struct{} } type ProcessEventType int @@ -47,6 +50,11 @@ func (pe ProcessEventType) String() string { return common.EventType(pe).String() } +type detailsFilterFn struct { + fn func(int, *ProcessExecDetails) bool + msg string +} + type ProcessEvent struct { // EventType is the type of the process event EventType ProcessEventType @@ -157,16 +165,41 @@ func NewDetector(output chan<- ProcessEvent, opts ...DetectorOption) (*Detector, filters := []common.ProcessesFilter{durationFilter} + envFilterFn := func(pid int, details *ProcessExecDetails) bool { + if c.envPrefixFilter == "" { + return false + } + for k := range details.Environments { + if strings.HasPrefix(k, c.envPrefixFilter) { + return false + } + } + return true + } + + exePathFilterFn := func(pid int, details *ProcessExecDetails) bool { + for p := range c.exePathsToFilter { + if details.ExePath == p { + return true + } + } + return false + } + d := &Detector{ - p: p, - filters: filters, - l: c.logger, - procEvents: procEvents, - output: output, - envKeys: c.envs, - envPrefixFilter: c.envPrefixFilter, - exePathsToFilter: c.exePathsToFilter, - pidsFilteredByEnvPrefix: make(map[int]struct{}), + p: p, + filters: filters, + l: c.logger, + procEvents: procEvents, + output: output, + envKeys: c.envs, + envPrefixFilter: c.envPrefixFilter, + exePathsToFilter: c.exePathsToFilter, + detailsFilters: []detailsFilterFn{ + {fn: envFilterFn, msg: "no env prefix was found in process envs"}, + {fn: exePathFilterFn, msg: "process exe path is in the excluded list"}, + }, + filteredPIDs: make(map[int]struct{}), } return d, nil @@ -205,6 +238,11 @@ func (d *Detector) processExecDetails(pid int) (*ProcessExecDetails, error) { } func (d *Detector) procEventLoop() { + var ( + filtered bool + filterMsg string + ) + for e := range d.procEvents { pe := ProcessEvent{PID: e.Pid} switch e.Type { @@ -215,34 +253,33 @@ func (d *Detector) procEventLoop() { continue } - // make sure the env prefix is present before sending the event - if d.envPrefixFilter != "" { - foundEnvPrefix := false - for k := range execDetails.Environments { - if strings.HasPrefix(k, d.envPrefixFilter) { - foundEnvPrefix = true - break - } - } - if !foundEnvPrefix { - d.l.Warn("skipping process event due to env prefix not present", - "pid", e.Pid, - "envPrefixFilter", d.envPrefixFilter, - "cmdLine", execDetails.CmdLine, - "exePath", execDetails.ExePath, - ) - d.pidsFilteredByEnvPrefix[e.Pid] = struct{}{} - continue + filtered = false + filterMsg = "" + for _, f := range d.detailsFilters { + if f.fn(e.Pid, execDetails) { + d.filteredPIDs[e.Pid] = struct{}{} + filtered = true + filterMsg = f.msg } } + if filtered { + d.l.Warn("skipping process event due to details filter", + "pid", e.Pid, + "reason", filterMsg, + "cmdLine", execDetails.CmdLine, + "exePath", execDetails.ExePath, + ) + continue + } + pe.ExecDetails = execDetails pe.EventType = ProcessEventType(e.Type) d.output <- pe case common.EventTypeExit: - if _, ok := d.pidsFilteredByEnvPrefix[e.Pid]; ok { + if _, ok := d.filteredPIDs[e.Pid]; ok { d.l.Debug("skipping exit event for process filtered by env prefix", "pid", e.Pid) - delete(d.pidsFilteredByEnvPrefix, e.Pid) + delete(d.filteredPIDs, e.Pid) continue } pe.EventType = ProcessExitEvent diff --git a/detector_test.go b/detector_test.go index 8e1c171..c994bab 100644 --- a/detector_test.go +++ b/detector_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/cilium/ebpf/features" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -162,12 +161,13 @@ func TestDetector(t *testing.T) { exePath: "/usr/bin/bash", args: []string{"-c", "start=$SECONDS; while (( SECONDS - start < 1 )); do :; done"}, shouldDetect: false, - - // When the kernel doesn't support bounded loops, we exclude the eBPF code that checks for the executable path. - // This is temporary until we have an additional check in user space to filter out the executable. - skipTest: func(t *testing.T) bool { - return features.HaveBoundedLoops() != nil - }, + }, + { + name: "bash script is filtered", + envVarsForExec: map[string]string{"USER_ENV": "value"}, + exePath: "test/script.sh", + args: []string{}, + shouldDetect: false, }, } diff --git a/test/script.sh b/test/script.sh new file mode 100755 index 0000000..6f4fda5 --- /dev/null +++ b/test/script.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Simple bash script for testing +# This script should be filtered out by the executable name filter + +echo "Running bash script" + +for i in {1..10}; do + sleep 0.08 +done + +echo "Bash script completed"