Skip to content

Commit e48c817

Browse files
committed
feat: add options to ignore command lines and parent processes in eBPF input
1 parent d1743ce commit e48c817

2 files changed

Lines changed: 104 additions & 8 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ Options:
176176
- `-I ebpf:ignore-comm:<name>` — drop events from this process name
177177
(matched against the resolved `/proc` name; may be repeated; supports
178178
glob wildcards `*`, `?`, `[...]` — e.g. `chrome*`, `*-worker`)
179+
- `-I ebpf:ignore-cmdline:<pattern>` — drop events whose full command line
180+
matches this pattern (substring match by default; with glob wildcards
181+
`*` crosses any character including `/` — e.g. `*/unbound*`, `syncthing`)
182+
- `-I ebpf:ignore-parent:<name>` — drop events whose parent process name
183+
matches (same syntax as `ignore-comm`: exact or glob)
179184

180185
### Filtering: nflog vs ebpf
181186

internal/inputs/ebpf/ebpf.go

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"net"
1616
"os"
1717
"path"
18+
"regexp"
1819
"strconv"
1920
"strings"
2021
"sync"
@@ -56,6 +57,12 @@ type Input struct {
5657
ignoreComms map[string]struct{} // exact matches (fast path)
5758
ignoreCommGlobs []string // glob patterns (path.Match syntax)
5859

60+
ignoreCmdlines []string // plain substring matches
61+
ignoreCmdlineGlobs []*regexp.Regexp // compiled glob-to-regex patterns
62+
63+
ignoreParents map[string]struct{} // exact parent name matches
64+
ignoreParentGlobs []string // glob patterns for parent name
65+
5966
objs bpfObjects
6067
links []link.Link
6168

@@ -86,13 +93,19 @@ func (e *Input) Description() string {
8693
- "ebpf:ignore-comm:<name>": drop events from this process name
8794
(matched against the resolved /proc name; may be specified multiple
8895
times; supports glob wildcards * ? [...] e.g. "chrome*", "*-worker")
96+
- "ebpf:ignore-cmdline:<pattern>": drop events whose full command line
97+
matches this pattern (substring match by default; supports glob
98+
wildcards * ? [...] where * crosses any character including /)
99+
- "ebpf:ignore-parent:<name>": drop events whose parent process name
100+
matches (same syntax as ignore-comm: exact or glob)
89101
90102
Example:
91103
sudo egress-auditor -i ebpf -o logfmt \
92104
-I ebpf:ignore-cidr:10.0.0.0/8 \
93105
-I ebpf:ignore-cidr:192.168.0.0/16 \
94106
-I ebpf:ignore-port:53 \
95-
-I ebpf:ignore-comm:chronyd
107+
-I ebpf:ignore-comm:chronyd \
108+
-I ebpf:ignore-cmdline:'/usr/sbin/unbound*'
96109
`
97110
}
98111

@@ -142,12 +155,69 @@ func (e *Input) SetOption(k, v string) error {
142155
}
143156
e.ignoreComms[v] = struct{}{}
144157
}
158+
case "ignore-cmdline":
159+
if v == "" {
160+
return fmt.Errorf("ignore-cmdline requires a non-empty value")
161+
}
162+
if strings.ContainsAny(v, "*?[") {
163+
re, err := regexp.Compile("^" + globToRegex(v) + "$")
164+
if err != nil {
165+
return fmt.Errorf("invalid ignore-cmdline pattern %q: %w", v, err)
166+
}
167+
e.ignoreCmdlineGlobs = append(e.ignoreCmdlineGlobs, re)
168+
} else {
169+
e.ignoreCmdlines = append(e.ignoreCmdlines, v)
170+
}
171+
case "ignore-parent":
172+
if v == "" {
173+
return fmt.Errorf("ignore-parent requires a non-empty value")
174+
}
175+
if strings.ContainsAny(v, "*?[") {
176+
if _, err := path.Match(v, ""); err != nil {
177+
return fmt.Errorf("invalid ignore-parent pattern %q: %w", v, err)
178+
}
179+
e.ignoreParentGlobs = append(e.ignoreParentGlobs, v)
180+
} else {
181+
if e.ignoreParents == nil {
182+
e.ignoreParents = make(map[string]struct{})
183+
}
184+
e.ignoreParents[v] = struct{}{}
185+
}
145186
default:
146187
return fmt.Errorf("option %q unknown for ebpf input", k)
147188
}
148189
return nil
149190
}
150191

192+
// globToRegex converts a shell-style glob pattern to a regex string.
193+
// Unlike path.Match, * and ? cross any character (including /).
194+
func globToRegex(pattern string) string {
195+
var b strings.Builder
196+
inBracket := false
197+
for i := 0; i < len(pattern); i++ {
198+
c := pattern[i]
199+
switch {
200+
case c == '*' && !inBracket:
201+
b.WriteString(".*")
202+
case c == '?' && !inBracket:
203+
b.WriteByte('.')
204+
case c == '[' && !inBracket:
205+
inBracket = true
206+
b.WriteByte('[')
207+
case c == ']' && inBracket:
208+
inBracket = false
209+
b.WriteByte(']')
210+
default:
211+
// Escape regex metacharacters outside brackets.
212+
if !inBracket && strings.ContainsRune(`\.+^${}()|`, rune(c)) {
213+
b.WriteByte('\\')
214+
}
215+
b.WriteByte(c)
216+
}
217+
}
218+
return b.String()
219+
}
220+
151221
// isNetFiltered returns true if the destination IP/port should be dropped.
152222
// This is checked early, before any process resolution.
153223
func (e *Input) isNetFiltered(destIP net.IP, dport uint16) bool {
@@ -162,12 +232,11 @@ func (e *Input) isNetFiltered(destIP net.IP, dport uint16) bool {
162232
return false
163233
}
164234

165-
// isProcFiltered returns true if the resolved process name matches an
166-
// ignore-comm rule. We match against proc.Name (from /proc) rather than the
167-
// eBPF-captured thread comm, because multi-threaded daemons set per-thread
168-
// names via prctl(PR_SET_NAME) — the kernel comm at hook time can differ
169-
// from the user-visible process name (e.g. unbound worker threads).
170-
func (e *Input) isProcFiltered(procName string) bool {
235+
// isProcFiltered returns true if the resolved process name or command line
236+
// matches an ignore-comm or ignore-cmdline rule. We match against proc.Name
237+
// and proc.CmdLine (from /proc) rather than the eBPF-captured thread comm,
238+
// because multi-threaded daemons set per-thread names via prctl(PR_SET_NAME).
239+
func (e *Input) isProcFiltered(procName, cmdLine, parentName string) bool {
171240
if _, skip := e.ignoreComms[procName]; skip {
172241
return true
173242
}
@@ -176,6 +245,24 @@ func (e *Input) isProcFiltered(procName string) bool {
176245
return true
177246
}
178247
}
248+
for _, sub := range e.ignoreCmdlines {
249+
if strings.Contains(cmdLine, sub) {
250+
return true
251+
}
252+
}
253+
for _, re := range e.ignoreCmdlineGlobs {
254+
if re.MatchString(cmdLine) {
255+
return true
256+
}
257+
}
258+
if _, skip := e.ignoreParents[parentName]; skip {
259+
return true
260+
}
261+
for _, pat := range e.ignoreParentGlobs {
262+
if ok, _ := path.Match(pat, parentName); ok {
263+
return true
264+
}
265+
}
179266
return false
180267
}
181268

@@ -284,7 +371,11 @@ func (e *Input) Process(ctx context.Context, c chan<- entry.Connection) {
284371
proc = fallbackProc(int32(evt.Pid), evt.Comm[:])
285372
}
286373

287-
if e.isProcFiltered(proc.Name) {
374+
parentName := ""
375+
if proc.Parent != nil {
376+
parentName = proc.Parent.Name
377+
}
378+
if e.isProcFiltered(proc.Name, proc.CmdLine, parentName) {
288379
continue
289380
}
290381

0 commit comments

Comments
 (0)