diff options
| -rw-r--r-- | cmd/ioworkload/scenario_process.go | 76 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 | ||||
| -rw-r--r-- | integrationtests/process_test.go | 31 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints.c | 25 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints_result.txt | 4 | ||||
| -rw-r--r-- | internal/c/types.h | 14 | ||||
| -rw-r--r-- | internal/eventloop_exit.go | 18 | ||||
| -rw-r--r-- | internal/eventloop_process_test.go | 80 | ||||
| -rw-r--r-- | internal/eventloop_runtime.go | 13 | ||||
| -rw-r--r-- | internal/generate/bpfhandler.go | 26 | ||||
| -rw-r--r-- | internal/generate/classify.go | 9 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 38 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 15 | ||||
| -rw-r--r-- | internal/generate/kindregistry.go | 1 | ||||
| -rw-r--r-- | internal/generate/testdata.go | 32 | ||||
| -rw-r--r-- | internal/types/fastdecode.go | 21 | ||||
| -rw-r--r-- | internal/types/fastdecode_test.go | 16 | ||||
| -rw-r--r-- | internal/types/generated_types.go | 73 |
18 files changed, 477 insertions, 16 deletions
diff --git a/cmd/ioworkload/scenario_process.go b/cmd/ioworkload/scenario_process.go new file mode 100644 index 0000000..c2c5bbd --- /dev/null +++ b/cmd/ioworkload/scenario_process.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "runtime" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +const processExecEmitFor = 2 * time.Second + +func processExecLifecycle() error { + deadline := time.Now().Add(processExecEmitFor) + for time.Now().Before(deadline) { + if err := callExecveMissing(); err != nil { + return err + } + if err := callExecveatMissing(); err != nil { + return err + } + time.Sleep(10 * time.Millisecond) + } + return nil +} + +func callExecveMissing() error { + filename, err := syscall.BytePtrFromString("/tmp/ior-missing-execve-only") + if err != nil { + return fmt.Errorf("execve filename: %w", err) + } + argv := []uintptr{uintptr(unsafe.Pointer(filename)), 0} + envp := []uintptr{0} + _, _, errno := syscall.RawSyscall( + syscall.SYS_EXECVE, + uintptr(unsafe.Pointer(filename)), + uintptr(unsafe.Pointer(&argv[0])), + uintptr(unsafe.Pointer(&envp[0])), + ) + runtime.KeepAlive(filename) + runtime.KeepAlive(argv) + runtime.KeepAlive(envp) + if errno != syscall.ENOENT { + return fmt.Errorf("execve errno=%v, want ENOENT", errno) + } + return nil +} + +func callExecveatMissing() error { + filename, err := syscall.BytePtrFromString("ior-missing-execveat-only") + if err != nil { + return fmt.Errorf("execveat filename: %w", err) + } + argv := []uintptr{uintptr(unsafe.Pointer(filename)), 0} + envp := []uintptr{0} + dirfdSigned := int64(unix.AT_FDCWD) + dirfd := uintptr(dirfdSigned) + _, _, errno := syscall.RawSyscall6( + unix.SYS_EXECVEAT, + dirfd, + uintptr(unsafe.Pointer(filename)), + uintptr(unsafe.Pointer(&argv[0])), + uintptr(unsafe.Pointer(&envp[0])), + 0, + 0, + ) + runtime.KeepAlive(filename) + runtime.KeepAlive(argv) + runtime.KeepAlive(envp) + if errno != syscall.ENOENT { + return fmt.Errorf("execveat errno=%v, want ENOENT", errno) + } + return nil +} diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index a1039e0..1ec8e6d 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -38,6 +38,7 @@ var scenarios = map[string]func() error{ "mountfs-management": mountfsManagement, "polling-epoll": pollingEpoll, "sleep-syscalls": sleepSyscalls, + "process-exec-lifecycle": processExecLifecycle, "family-mixed": familyMixed, "close-basic": closeBasic, "close-range": closeRange, diff --git a/integrationtests/process_test.go b/integrationtests/process_test.go new file mode 100644 index 0000000..e9cd739 --- /dev/null +++ b/integrationtests/process_test.go @@ -0,0 +1,31 @@ +package integrationtests + +import "testing" + +func TestProcessExecLifecycle(t *testing.T) { + result, _ := runScenarioResult(t, "process-exec-lifecycle", []ExpectedEvent{ + { + Tracepoint: "enter_execve", + PathContains: "ior-missing-execve-only", + Comm: "ioworkload", + MinCount: 1, + }, + { + Tracepoint: "enter_execveat", + PathContains: "ior-missing-execveat-only", + Comm: "ioworkload", + MinCount: 1, + }, + }) + + assertEventDurationPositive(t, result, ExpectedEvent{ + Tracepoint: "enter_execve", + PathContains: "ior-missing-execve-only", + Comm: "ioworkload", + }) + assertEventDurationPositive(t, result, ExpectedEvent{ + Tracepoint: "enter_execveat", + PathContains: "ior-missing-execveat-only", + Comm: "ioworkload", + }) +} diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c index b7fa686..58c993f 100644 --- a/internal/c/generated_tracepoints.c +++ b/internal/c/generated_tracepoints.c @@ -8972,7 +8972,7 @@ int handle_sys_exit_pipe(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_execve is a struct path_event +/// sys_enter_execve is a struct exec_event SEC("tracepoint/syscalls/sys_enter_execve") int handle_sys_enter_execve(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -8982,17 +8982,20 @@ int handle_sys_enter_execve(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_EXECVE)) return 0; - struct path_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct path_event), 0); + struct exec_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct exec_event), 0); if (!ev) return 0; - ev->event_type = ENTER_PATH_EVENT; + ev->event_type = ENTER_EXEC_EVENT; ev->trace_id = SYS_ENTER_EXECVE; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); - __builtin_memset(&(ev->pathname), 0, sizeof(ev->pathname)); - bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[0]); + __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm)); + bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[0]); + bpf_get_current_comm(&ev->comm, sizeof(ev->comm)); + ev->dirfd = -1; + ev->flags = 0; bpf_ringbuf_submit(ev, 0); return 0; @@ -9024,7 +9027,7 @@ int handle_sys_exit_execve(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_execveat is a struct fd_event +/// sys_enter_execveat is a struct exec_event SEC("tracepoint/syscalls/sys_enter_execveat") int handle_sys_enter_execveat(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -9034,16 +9037,20 @@ int handle_sys_enter_execveat(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_EXECVEAT)) return 0; - struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0); + struct exec_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct exec_event), 0); if (!ev) return 0; - ev->event_type = ENTER_FD_EVENT; + ev->event_type = ENTER_EXEC_EVENT; ev->trace_id = SYS_ENTER_EXECVEAT; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); - ev->fd = (__s32)ctx->args[0]; + __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm)); + bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]); + bpf_get_current_comm(&ev->comm, sizeof(ev->comm)); + ev->dirfd = -1; + ev->flags = (__s32)ctx->args[4]; bpf_ringbuf_submit(ev, 0); return 0; diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt index 0d516db..8328467 100644 --- a/internal/c/generated_tracepoints_result.txt +++ b/internal/c/generated_tracepoints_result.txt @@ -40,8 +40,8 @@ sys_enter_epoll_pwait2 is a struct fd_event sys_enter_epoll_wait is a struct fd_event sys_enter_eventfd is a struct eventfd_event sys_enter_eventfd2 is a struct eventfd_event -sys_enter_execve is a struct path_event -sys_enter_execveat is a struct fd_event +sys_enter_execve is a struct exec_event +sys_enter_execveat is a struct exec_event sys_enter_exit is a struct null_event sys_enter_exit_group is a struct null_event sys_enter_faccessat is a struct path_event diff --git a/internal/c/types.h b/internal/c/types.h index 6fde3a1..f384075 100644 --- a/internal/c/types.h +++ b/internal/c/types.h @@ -47,6 +47,8 @@ #define EXIT_PTRACE_EVENT 42 #define ENTER_PERF_OPEN_EVENT 43 #define EXIT_PERF_OPEN_EVENT 44 +#define ENTER_EXEC_EVENT 45 +#define EXIT_EXEC_EVENT 46 #define UNCLASSIFIED 0 #define READ_CLASSIFIED 1 @@ -64,6 +66,18 @@ struct open_event { char comm[MAX_PROGNAME_LENGTH]; }; +struct exec_event { + __u32 event_type; + __u32 trace_id; + __u64 time; + __u32 pid; + __u32 tid; + __s32 dirfd; + __s32 flags; + char filename[MAX_FILENAME_LENGTH]; + char comm[MAX_PROGNAME_LENGTH]; +}; + struct null_event { __u32 event_type; __u32 trace_id; diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go index 5ee31f5..598adba 100644 --- a/internal/eventloop_exit.go +++ b/internal/eventloop_exit.go @@ -16,6 +16,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool { switch ev := ep.EnterEv.(type) { case *types.OpenEvent: return e.handleOpenExit(ep, ev) + case *types.ExecEvent: + return e.handleExecExit(ep, ev) case *types.NameEvent: return e.handleNameExit(ep, ev) case *types.PathEvent: @@ -83,6 +85,22 @@ func (e *eventLoop) handleOpenExit(ep *event.Pair, openEv *types.OpenEvent) bool return true } +func (e *eventLoop) handleExecExit(ep *event.Pair, execEv *types.ExecEvent) bool { + if _, ok := ep.ExitEv.(*types.RetEvent); !ok { + e.recyclePair(ep, "Dropped malformed exec exit event") + return false + } + comm := types.StringValue(execEv.Comm[:]) + ep.Comm = comm + ep.File = file.NewPathname(execEv.Filename[:]) + e.setCachedComm(execEv.Tid, comm) + if !e.Filter().MatchPair(ep) { + ep.Recycle() + return false + } + return true +} + func (e *eventLoop) handleNameExit(ep *event.Pair, nameEv *types.NameEvent) bool { ep.File = file.NewOldnameNewname(nameEv.Oldname[:], nameEv.Newname[:]) ep.Comm = e.comm(nameEv.GetTid()) diff --git a/internal/eventloop_process_test.go b/internal/eventloop_process_test.go new file mode 100644 index 0000000..9a06780 --- /dev/null +++ b/internal/eventloop_process_test.go @@ -0,0 +1,80 @@ +package internal + +import ( + "testing" + + "ior/internal/event" + "ior/internal/globalfilter" + "ior/internal/types" +) + +func TestHandleExecExitSetsPathAndComm(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + enter := &types.ExecEvent{ + EventType: types.ENTER_EXEC_EVENT, + TraceId: types.SYS_ENTER_EXECVEAT, + Time: 100, + Pid: 200, + Tid: 201, + Dirfd: -100, + Flags: 0, + } + copy(enter.Filename[:], "/definitely-missing-execveat") + copy(enter.Comm[:], "ioworkload") + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_EXECVEAT, + Time: 200, + Ret: -2, + Pid: 200, + Tid: 201, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handleExecExit(ep, enter); !ok { + t.Fatal("handleExecExit returned false") + } + if got := ep.File.Name(); got != "/definitely-missing-execveat" { + t.Fatalf("exec path = %q, want %q", got, "/definitely-missing-execveat") + } + if got := ep.Comm; got != "ioworkload" { + t.Fatalf("comm = %q, want %q", got, "ioworkload") + } +} + +func TestHandleExecExitAppliesPairFilter(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{ + filter: globalfilter.Filter{ + Syscall: &globalfilter.StringFilter{Pattern: "openat"}, + }, + }) + enter := &types.ExecEvent{ + EventType: types.ENTER_EXEC_EVENT, + TraceId: types.SYS_ENTER_EXECVE, + Time: 100, + Pid: 210, + Tid: 211, + } + copy(enter.Filename[:], "/definitely-missing-execve") + copy(enter.Comm[:], "ioworkload") + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_EXECVE, + Time: 200, + Ret: -2, + Pid: 210, + Tid: 211, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handleExecExit(ep, enter); ok { + t.Fatal("handleExecExit should reject pair due to filter mismatch") + } +} + +func TestInitRawHandlersRegistersExecEvent(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + if _, ok := el.rawHandlers[types.ENTER_EXEC_EVENT]; !ok { + t.Fatal("ENTER_EXEC_EVENT handler is not registered") + } +} diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go index 334fa63..d9d9c4c 100644 --- a/internal/eventloop_runtime.go +++ b/internal/eventloop_runtime.go @@ -250,6 +250,7 @@ func (e *eventLoop) initRawHandlers() { e.registerTwoFdHandlers() e.registerMemoryHandlers() e.registerSleepHandlers() + e.registerProcessHandlers() e.registerSecurityHandlers() } @@ -483,6 +484,16 @@ func (e *eventLoop) registerSleepHandlers() { } } +func (e *eventLoop) registerProcessHandlers() { + e.rawHandlers[types.ENTER_EXEC_EVENT] = func(raw []byte, _ chan<- *event.Pair) { + execEv, ok := decodeRawEvent(e, types.ENTER_EXEC_EVENT, raw, types.NewExecEventFast) + if !ok { + return + } + e.tracepointEntered(execEv) + } +} + func (e *eventLoop) registerSecurityHandlers() { e.rawHandlers[types.ENTER_KEYCTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) { keyctlEv, ok := decodeRawEvent(e, types.ENTER_KEYCTL_EVENT, raw, types.NewKeyctlEventFast) @@ -528,6 +539,8 @@ func (e *eventLoop) tracepointEntered(enterEv event.Event) { switch enterEv.(type) { case *types.OpenEvent: e.pairs.set(enterEv) + case *types.ExecEvent: + e.pairs.set(enterEv) default: // Only, when we have a comm name if _, ok := e.cachedComm(tid); ok { diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go index b166725..5489d88 100644 --- a/internal/generate/bpfhandler.go +++ b/internal/generate/bpfhandler.go @@ -111,6 +111,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string { return generateExtraOpen(f) case KindMqOpen: return generateExtraMqOpen(f) + case KindExec: + return generateExtraExec(f) case KindPathname: return generateExtraPathname(tp, f) case KindName: @@ -146,6 +148,30 @@ func generateExtraMqOpen(f *Format) string { return generateExtraOpenWithFields(f, "u_name", "oflag") } +func generateExtraExec(f *Format) string { + filenameIdx := f.FieldNumber("filename") + dirfdIdx := f.FieldNumber("dfd") + flagsIdx := f.FieldNumber("flags") + if filenameIdx < 0 { + filenameIdx = 0 + } + var b strings.Builder + b.WriteString(" __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));\n") + fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[%d]);\n", filenameIdx) + b.WriteString(" bpf_get_current_comm(&ev->comm, sizeof(ev->comm));\n") + if dirfdIdx > -1 { + fmt.Fprintf(&b, " ev->dirfd = (__s32)ctx->args[%d];\n", dirfdIdx) + } else { + b.WriteString(" ev->dirfd = -1;\n") + } + if flagsIdx > -1 { + fmt.Fprintf(&b, " ev->flags = (__s32)ctx->args[%d];\n", flagsIdx) + } else { + b.WriteString(" ev->flags = 0;\n") + } + return b.String() +} + func generateExtraOpenWithFields(f *Format, pathnameField, flagsField string) string { filenameIdx := f.FieldNumber(pathnameField) flagsIdx := f.FieldNumber(flagsField) diff --git a/internal/generate/classify.go b/internal/generate/classify.go index 77bab7e..af7d78d 100644 --- a/internal/generate/classify.go +++ b/internal/generate/classify.go @@ -9,6 +9,7 @@ const ( KindFd KindOpen KindMqOpen + KindExec KindPathname KindName KindRet @@ -193,6 +194,14 @@ func classifyNameOnly(name string) (ClassificationResult, bool) { return ClassificationResult{Kind: KindFd}, true case "sys_enter_mq_getsetattr": return ClassificationResult{Kind: KindFd}, true + case "sys_enter_execve": + return ClassificationResult{Kind: KindExec}, true + case "sys_enter_execveat": + return ClassificationResult{Kind: KindExec}, true + case "sys_enter_exit": + return ClassificationResult{Kind: KindNull}, true + case "sys_enter_exit_group": + return ClassificationResult{Kind: KindNull}, true } if strings.HasPrefix(name, "sys_enter_io_") { return ClassificationResult{Kind: KindNull}, true diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 2a12911..5d6424b 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -231,10 +231,17 @@ func TestClassifyPathnameMknod(t *testing.T) { } } -func TestClassifyPathnameExecve(t *testing.T) { +func TestClassifyExecExecve(t *testing.T) { r := classifyFromData(t, FormatExecve) - if r.Kind != KindPathname { - t.Errorf("execve: got kind %d, want KindPathname", r.Kind) + if r.Kind != KindExec { + t.Errorf("execve: got kind %d, want KindExec", r.Kind) + } +} + +func TestClassifyExecExecveat(t *testing.T) { + r := classifyFromData(t, FormatExecveat) + if r.Kind != KindExec { + t.Errorf("execveat: got kind %d, want KindExec", r.Kind) } } @@ -694,6 +701,24 @@ func TestClassifyKillRequiresGenerationFallback(t *testing.T) { } } +func TestClassifyNullExitByName(t *testing.T) { + tests := []string{"sys_enter_exit", "sys_enter_exit_group"} + for _, name := range tests { + t.Run(name, func(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: name, + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "int", Name: "error_code"}, + }, + }) + if r.Kind != KindNull { + t.Errorf("%s: got kind %d, want KindNull", name, r.Kind) + } + }) + } +} + // --- End-to-end classification with enter+exit pairs --- func TestClassifySyscallPairAccepted(t *testing.T) { @@ -722,7 +747,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) { {"pread64", FormatPread64, FormatExitPread64, KindFd}, {"symlink", FormatSymlink, FormatExitSymlink, KindName}, {"mknod", FormatMknod, FormatExitMknod, KindPathname}, - {"execve", FormatExecve, FormatExitExecve, KindPathname}, + {"execve", FormatExecve, FormatExitExecve, KindExec}, + {"execveat", FormatExecveat, FormatExitExecveat, KindExec}, {"accept", FormatAccept, FormatExitAccept, KindAccept}, {"accept4", FormatAccept4, FormatExitAccept4, KindAccept}, {"socket", FormatSocket, FormatExitSocket, KindSocket}, @@ -760,6 +786,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) { {"swapon", FormatSwapon, FormatExitSwapon, KindPathname}, {"swapoff", FormatSwapoff, FormatExitSwapoff, KindPathname}, {"kill", FormatKill, FormatExitKill, KindNull}, + {"exit", syntheticEnter("exit", 9310), syntheticExit("exit", 9309), KindNull}, + {"exit_group", syntheticEnter("exit_group", 9312), syntheticExit("exit_group", 9311), KindNull}, } for _, tt := range tests { @@ -782,6 +810,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { }{ {"mknod", FormatMknod, FormatExitMknod, FamilyFS}, {"execve", FormatExecve, FormatExitExecve, FamilyProcess}, + {"execveat", FormatExecveat, FormatExitExecveat, FamilyProcess}, {"accept", FormatAccept, FormatExitAccept, FamilyNetwork}, {"accept4", FormatAccept4, FormatExitAccept4, FamilyNetwork}, {"socket", FormatSocket, FormatExitSocket, FamilyNetwork}, @@ -816,6 +845,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { {"swapon", FormatSwapon, FormatExitSwapon, FamilyFS}, {"swapoff", FormatSwapoff, FormatExitSwapoff, FamilyFS}, {"kill", FormatKill, FormatExitKill, FamilySignals}, + {"exit_group", syntheticEnter("exit_group", 9316), syntheticExit("exit_group", 9315), FamilyProcess}, } for _, tt := range tests { diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index f1c98df..95ced4d 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -64,6 +64,17 @@ func TestGenerateMqOpenHandler(t *testing.T) { requireContains(t, output, "ev->flags = ctx->args[1];") } +func TestGenerateExecHandler(t *testing.T) { + output := generateFromPair(t, FormatExecveat, FormatExitExecveat) + + requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_execveat")`) + requireContains(t, output, "struct exec_event *ev") + requireContains(t, output, "ev->event_type = ENTER_EXEC_EVENT;") + requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]);") + requireContains(t, output, "ev->dirfd = (__s32)ctx->args[0];") + requireContains(t, output, "ev->flags = (__s32)ctx->args[4];") +} + func TestGenerateOpenat2Handler(t *testing.T) { f := mustParseOne(t, FormatOpenat2) r := ClassifyFormat(&f) @@ -515,6 +526,7 @@ func TestGenerateAllEventTypes(t *testing.T) { {KindFd, "ENTER_FD_EVENT", "EXIT_FD_EVENT"}, {KindOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"}, {KindMqOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"}, + {KindExec, "ENTER_EXEC_EVENT", "EXIT_EXEC_EVENT"}, {KindPathname, "ENTER_PATH_EVENT", "EXIT_PATH_EVENT"}, {KindName, "ENTER_NAME_EVENT", "EXIT_NAME_EVENT"}, {KindRet, "ENTER_RET_EVENT", "EXIT_RET_EVENT"}, @@ -555,6 +567,7 @@ func TestEventStructNames(t *testing.T) { {KindFd, "fd_event"}, {KindOpen, "open_event"}, {KindMqOpen, "open_event"}, + {KindExec, "exec_event"}, {KindPathname, "path_event"}, {KindName, "name_event"}, {KindRet, "ret_event"}, @@ -593,7 +606,7 @@ func TestEnterReject(t *testing.T) { t.Error("KindNone should be enter-rejected") } - accepted := []TracepointKind{KindFd, KindOpen, KindMqOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindTwoFd, KindPoll, KindMem, KindSleep, KindKeyctl, KindPtrace, KindPerfOpen} + accepted := []TracepointKind{KindFd, KindOpen, KindMqOpen, KindExec, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindTwoFd, KindPoll, KindMem, KindSleep, KindKeyctl, KindPtrace, KindPerfOpen} for _, k := range accepted { if isEnterRejected(k) { t.Errorf("kind %d should NOT be enter-rejected", k) diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go index 6afe4c1..a5f5795 100644 --- a/internal/generate/kindregistry.go +++ b/internal/generate/kindregistry.go @@ -19,6 +19,7 @@ var kindRegistry = map[TracepointKind]kindMeta{ KindFd: {structName: "fd_event", enterAccepted: true}, KindOpen: {structName: "open_event", enterAccepted: true}, KindMqOpen: {structName: "open_event", enterAccepted: true}, + KindExec: {structName: "exec_event", enterAccepted: true}, KindPathname: {structName: "path_event", enterAccepted: true}, KindName: {structName: "name_event", enterAccepted: true}, KindRet: {structName: "ret_event", enterAccepted: false}, diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go index 2bc041e..d94c141 100644 --- a/internal/generate/testdata.go +++ b/internal/generate/testdata.go @@ -1098,6 +1098,38 @@ format: print fmt: "0x%lx", REC->ret ` +const FormatExecveat = `name: sys_enter_execveat +ID: 869 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:int __syscall_nr; offset:8; size:4; signed:1; + field:int dfd; offset:16; size:8; signed:0; + field:const char * filename; offset:24; size:8; signed:0; + field:const char *const * argv; offset:32; size:8; signed:0; + field:const char *const * envp; offset:40; size:8; signed:0; + field:int flags; offset:48; size:8; signed:0; + +print fmt: "dfd: 0x%08lx, filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp)), ((unsigned long)(REC->flags)) +` + +const FormatExitExecveat = `name: sys_exit_execveat +ID: 868 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:int __syscall_nr; offset:8; size:4; signed:1; + field:long ret; offset:16; size:8; signed:1; + +print fmt: "0x%lx", REC->ret +` + const FormatMknod = `name: sys_enter_mknod ID: 894 format: diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go index e592e5d..5b0f588 100644 --- a/internal/types/fastdecode.go +++ b/internal/types/fastdecode.go @@ -4,6 +4,7 @@ import "encoding/binary" const ( openEventSize = 300 + execEventSize = 304 nullEventSize = 24 fdEventSize = 28 retEventSize = 36 @@ -51,6 +52,26 @@ func NewOpenEventFast(raw []byte) *OpenEvent { return o } +func NewExecEventFast(raw []byte) *ExecEvent { + if len(raw) < execEventSize { + return nil + } + if len(raw) != execEventSize { + return NewExecEvent(raw) + } + e := poolOfExecEvents.Get().(*ExecEvent) + e.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4])) + e.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8])) + e.Time = binary.LittleEndian.Uint64(raw[8:16]) + e.Pid = binary.LittleEndian.Uint32(raw[16:20]) + e.Tid = binary.LittleEndian.Uint32(raw[20:24]) + e.Dirfd = int32(binary.LittleEndian.Uint32(raw[24:28])) + e.Flags = int32(binary.LittleEndian.Uint32(raw[28:32])) + copy(e.Filename[:], raw[32:288]) + copy(e.Comm[:], raw[288:304]) + return e +} + func NewNullEventFast(raw []byte) *NullEvent { if len(raw) < nullEventSize { return nil diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go index 1a3a8bc..d09f6a9 100644 --- a/internal/types/fastdecode_test.go +++ b/internal/types/fastdecode_test.go @@ -21,6 +21,21 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) { } }) + t.Run("ExecEvent", func(t *testing.T) { + ev := &ExecEvent{EventType: ENTER_EXEC_EVENT, TraceId: SYS_ENTER_EXECVEAT, Time: 1, Pid: 2, Tid: 3, Dirfd: -100, Flags: 4} + copy(ev.Filename[:], "a") + copy(ev.Comm[:], "b") + raw, _ := ev.Bytes() + + slow := NewExecEvent(raw) + fast := NewExecEventFast(raw) + defer slow.Recycle() + defer fast.Recycle() + if !slow.Equals(fast) { + t.Fatalf("exec decode mismatch") + } + }) + t.Run("NullEvent", func(t *testing.T) { ev := &NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 2, Tid: 3} raw, _ := ev.Bytes() @@ -535,6 +550,7 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) { decode func([]byte) bool }{ {name: "OpenEvent", decode: func(raw []byte) bool { return NewOpenEventFast(raw) == nil }}, + {name: "ExecEvent", decode: func(raw []byte) bool { return NewExecEventFast(raw) == nil }}, {name: "NullEvent", decode: func(raw []byte) bool { return NewNullEventFast(raw) == nil }}, {name: "FdEvent", decode: func(raw []byte) bool { return NewFdEventFast(raw) == nil }}, {name: "RetEvent", decode: func(raw []byte) bool { return NewRetEventFast(raw) == nil }}, diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go index d378794..8ddac3e 100644 --- a/internal/types/generated_types.go +++ b/internal/types/generated_types.go @@ -112,6 +112,8 @@ const ENTER_PTRACE_EVENT = 41 const EXIT_PTRACE_EVENT = 42 const ENTER_PERF_OPEN_EVENT = 43 const EXIT_PERF_OPEN_EVENT = 44 +const ENTER_EXEC_EVENT = 45 +const EXIT_EXEC_EVENT = 46 const UNCLASSIFIED = 0 const READ_CLASSIFIED = 1 const WRITE_CLASSIFIED = 2 @@ -921,6 +923,77 @@ func (o *OpenEvent) Recycle() { poolOfOpenEvents.Put(o) } +type ExecEvent struct { + EventType EventType + TraceId TraceId + Time uint64 + Pid uint32 + Tid uint32 + Dirfd int32 + Flags int32 + Filename [MAX_FILENAME_LENGTH]byte + Comm [MAX_PROGNAME_LENGTH]byte +} + +func (e ExecEvent) String() string { + return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Dirfd:%v Flags:%v Filename:%v Comm:%v", e.EventType, e.TraceId, e.Time, e.Pid, e.Tid, e.Dirfd, e.Flags, string(e.Filename[:]), string(e.Comm[:])) +} + +func (e ExecEvent) Equals(other any) bool { + otherConcrete, ok := other.(*ExecEvent) + if !ok { + return false + } + return e.EventType == otherConcrete.EventType && e.TraceId == otherConcrete.TraceId && e.Time == otherConcrete.Time && e.Pid == otherConcrete.Pid && e.Tid == otherConcrete.Tid && e.Dirfd == otherConcrete.Dirfd && e.Flags == otherConcrete.Flags && e.Filename == otherConcrete.Filename && e.Comm == otherConcrete.Comm +} + +func (e *ExecEvent) GetEventType() EventType { + return e.EventType +} + +func (e *ExecEvent) GetTraceId() TraceId { + return e.TraceId +} + +func (e *ExecEvent) GetPid() uint32 { + return e.Pid +} + +func (e *ExecEvent) GetTid() uint32 { + return e.Tid +} + +func (e *ExecEvent) GetTime() uint64 { + return e.Time +} + +var poolOfExecEvents = sync.Pool{ + New: func() any { return &ExecEvent{} }, +} + +func NewExecEvent(raw []byte) *ExecEvent { + e := poolOfExecEvents.Get().(*ExecEvent) + if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, e); err != nil { + *e = ExecEvent{} + poolOfExecEvents.Put(e) + return nil + } + return e +} + +func (e *ExecEvent) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.LittleEndian, e) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (e *ExecEvent) Recycle() { + poolOfExecEvents.Put(e) +} + type NullEvent struct { EventType EventType TraceId TraceId |
