From be6d4e8ffc722bf0d36c5b01ff46f817539a1525 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 20 May 2026 23:42:12 +0300 Subject: task-47: add KindExec for execve paths --- internal/generate/bpfhandler.go | 26 ++++++++++++++++++++++++++ internal/generate/classify.go | 9 +++++++++ internal/generate/classify_test.go | 38 ++++++++++++++++++++++++++++++++++---- internal/generate/codegen_test.go | 15 ++++++++++++++- internal/generate/kindregistry.go | 1 + internal/generate/testdata.go | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 5 deletions(-) (limited to 'internal/generate') 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: -- cgit v1.2.3