diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-20 22:43:32 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-20 22:43:32 +0300 |
| commit | 6ca4d5ddacaff05d8bd82a5e9a6dfbb39ac111c9 (patch) | |
| tree | a0b4469a9eb96bfb0b5a09d5f086219782040982 | |
| parent | 7a9839917461b12c810329ccb8fd3c6de06902d2 (diff) | |
feat: add keyctl ptrace perf_event_open tracing (task 77)
| -rw-r--r-- | cmd/ioworkload/scenario_security.go | 134 | ||||
| -rw-r--r-- | cmd/ioworkload/scenario_security_test.go | 38 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 | ||||
| -rw-r--r-- | integrationtests/security_test.go | 13 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints.c | 61 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints_result.txt | 10 | ||||
| -rw-r--r-- | internal/c/types.h | 44 | ||||
| -rw-r--r-- | internal/eventloop_exit.go | 55 | ||||
| -rw-r--r-- | internal/eventloop_runtime.go | 25 | ||||
| -rw-r--r-- | internal/eventloop_security_test.go | 86 | ||||
| -rw-r--r-- | internal/generate/bpfhandler.go | 27 | ||||
| -rw-r--r-- | internal/generate/classify.go | 13 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 101 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 61 | ||||
| -rw-r--r-- | internal/generate/kindregistry.go | 3 | ||||
| -rw-r--r-- | internal/types/fastdecode.go | 65 | ||||
| -rw-r--r-- | internal/types/fastdecode_test.go | 55 | ||||
| -rw-r--r-- | internal/types/generated_types.go | 221 | ||||
| -rw-r--r-- | internal/types/types_test.go | 89 |
19 files changed, 1081 insertions, 21 deletions
diff --git a/cmd/ioworkload/scenario_security.go b/cmd/ioworkload/scenario_security.go new file mode 100644 index 0000000..e9e0fe8 --- /dev/null +++ b/cmd/ioworkload/scenario_security.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "runtime" + "syscall" + "unsafe" +) + +var keySpecProcessKeyringArg = ^uintptr(1) + +func securityKeysPtracePerf() error { + nr, err := securitySyscallNumbers(runtime.GOARCH) + if err != nil { + return err + } + + // Best-effort probes: these syscalls may fail with EPERM/EACCES depending on + // policy, but the tracepoints are still exercised. + runKeySyscalls(nr) + runPtraceSyscall(nr) + runPerfEventOpenSyscall(nr) + return nil +} + +type securitySyscalls struct { + addKey uintptr + requestKey uintptr + keyctl uintptr + ptrace uintptr + perfEventOpen uintptr +} + +func securitySyscallNumbers(arch string) (securitySyscalls, error) { + switch arch { + case "amd64": + return securitySyscalls{ + addKey: 248, + requestKey: 249, + keyctl: 250, + ptrace: 101, + perfEventOpen: 298, + }, nil + case "arm64": + return securitySyscalls{ + addKey: 217, + requestKey: 218, + keyctl: 219, + ptrace: 117, + perfEventOpen: 241, + }, nil + default: + return securitySyscalls{}, fmt.Errorf("security syscall numbers not defined for GOARCH=%s", arch) + } +} + +func runKeySyscalls(nr securitySyscalls) { + keyType, _ := syscall.BytePtrFromString("user") + desc, _ := syscall.BytePtrFromString("ior-key") + payload := []byte("ior") + + var payloadPtr uintptr + if len(payload) > 0 { + payloadPtr = uintptr(unsafe.Pointer(&payload[0])) + } + + _, _, _ = syscall.Syscall6( + nr.addKey, + uintptr(unsafe.Pointer(keyType)), + uintptr(unsafe.Pointer(desc)), + payloadPtr, + uintptr(len(payload)), + keySpecProcessKeyringArg, + 0, + ) + + _, _, _ = syscall.Syscall6( + nr.requestKey, + uintptr(unsafe.Pointer(keyType)), + uintptr(unsafe.Pointer(desc)), + 0, + keySpecProcessKeyringArg, + 0, + 0, + ) + + _, _, _ = syscall.Syscall6( + nr.keyctl, + 0, + keySpecProcessKeyringArg, + 0, + 0, + 0, + 0, + ) +} + +func runPtraceSyscall(nr securitySyscalls) { + _, _, _ = syscall.Syscall6( + nr.ptrace, + uintptr(syscall.PTRACE_TRACEME), + 0, + 0, + 0, + 0, + 0, + ) +} + +type perfEventAttr struct { + Type uint32 + Size uint32 + Config uint64 +} + +func runPerfEventOpenSyscall(nr securitySyscalls) { + attr := perfEventAttr{ + Type: 1, // PERF_TYPE_SOFTWARE + Size: uint32(unsafe.Sizeof(perfEventAttr{})), + Config: 0, // PERF_COUNT_SW_CPU_CLOCK + } + fd, _, _ := syscall.Syscall6( + nr.perfEventOpen, + uintptr(unsafe.Pointer(&attr)), + 0, + ^uintptr(0), // cpu = -1 + ^uintptr(0), // group_fd = -1 + 0, + 0, + ) + if int64(fd) >= 0 { + _ = syscall.Close(int(fd)) + } +} diff --git a/cmd/ioworkload/scenario_security_test.go b/cmd/ioworkload/scenario_security_test.go new file mode 100644 index 0000000..f1b6152 --- /dev/null +++ b/cmd/ioworkload/scenario_security_test.go @@ -0,0 +1,38 @@ +package main + +import "testing" + +func TestSecuritySyscallNumbers(t *testing.T) { + for _, tc := range []struct { + name string + arch string + wantErr bool + addKey uintptr + ptrace uintptr + perf uintptr + }{ + {name: "amd64", arch: "amd64", addKey: 248, ptrace: 101, perf: 298}, + {name: "arm64", arch: "arm64", addKey: 217, ptrace: 117, perf: 241}, + {name: "unsupported", arch: "riscv64", wantErr: true}, + } { + got, err := securitySyscallNumbers(tc.arch) + if tc.wantErr { + if err == nil { + t.Fatalf("%s: expected error", tc.name) + } + continue + } + if err != nil { + t.Fatalf("%s: unexpected error: %v", tc.name, err) + } + if got.addKey != tc.addKey || got.ptrace != tc.ptrace || got.perfEventOpen != tc.perf { + t.Fatalf( + "%s: unexpected numbers add_key=%d ptrace=%d perf_event_open=%d", + tc.name, + got.addKey, + got.ptrace, + got.perfEventOpen, + ) + } + } +} diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 7ca5aa4..a1039e0 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -111,6 +111,7 @@ var scenarios = map[string]func() error{ "truncate-ftruncate-ebadf": truncateFtruncateEbadf, "pidfd-getfd-success": pidfdGetfdSuccess, "pidfd-getfd-failure": pidfdGetfdFailure, + "security-keys-ptrace-perf": securityKeysPtracePerf, "iouring-setup": iouringSetup, "iouring-enter": iouringEnter, "iouring-register": iouringRegister, diff --git a/integrationtests/security_test.go b/integrationtests/security_test.go new file mode 100644 index 0000000..cf47809 --- /dev/null +++ b/integrationtests/security_test.go @@ -0,0 +1,13 @@ +package integrationtests + +import "testing" + +func TestSecurityKeysPtracePerf(t *testing.T) { + runScenario(t, "security-keys-ptrace-perf", []ExpectedEvent{ + {Tracepoint: "enter_keyctl", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_add_key", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_request_key", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_ptrace", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_perf_event_open", Comm: "ioworkload", MinCount: 1}, + }) +} diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c index d14f5ef..b7fa686 100644 --- a/internal/c/generated_tracepoints.c +++ b/internal/c/generated_tracepoints.c @@ -2294,7 +2294,7 @@ int handle_sys_exit_lsm_list_modules(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_add_key is a struct null_event +/// sys_enter_add_key is a struct keyctl_event SEC("tracepoint/syscalls/sys_enter_add_key") int handle_sys_enter_add_key(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -2304,15 +2304,18 @@ int handle_sys_enter_add_key(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_ADD_KEY)) return 0; - struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0); + struct keyctl_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct keyctl_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_KEYCTL_EVENT; ev->trace_id = SYS_ENTER_ADD_KEY; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->option = -1; + ev->key_serial = (__s32)ctx->args[4]; + ev->value = (__u64)ctx->args[3]; bpf_ringbuf_submit(ev, 0); return 0; @@ -2344,7 +2347,7 @@ int handle_sys_exit_add_key(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_request_key is a struct null_event +/// sys_enter_request_key is a struct keyctl_event SEC("tracepoint/syscalls/sys_enter_request_key") int handle_sys_enter_request_key(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -2354,15 +2357,18 @@ int handle_sys_enter_request_key(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_REQUEST_KEY)) return 0; - struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0); + struct keyctl_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct keyctl_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_KEYCTL_EVENT; ev->trace_id = SYS_ENTER_REQUEST_KEY; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->option = -2; + ev->key_serial = (__s32)ctx->args[3]; + ev->value = 0; bpf_ringbuf_submit(ev, 0); return 0; @@ -2394,7 +2400,7 @@ int handle_sys_exit_request_key(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_keyctl is a struct null_event +/// sys_enter_keyctl is a struct keyctl_event SEC("tracepoint/syscalls/sys_enter_keyctl") int handle_sys_enter_keyctl(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -2404,15 +2410,18 @@ int handle_sys_enter_keyctl(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_KEYCTL)) return 0; - struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0); + struct keyctl_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct keyctl_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_KEYCTL_EVENT; ev->trace_id = SYS_ENTER_KEYCTL; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->option = (__s32)ctx->args[0]; + ev->key_serial = (__s32)ctx->args[1]; + ev->value = (__u64)ctx->args[2]; bpf_ringbuf_submit(ev, 0); return 0; @@ -13050,7 +13059,7 @@ int handle_sys_exit_rseq(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_perf_event_open is a struct null_event +/// sys_enter_perf_event_open is a struct perf_open_event SEC("tracepoint/syscalls/sys_enter_perf_event_open") int handle_sys_enter_perf_event_open(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -13060,15 +13069,34 @@ int handle_sys_enter_perf_event_open(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_PERF_EVENT_OPEN)) return 0; - struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0); + struct perf_open_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct perf_open_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_PERF_OPEN_EVENT; ev->trace_id = SYS_ENTER_PERF_EVENT_OPEN; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->attr_type = 0; + ev->attr_size = 0; + ev->config = 0; + if (ctx->args[0] != 0) { + struct __ior_perf_event_attr { + __u32 type; + __u32 size; + __u64 config; + } attr = {}; + if (bpf_probe_read_user(&attr, sizeof(attr), (void *)ctx->args[0]) == 0) { + ev->attr_type = attr.type; + ev->attr_size = attr.size; + ev->config = attr.config; + } + } + ev->target_pid = (__s32)ctx->args[1]; + ev->cpu = (__s32)ctx->args[2]; + ev->group_fd = (__s32)ctx->args[3]; + ev->flags = (__u32)ctx->args[4]; bpf_ringbuf_submit(ev, 0); return 0; @@ -18373,7 +18401,7 @@ int handle_sys_exit_rt_sigsuspend(struct syscall_trace_exit *ctx) { return 0; } -/// sys_enter_ptrace is a struct null_event +/// sys_enter_ptrace is a struct ptrace_event SEC("tracepoint/syscalls/sys_enter_ptrace") int handle_sys_enter_ptrace(struct syscall_trace_enter *ctx) { __u32 pid, tid; @@ -18383,15 +18411,18 @@ int handle_sys_enter_ptrace(struct syscall_trace_enter *ctx) { if (!ior_on_syscall_enter(tid, SYS_ENTER_PTRACE)) return 0; - struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0); + struct ptrace_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ptrace_event), 0); if (!ev) return 0; - ev->event_type = ENTER_NULL_EVENT; + ev->event_type = ENTER_PTRACE_EVENT; ev->trace_id = SYS_ENTER_PTRACE; ev->pid = pid; ev->tid = tid; ev->time = bpf_ktime_get_boot_ns(); + ev->request = (__s64)ctx->args[0]; + ev->target_pid = (__s32)ctx->args[1]; + ev->data = (__u64)ctx->args[3]; bpf_ringbuf_submit(ev, 0); return 0; diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt index 892cb1a..0d516db 100644 --- a/internal/c/generated_tracepoints_result.txt +++ b/internal/c/generated_tracepoints_result.txt @@ -2,7 +2,7 @@ sys_enter_accept is a struct accept_event sys_enter_accept4 is a struct accept_event sys_enter_access is a struct path_event sys_enter_acct is a struct null_event -sys_enter_add_key is a struct null_event +sys_enter_add_key is a struct keyctl_event sys_enter_adjtimex is a struct null_event sys_enter_alarm is a struct null_event sys_enter_arch_prctl is a struct null_event @@ -132,7 +132,7 @@ sys_enter_ioprio_set is a struct null_event sys_enter_kcmp is a struct null_event sys_enter_kexec_file_load is a struct null_event sys_enter_kexec_load is a struct null_event -sys_enter_keyctl is a struct null_event +sys_enter_keyctl is a struct keyctl_event sys_enter_kill is a struct null_event sys_enter_landlock_add_rule is a struct null_event sys_enter_landlock_create_ruleset is a struct null_event @@ -205,7 +205,7 @@ sys_enter_open_tree_attr is a struct open_event sys_enter_openat is a struct open_event sys_enter_openat2 is a struct open_event sys_enter_pause is a struct null_event -sys_enter_perf_event_open is a struct null_event +sys_enter_perf_event_open is a struct perf_open_event sys_enter_personality is a struct null_event sys_enter_pidfd_getfd is a struct fd_event sys_enter_pidfd_open is a struct null_event @@ -228,7 +228,7 @@ sys_enter_process_mrelease is a struct null_event sys_enter_process_vm_readv is a struct null_event sys_enter_process_vm_writev is a struct null_event sys_enter_pselect6 is a struct poll_event -sys_enter_ptrace is a struct null_event +sys_enter_ptrace is a struct ptrace_event sys_enter_pwrite64 is a struct fd_event sys_enter_pwritev is a struct fd_event sys_enter_pwritev2 is a struct fd_event @@ -249,7 +249,7 @@ sys_enter_removexattrat is a struct path_event sys_enter_rename is a struct name_event sys_enter_renameat is a struct name_event sys_enter_renameat2 is a struct name_event -sys_enter_request_key is a struct null_event +sys_enter_request_key is a struct keyctl_event sys_enter_restart_syscall is a struct null_event sys_enter_rmdir is a struct path_event sys_enter_rseq is a struct null_event diff --git a/internal/c/types.h b/internal/c/types.h index 6b4785e..6fde3a1 100644 --- a/internal/c/types.h +++ b/internal/c/types.h @@ -41,6 +41,12 @@ #define EXIT_SLEEP_EVENT 36 #define ENTER_TWO_FD_EVENT 37 #define EXIT_TWO_FD_EVENT 38 +#define ENTER_KEYCTL_EVENT 39 +#define EXIT_KEYCTL_EVENT 40 +#define ENTER_PTRACE_EVENT 41 +#define EXIT_PTRACE_EVENT 42 +#define ENTER_PERF_OPEN_EVENT 43 +#define EXIT_PERF_OPEN_EVENT 44 #define UNCLASSIFIED 0 #define READ_CLASSIFIED 1 @@ -245,3 +251,41 @@ struct two_fd_event { __s32 fd_b; __u64 extra; }; + +struct keyctl_event { + __u32 event_type; + __u32 trace_id; + __u64 time; + __u32 pid; + __u32 tid; + __s32 option; + __s32 key_serial; + __u64 value; +}; + +struct ptrace_event { + __u32 event_type; + __u32 trace_id; + __u64 time; + __u32 pid; + __u32 tid; + __s64 request; + __s32 target_pid; + __s32 _pad; + __u64 data; +}; + +struct perf_open_event { + __u32 event_type; + __u32 trace_id; + __u64 time; + __u32 pid; + __u32 tid; + __u32 attr_type; + __u32 attr_size; + __u64 config; + __s32 target_pid; + __s32 cpu; + __s32 group_fd; + __u32 flags; +}; diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go index faaa9e1..5ee31f5 100644 --- a/internal/eventloop_exit.go +++ b/internal/eventloop_exit.go @@ -46,6 +46,12 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool { return e.handleMemExit(ep, ev) case *types.SleepEvent: return e.handleSleepExit(ep, ev) + case *types.KeyctlEvent: + return e.handleKeyctlExit(ep, ev) + case *types.PtraceEvent: + return e.handlePtraceExit(ep, ev) + case *types.PerfOpenEvent: + return e.handlePerfOpenExit(ep, ev) case *types.NullEvent: return e.handleNullExit(ep, ev) case *types.FcntlEvent: @@ -440,6 +446,44 @@ func (e *eventLoop) handleSleepExit(ep *event.Pair, sleepEv *types.SleepEvent) b return true } +func (e *eventLoop) handleKeyctlExit(ep *event.Pair, keyctlEv *types.KeyctlEvent) bool { + ep.Comm = e.comm(keyctlEv.GetTid()) + if !e.Filter().MatchPair(ep) { + ep.Recycle() + return false + } + return true +} + +func (e *eventLoop) handlePtraceExit(ep *event.Pair, ptraceEv *types.PtraceEvent) bool { + ep.Comm = e.comm(ptraceEv.GetTid()) + if !e.Filter().MatchPair(ep) { + ep.Recycle() + return false + } + return true +} + +func (e *eventLoop) handlePerfOpenExit(ep *event.Pair, perfOpenEv *types.PerfOpenEvent) bool { + retEvent, ok := ep.ExitEv.(*types.RetEvent) + if !ok { + e.recyclePair(ep, "Dropped malformed perf_event_open exit event") + return false + } + + if fd := int32(retEvent.Ret); fd >= 0 { + fdFile := file.NewFd(fd, perfDescriptorName(perfOpenEv), -1) + e.fdState().set(fd, fdFile) + ep.File = fdFile + } + ep.Comm = e.comm(perfOpenEv.GetTid()) + if !e.Filter().MatchPair(ep) { + ep.Recycle() + return false + } + return true +} + func pipeDescriptorName(flags, fd0, fd1 int32) string { return fmt.Sprintf("pipe:%d:%d:%d", flags, fd0, fd1) } @@ -448,6 +492,17 @@ func eventfdDescriptorName(flags int32) string { return fmt.Sprintf("eventfd:%d", flags) } +func perfDescriptorName(perfOpenEv *types.PerfOpenEvent) string { + return fmt.Sprintf( + "perf:%d:%d:%d:%d:%d", + perfOpenEv.AttrType, + perfOpenEv.Config, + perfOpenEv.TargetPid, + perfOpenEv.Cpu, + perfOpenEv.GroupFd, + ) +} + func (e *eventLoop) handleNullExit(ep *event.Pair, nullEv *types.NullEvent) bool { if ep.Is(types.SYS_ENTER_IO_URING_SETUP) { retEvent, ok := ep.ExitEv.(*types.RetEvent) diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go index 9a818e3..334fa63 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.registerSecurityHandlers() } // registerOpenHandlers wires enter/exit handlers for open-family events. @@ -482,6 +483,30 @@ func (e *eventLoop) registerSleepHandlers() { } } +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) + if !ok { + return + } + e.tracepointEntered(keyctlEv) + } + e.rawHandlers[types.ENTER_PTRACE_EVENT] = func(raw []byte, _ chan<- *event.Pair) { + ptraceEv, ok := decodeRawEvent(e, types.ENTER_PTRACE_EVENT, raw, types.NewPtraceEventFast) + if !ok { + return + } + e.tracepointEntered(ptraceEv) + } + e.rawHandlers[types.ENTER_PERF_OPEN_EVENT] = func(raw []byte, _ chan<- *event.Pair) { + perfOpenEv, ok := decodeRawEvent(e, types.ENTER_PERF_OPEN_EVENT, raw, types.NewPerfOpenEventFast) + if !ok { + return + } + e.tracepointEntered(perfOpenEv) + } +} + func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) { decoded := decode(raw) if decoded == nil { diff --git a/internal/eventloop_security_test.go b/internal/eventloop_security_test.go new file mode 100644 index 0000000..0dd9ae7 --- /dev/null +++ b/internal/eventloop_security_test.go @@ -0,0 +1,86 @@ +package internal + +import ( + "testing" + + "ior/internal/event" + "ior/internal/globalfilter" + "ior/internal/types" +) + +func TestHandlePerfOpenExitTracksReturnedFd(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + + enter := &types.PerfOpenEvent{ + EventType: types.ENTER_PERF_OPEN_EVENT, + TraceId: types.SYS_ENTER_PERF_EVENT_OPEN, + Time: 100, + Pid: 200, + Tid: 201, + AttrType: 1, + AttrSize: 64, + Config: 2, + TargetPid: 0, + Cpu: -1, + GroupFd: -1, + Flags: 0, + } + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_PERF_EVENT_OPEN, + Time: 200, + Ret: 77, + Pid: 200, + Tid: 201, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handlePerfOpenExit(ep, enter); !ok { + t.Fatal("handlePerfOpenExit returned false") + } + if ep.File == nil || ep.File.FD() != 77 { + t.Fatalf("expected resolved perf fd 77, got file=%v", ep.File) + } +} + +func TestHandlePerfOpenExitAppliesPairFilter(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{ + filter: globalfilter.Filter{ + Syscall: &globalfilter.StringFilter{Pattern: "openat"}, + }, + }) + + enter := &types.PerfOpenEvent{ + EventType: types.ENTER_PERF_OPEN_EVENT, + TraceId: types.SYS_ENTER_PERF_EVENT_OPEN, + Time: 100, + Pid: 202, + Tid: 203, + } + exit := &types.RetEvent{ + EventType: types.EXIT_RET_EVENT, + TraceId: types.SYS_EXIT_PERF_EVENT_OPEN, + Time: 200, + Ret: 1, + Pid: 202, + Tid: 203, + } + ep := &event.Pair{EnterEv: enter, ExitEv: exit} + + if ok := el.handlePerfOpenExit(ep, enter); ok { + t.Fatal("handlePerfOpenExit should reject pair due to filter mismatch") + } +} + +func TestInitRawHandlersRegistersSecurityEvents(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + if _, ok := el.rawHandlers[types.ENTER_KEYCTL_EVENT]; !ok { + t.Fatal("ENTER_KEYCTL_EVENT handler is not registered") + } + if _, ok := el.rawHandlers[types.ENTER_PTRACE_EVENT]; !ok { + t.Fatal("ENTER_PTRACE_EVENT handler is not registered") + } + if _, ok := el.rawHandlers[types.ENTER_PERF_OPEN_EVENT]; !ok { + t.Fatal("ENTER_PERF_OPEN_EVENT handler is not registered") + } +} diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go index 57f635a..b166725 100644 --- a/internal/generate/bpfhandler.go +++ b/internal/generate/bpfhandler.go @@ -101,6 +101,12 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string { return generateExtraMem(f.Name) case KindSleep: return generateExtraSleep(f.Name) + case KindKeyctl: + return generateExtraKeyctl(f.Name) + case KindPtrace: + return generateExtraPtrace() + case KindPerfOpen: + return generateExtraPerfOpen() case KindOpen: return generateExtraOpen(f) case KindMqOpen: @@ -282,6 +288,27 @@ func generateExtraSleep(name string) string { return " ev->requested_ns = -1;\n if (" + ptrExpr + " != 0) {\n struct __ior_timespec {\n __s64 tv_sec;\n __s64 tv_nsec;\n } ts = {};\n if (bpf_probe_read_user(&ts, sizeof(ts), (void *)" + ptrExpr + ") == 0) {\n ev->requested_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;\n }\n }\n" } +func generateExtraKeyctl(name string) string { + switch name { + case "sys_enter_keyctl": + return " ev->option = (__s32)ctx->args[0];\n ev->key_serial = (__s32)ctx->args[1];\n ev->value = (__u64)ctx->args[2];\n" + case "sys_enter_add_key": + return " ev->option = -1;\n ev->key_serial = (__s32)ctx->args[4];\n ev->value = (__u64)ctx->args[3];\n" + case "sys_enter_request_key": + return " ev->option = -2;\n ev->key_serial = (__s32)ctx->args[3];\n ev->value = 0;\n" + default: + return " ev->option = 0;\n ev->key_serial = 0;\n ev->value = 0;\n" + } +} + +func generateExtraPtrace() string { + return " ev->request = (__s64)ctx->args[0];\n ev->target_pid = (__s32)ctx->args[1];\n ev->data = (__u64)ctx->args[3];\n" +} + +func generateExtraPerfOpen() string { + return " ev->attr_type = 0;\n ev->attr_size = 0;\n ev->config = 0;\n if (ctx->args[0] != 0) {\n struct __ior_perf_event_attr {\n __u32 type;\n __u32 size;\n __u64 config;\n } attr = {};\n if (bpf_probe_read_user(&attr, sizeof(attr), (void *)ctx->args[0]) == 0) {\n ev->attr_type = attr.type;\n ev->attr_size = attr.size;\n ev->config = attr.config;\n }\n }\n ev->target_pid = (__s32)ctx->args[1];\n ev->cpu = (__s32)ctx->args[2];\n ev->group_fd = (__s32)ctx->args[3];\n ev->flags = (__u32)ctx->args[4];\n" +} + // eventStructName returns the C struct name for a TracepointKind. The mapping // is driven by kindRegistry so adding a new kind only requires a registry entry. func eventStructName(kind TracepointKind) string { diff --git a/internal/generate/classify.go b/internal/generate/classify.go index 4afc035..77bab7e 100644 --- a/internal/generate/classify.go +++ b/internal/generate/classify.go @@ -26,6 +26,9 @@ const ( KindPoll KindMem KindSleep + KindKeyctl + KindPtrace + KindPerfOpen ) type RetClassification string @@ -172,6 +175,16 @@ func classifyNameOnly(name string) (ClassificationResult, bool) { return ClassificationResult{Kind: KindSleep}, true case "sys_enter_clock_nanosleep": return ClassificationResult{Kind: KindSleep}, true + case "sys_enter_keyctl": + return ClassificationResult{Kind: KindKeyctl}, true + case "sys_enter_add_key": + return ClassificationResult{Kind: KindKeyctl}, true + case "sys_enter_request_key": + return ClassificationResult{Kind: KindKeyctl}, true + case "sys_enter_ptrace": + return ClassificationResult{Kind: KindPtrace}, true + case "sys_enter_perf_event_open": + return ClassificationResult{Kind: KindPerfOpen}, true case "sys_enter_mq_timedsend": return ClassificationResult{Kind: KindFd}, true case "sys_enter_mq_timedreceive": diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 5eef40f..2a12911 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -1,6 +1,7 @@ package generate import ( + "strconv" "strings" "testing" ) @@ -453,6 +454,84 @@ func TestClassifyClockNanosleep(t *testing.T) { } } +func TestClassifyKeyctl(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_keyctl", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "int", Name: "option"}, + {Type: "key_serial_t", Name: "arg2"}, + }, + }) + if r.Kind != KindKeyctl { + t.Errorf("keyctl: got kind %d, want KindKeyctl", r.Kind) + } +} + +func TestClassifyAddKey(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_add_key", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "const char *", Name: "_type"}, + {Type: "const char *", Name: "_description"}, + {Type: "const void *", Name: "_payload"}, + {Type: "size_t", Name: "plen"}, + {Type: "key_serial_t", Name: "ringid"}, + }, + }) + if r.Kind != KindKeyctl { + t.Errorf("add_key: got kind %d, want KindKeyctl", r.Kind) + } +} + +func TestClassifyRequestKey(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_request_key", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "const char *", Name: "_type"}, + {Type: "const char *", Name: "_description"}, + {Type: "const char *", Name: "_callout_info"}, + {Type: "key_serial_t", Name: "destringid"}, + }, + }) + if r.Kind != KindKeyctl { + t.Errorf("request_key: got kind %d, want KindKeyctl", r.Kind) + } +} + +func TestClassifyPtrace(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_ptrace", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "long", Name: "request"}, + {Type: "long", Name: "pid"}, + }, + }) + if r.Kind != KindPtrace { + t.Errorf("ptrace: got kind %d, want KindPtrace", r.Kind) + } +} + +func TestClassifyPerfEventOpen(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_perf_event_open", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "struct perf_event_attr *", Name: "attr_uptr"}, + {Type: "pid_t", Name: "pid"}, + {Type: "int", Name: "cpu"}, + {Type: "int", Name: "group_fd"}, + {Type: "unsigned long", Name: "flags"}, + }, + }) + if r.Kind != KindPerfOpen { + t.Errorf("perf_event_open: got kind %d, want KindPerfOpen", r.Kind) + } +} + func TestClassifyMqOpen(t *testing.T) { r := ClassifyFormat(&Format{ Name: "sys_enter_mq_open", @@ -664,6 +743,11 @@ func TestClassifySyscallPairAccepted(t *testing.T) { {"mremap", FormatMremap, FormatExitMremap, KindMem}, {"nanosleep", FormatNanosleep, FormatExitNanosleep, KindSleep}, {"clock_nanosleep", FormatClockNanosleep, FormatExitClockNanosleep, KindSleep}, + {"keyctl", syntheticEnter("keyctl", 9200), syntheticExit("keyctl", 9199), KindKeyctl}, + {"add_key", syntheticEnter("add_key", 9202), syntheticExit("add_key", 9201), KindKeyctl}, + {"request_key", syntheticEnter("request_key", 9204), syntheticExit("request_key", 9203), KindKeyctl}, + {"ptrace", syntheticEnter("ptrace", 9206), syntheticExit("ptrace", 9205), KindPtrace}, + {"perf_event_open", syntheticEnter("perf_event_open", 9208), syntheticExit("perf_event_open", 9207), KindPerfOpen}, {"mount", FormatMount, FormatExitMount, KindPathname}, {"umount", FormatUmount, FormatExitUmount, KindPathname}, {"move_mount", FormatMoveMount, FormatExitMoveMount, KindTwoFd}, @@ -716,6 +800,11 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { {"mremap", FormatMremap, FormatExitMremap, FamilyMemory}, {"nanosleep", FormatNanosleep, FormatExitNanosleep, FamilyTime}, {"clock_nanosleep", FormatClockNanosleep, FormatExitClockNanosleep, FamilyTime}, + {"keyctl", syntheticEnter("keyctl", 9300), syntheticExit("keyctl", 9299), FamilySecurity}, + {"add_key", syntheticEnter("add_key", 9302), syntheticExit("add_key", 9301), FamilySecurity}, + {"request_key", syntheticEnter("request_key", 9304), syntheticExit("request_key", 9303), FamilySecurity}, + {"ptrace", syntheticEnter("ptrace", 9306), syntheticExit("ptrace", 9305), FamilySecurity}, + {"perf_event_open", syntheticEnter("perf_event_open", 9308), syntheticExit("perf_event_open", 9307), FamilySecurity}, {"mount", FormatMount, FormatExitMount, FamilyFS}, {"umount", FormatUmount, FormatExitUmount, FamilyFS}, {"move_mount", FormatMoveMount, FormatExitMoveMount, FamilyFS}, @@ -893,6 +982,18 @@ func mqFormats(name string, enterID int) []Format { } } +func syntheticEnter(syscall string, id int) string { + return strings.Replace(strings.Replace(FormatKill, "sys_enter_kill", "sys_enter_"+syscall, 1), "ID: 183", "ID: "+itoa(id), 1) +} + +func syntheticExit(syscall string, id int) string { + return strings.Replace(strings.Replace(FormatExitKill, "sys_exit_kill", "sys_exit_"+syscall, 1), "ID: 182", "ID: "+itoa(id), 1) +} + +func itoa(v int) string { + return strconv.Itoa(v) +} + func TestClassifyFormatNoExternalFields(t *testing.T) { f := &Format{ Name: "sys_enter_test", diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 7ad076f..f1c98df 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -361,6 +361,59 @@ func TestGenerateClockNanosleepHandlerCapturesRequestedTimespec(t *testing.T) { requireContains(t, output, "ev->requested_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;") } +func TestGenerateKeyctlHandler(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("keyctl"))) + + requireContains(t, output, "struct keyctl_event *ev") + requireContains(t, output, "ev->event_type = ENTER_KEYCTL_EVENT;") + requireContains(t, output, "ev->option = (__s32)ctx->args[0];") + requireContains(t, output, "ev->key_serial = (__s32)ctx->args[1];") + requireContains(t, output, "ev->value = (__u64)ctx->args[2];") + requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;") +} + +func TestGenerateAddKeyHandler(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("add_key"))) + + requireContains(t, output, "struct keyctl_event *ev") + requireContains(t, output, "ev->event_type = ENTER_KEYCTL_EVENT;") + requireContains(t, output, "ev->option = -1;") + requireContains(t, output, "ev->key_serial = (__s32)ctx->args[4];") + requireContains(t, output, "ev->value = (__u64)ctx->args[3];") +} + +func TestGenerateRequestKeyHandler(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("request_key"))) + + requireContains(t, output, "struct keyctl_event *ev") + requireContains(t, output, "ev->event_type = ENTER_KEYCTL_EVENT;") + requireContains(t, output, "ev->option = -2;") + requireContains(t, output, "ev->key_serial = (__s32)ctx->args[3];") +} + +func TestGeneratePtraceHandler(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("ptrace"))) + + requireContains(t, output, "struct ptrace_event *ev") + requireContains(t, output, "ev->event_type = ENTER_PTRACE_EVENT;") + requireContains(t, output, "ev->request = (__s64)ctx->args[0];") + requireContains(t, output, "ev->target_pid = (__s32)ctx->args[1];") + requireContains(t, output, "ev->data = (__u64)ctx->args[3];") +} + +func TestGeneratePerfEventOpenHandler(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("perf_event_open"))) + + requireContains(t, output, "struct perf_open_event *ev") + requireContains(t, output, "ev->event_type = ENTER_PERF_OPEN_EVENT;") + requireContains(t, output, "struct __ior_perf_event_attr {") + requireContains(t, output, "ev->attr_type = attr.type;") + requireContains(t, output, "ev->config = attr.config;") + requireContains(t, output, "ev->target_pid = (__s32)ctx->args[1];") + requireContains(t, output, "ev->group_fd = (__s32)ctx->args[3];") + requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;") +} + func TestGenerateNameToHandleAtHandler(t *testing.T) { output := generateFromPair(t, FormatNameToHandleAt, FormatExitNameToHandleAt) @@ -479,6 +532,9 @@ func TestGenerateAllEventTypes(t *testing.T) { {KindPoll, "ENTER_POLL_EVENT", "EXIT_POLL_EVENT"}, {KindMem, "ENTER_MEM_EVENT", "EXIT_MEM_EVENT"}, {KindSleep, "ENTER_SLEEP_EVENT", "EXIT_SLEEP_EVENT"}, + {KindKeyctl, "ENTER_KEYCTL_EVENT", "EXIT_KEYCTL_EVENT"}, + {KindPtrace, "ENTER_PTRACE_EVENT", "EXIT_PTRACE_EVENT"}, + {KindPerfOpen, "ENTER_PERF_OPEN_EVENT", "EXIT_PERF_OPEN_EVENT"}, } for _, tt := range tests { @@ -516,6 +572,9 @@ func TestEventStructNames(t *testing.T) { {KindPoll, "poll_event"}, {KindMem, "mem_event"}, {KindSleep, "sleep_event"}, + {KindKeyctl, "keyctl_event"}, + {KindPtrace, "ptrace_event"}, + {KindPerfOpen, "perf_open_event"}, } for _, tt := range tests { @@ -534,7 +593,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} + accepted := []TracepointKind{KindFd, KindOpen, KindMqOpen, 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 188beb0..6afe4c1 100644 --- a/internal/generate/kindregistry.go +++ b/internal/generate/kindregistry.go @@ -36,6 +36,9 @@ var kindRegistry = map[TracepointKind]kindMeta{ KindPoll: {structName: "poll_event", enterAccepted: true}, KindMem: {structName: "mem_event", enterAccepted: true}, KindSleep: {structName: "sleep_event", enterAccepted: true}, + KindKeyctl: {structName: "keyctl_event", enterAccepted: true}, + KindPtrace: {structName: "ptrace_event", enterAccepted: true}, + KindPerfOpen: {structName: "perf_open_event", enterAccepted: true}, // KindNone is intentionally absent: it represents "unclassified" and is // never enter-accepted. lookupKind returns the zero kindMeta (enterAccepted=false) // for any unregistered kind, so KindNone is implicitly rejected. diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go index fcaee45..e592e5d 100644 --- a/internal/types/fastdecode.go +++ b/internal/types/fastdecode.go @@ -27,6 +27,9 @@ const ( pollEventSizeV1 = 36 memEventSize = 56 sleepEventSize = 32 + keyctlEventSize = 40 + ptraceEventSize = 48 + perfOpenEventSize = 56 ) func NewOpenEventFast(raw []byte) *OpenEvent { @@ -398,3 +401,65 @@ func NewSleepEventFast(raw []byte) *SleepEvent { s.RequestedNs = int64(binary.LittleEndian.Uint64(raw[24:32])) return s } + +func NewKeyctlEventFast(raw []byte) *KeyctlEvent { + if len(raw) < keyctlEventSize { + return nil + } + if len(raw) != keyctlEventSize { + return NewKeyctlEvent(raw) + } + k := poolOfKeyctlEvents.Get().(*KeyctlEvent) + k.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4])) + k.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8])) + k.Time = binary.LittleEndian.Uint64(raw[8:16]) + k.Pid = binary.LittleEndian.Uint32(raw[16:20]) + k.Tid = binary.LittleEndian.Uint32(raw[20:24]) + k.Option = int32(binary.LittleEndian.Uint32(raw[24:28])) + k.KeySerial = int32(binary.LittleEndian.Uint32(raw[28:32])) + k.Value = binary.LittleEndian.Uint64(raw[32:40]) + return k +} + +func NewPtraceEventFast(raw []byte) *PtraceEvent { + if len(raw) < ptraceEventSize { + return nil + } + if len(raw) != ptraceEventSize { + return NewPtraceEvent(raw) + } + p := poolOfPtraceEvents.Get().(*PtraceEvent) + p.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4])) + p.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8])) + p.Time = binary.LittleEndian.Uint64(raw[8:16]) + p.Pid = binary.LittleEndian.Uint32(raw[16:20]) + p.Tid = binary.LittleEndian.Uint32(raw[20:24]) + p.Request = int64(binary.LittleEndian.Uint64(raw[24:32])) + p.TargetPid = int32(binary.LittleEndian.Uint32(raw[32:36])) + p.Pad = int32(binary.LittleEndian.Uint32(raw[36:40])) + p.Data = binary.LittleEndian.Uint64(raw[40:48]) + return p +} + +func NewPerfOpenEventFast(raw []byte) *PerfOpenEvent { + if len(raw) < perfOpenEventSize { + return nil + } + if len(raw) != perfOpenEventSize { + return NewPerfOpenEvent(raw) + } + p := poolOfPerfOpenEvents.Get().(*PerfOpenEvent) + p.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4])) + p.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8])) + p.Time = binary.LittleEndian.Uint64(raw[8:16]) + p.Pid = binary.LittleEndian.Uint32(raw[16:20]) + p.Tid = binary.LittleEndian.Uint32(raw[20:24]) + p.AttrType = binary.LittleEndian.Uint32(raw[24:28]) + p.AttrSize = binary.LittleEndian.Uint32(raw[28:32]) + p.Config = binary.LittleEndian.Uint64(raw[32:40]) + p.TargetPid = int32(binary.LittleEndian.Uint32(raw[40:44])) + p.Cpu = int32(binary.LittleEndian.Uint32(raw[44:48])) + p.GroupFd = int32(binary.LittleEndian.Uint32(raw[48:52])) + p.Flags = binary.LittleEndian.Uint32(raw[52:56]) + return p +} diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go index 41ba7e1..1a3a8bc 100644 --- a/internal/types/fastdecode_test.go +++ b/internal/types/fastdecode_test.go @@ -274,6 +274,58 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) { t.Fatalf("sleep decode mismatch") } }) + + t.Run("KeyctlEvent", func(t *testing.T) { + ev := &KeyctlEvent{EventType: ENTER_KEYCTL_EVENT, TraceId: SYS_ENTER_KEYCTL, Time: 1, Pid: 2, Tid: 3, Option: 1, KeySerial: 2, Value: 3} + raw, _ := ev.Bytes() + + slow := NewKeyctlEvent(raw) + fast := NewKeyctlEventFast(raw) + defer slow.Recycle() + defer fast.Recycle() + if !slow.Equals(fast) { + t.Fatalf("keyctl decode mismatch") + } + }) + + t.Run("PtraceEvent", func(t *testing.T) { + ev := &PtraceEvent{EventType: ENTER_PTRACE_EVENT, TraceId: SYS_ENTER_PTRACE, Time: 1, Pid: 2, Tid: 3, Request: 4, TargetPid: 5, Data: 6} + raw, _ := ev.Bytes() + + slow := NewPtraceEvent(raw) + fast := NewPtraceEventFast(raw) + defer slow.Recycle() + defer fast.Recycle() + if !slow.Equals(fast) { + t.Fatalf("ptrace decode mismatch") + } + }) + + t.Run("PerfOpenEvent", func(t *testing.T) { + ev := &PerfOpenEvent{ + EventType: ENTER_PERF_OPEN_EVENT, + TraceId: SYS_ENTER_PERF_EVENT_OPEN, + Time: 1, + Pid: 2, + Tid: 3, + AttrType: 1, + AttrSize: 64, + Config: 5, + TargetPid: 0, + Cpu: -1, + GroupFd: -1, + Flags: 0, + } + raw, _ := ev.Bytes() + + slow := NewPerfOpenEvent(raw) + fast := NewPerfOpenEventFast(raw) + defer slow.Recycle() + defer fast.Recycle() + if !slow.Equals(fast) { + t.Fatalf("perf_open decode mismatch") + } + }) } func TestNewSocketpairEventFastKernelLayout(t *testing.T) { @@ -500,6 +552,9 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) { {name: "TwoFdEvent", decode: func(raw []byte) bool { return NewTwoFdEventFast(raw) == nil }}, {name: "PollEvent", decode: func(raw []byte) bool { return NewPollEventFast(raw) == nil }}, {name: "SleepEvent", decode: func(raw []byte) bool { return NewSleepEventFast(raw) == nil }}, + {name: "KeyctlEvent", decode: func(raw []byte) bool { return NewKeyctlEventFast(raw) == nil }}, + {name: "PtraceEvent", decode: func(raw []byte) bool { return NewPtraceEventFast(raw) == nil }}, + {name: "PerfOpenEvent", decode: func(raw []byte) bool { return NewPerfOpenEventFast(raw) == nil }}, } for _, tc := range cases { diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go index d731d30..d378794 100644 --- a/internal/types/generated_types.go +++ b/internal/types/generated_types.go @@ -106,6 +106,12 @@ const ENTER_SLEEP_EVENT = 35 const EXIT_SLEEP_EVENT = 36 const ENTER_TWO_FD_EVENT = 37 const EXIT_TWO_FD_EVENT = 38 +const ENTER_KEYCTL_EVENT = 39 +const EXIT_KEYCTL_EVENT = 40 +const ENTER_PTRACE_EVENT = 41 +const EXIT_PTRACE_EVENT = 42 +const ENTER_PERF_OPEN_EVENT = 43 +const EXIT_PERF_OPEN_EVENT = 44 const UNCLASSIFIED = 0 const READ_CLASSIFIED = 1 const WRITE_CLASSIFIED = 2 @@ -2163,3 +2169,218 @@ func (t *TwoFdEvent) Bytes() ([]byte, error) { func (t *TwoFdEvent) Recycle() { poolOfTwoFdEvents.Put(t) } + +type KeyctlEvent struct { + EventType EventType + TraceId TraceId + Time uint64 + Pid uint32 + Tid uint32 + Option int32 + KeySerial int32 + Value uint64 +} + +func (k KeyctlEvent) String() string { + return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Option:%v KeySerial:%v Value:%v", k.EventType, k.TraceId, k.Time, k.Pid, k.Tid, k.Option, k.KeySerial, k.Value) +} + +func (k KeyctlEvent) Equals(other any) bool { + otherConcrete, ok := other.(*KeyctlEvent) + if !ok { + return false + } + return k.EventType == otherConcrete.EventType && k.TraceId == otherConcrete.TraceId && k.Time == otherConcrete.Time && k.Pid == otherConcrete.Pid && k.Tid == otherConcrete.Tid && k.Option == otherConcrete.Option && k.KeySerial == otherConcrete.KeySerial && k.Value == otherConcrete.Value +} + +func (k *KeyctlEvent) GetEventType() EventType { + return k.EventType +} + +func (k *KeyctlEvent) GetTraceId() TraceId { + return k.TraceId +} + +func (k *KeyctlEvent) GetPid() uint32 { + return k.Pid +} + +func (k *KeyctlEvent) GetTid() uint32 { + return k.Tid +} + +func (k *KeyctlEvent) GetTime() uint64 { + return k.Time +} + +var poolOfKeyctlEvents = sync.Pool{ + New: func() any { return &KeyctlEvent{} }, +} + +func NewKeyctlEvent(raw []byte) *KeyctlEvent { + k := poolOfKeyctlEvents.Get().(*KeyctlEvent) + if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, k); err != nil { + *k = KeyctlEvent{} + poolOfKeyctlEvents.Put(k) + return nil + } + return k +} + +func (k *KeyctlEvent) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.LittleEndian, k) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (k *KeyctlEvent) Recycle() { + poolOfKeyctlEvents.Put(k) +} + +type PtraceEvent struct { + EventType EventType + TraceId TraceId + Time uint64 + Pid uint32 + Tid uint32 + Request int64 + TargetPid int32 + Pad int32 + Data uint64 +} + +func (p PtraceEvent) String() string { + return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Request:%v TargetPid:%v Pad:%v Data:%v", p.EventType, p.TraceId, p.Time, p.Pid, p.Tid, p.Request, p.TargetPid, p.Pad, p.Data) +} + +func (p PtraceEvent) Equals(other any) bool { + otherConcrete, ok := other.(*PtraceEvent) + if !ok { + return false + } + return p.EventType == otherConcrete.EventType && p.TraceId == otherConcrete.TraceId && p.Time == otherConcrete.Time && p.Pid == otherConcrete.Pid && p.Tid == otherConcrete.Tid && p.Request == otherConcrete.Request && p.TargetPid == otherConcrete.TargetPid && p.Pad == otherConcrete.Pad && p.Data == otherConcrete.Data +} + +func (p *PtraceEvent) GetEventType() EventType { + return p.EventType +} + +func (p *PtraceEvent) GetTraceId() TraceId { + return p.TraceId +} + +func (p *PtraceEvent) GetPid() uint32 { + return p.Pid +} + +func (p *PtraceEvent) GetTid() uint32 { + return p.Tid +} + +func (p *PtraceEvent) GetTime() uint64 { + return p.Time +} + +var poolOfPtraceEvents = sync.Pool{ + New: func() any { return &PtraceEvent{} }, +} + +func NewPtraceEvent(raw []byte) *PtraceEvent { + p := poolOfPtraceEvents.Get().(*PtraceEvent) + if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, p); err != nil { + *p = PtraceEvent{} + poolOfPtraceEvents.Put(p) + return nil + } + return p +} + +func (p *PtraceEvent) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.LittleEndian, p) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (p *PtraceEvent) Recycle() { + poolOfPtraceEvents.Put(p) +} + +type PerfOpenEvent struct { + EventType EventType + TraceId TraceId + Time uint64 + Pid uint32 + Tid uint32 + AttrType uint32 + AttrSize uint32 + Config uint64 + TargetPid int32 + Cpu int32 + GroupFd int32 + Flags uint32 +} + +func (p PerfOpenEvent) String() string { + return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v AttrType:%v AttrSize:%v Config:%v TargetPid:%v Cpu:%v GroupFd:%v Flags:%v", p.EventType, p.TraceId, p.Time, p.Pid, p.Tid, p.AttrType, p.AttrSize, p.Config, p.TargetPid, p.Cpu, p.GroupFd, p.Flags) +} + +func (p PerfOpenEvent) Equals(other any) bool { + otherConcrete, ok := other.(*PerfOpenEvent) + if !ok { + return false + } + return p.EventType == otherConcrete.EventType && p.TraceId == otherConcrete.TraceId && p.Time == otherConcrete.Time && p.Pid == otherConcrete.Pid && p.Tid == otherConcrete.Tid && p.AttrType == otherConcrete.AttrType && p.AttrSize == otherConcrete.AttrSize && p.Config == otherConcrete.Config && p.TargetPid == otherConcrete.TargetPid && p.Cpu == otherConcrete.Cpu && p.GroupFd == otherConcrete.GroupFd && p.Flags == otherConcrete.Flags +} + +func (p *PerfOpenEvent) GetEventType() EventType { + return p.EventType +} + +func (p *PerfOpenEvent) GetTraceId() TraceId { + return p.TraceId +} + +func (p *PerfOpenEvent) GetPid() uint32 { + return p.Pid +} + +func (p *PerfOpenEvent) GetTid() uint32 { + return p.Tid +} + +func (p *PerfOpenEvent) GetTime() uint64 { + return p.Time +} + +var poolOfPerfOpenEvents = sync.Pool{ + New: func() any { return &PerfOpenEvent{} }, +} + +func NewPerfOpenEvent(raw []byte) *PerfOpenEvent { + p := poolOfPerfOpenEvents.Get().(*PerfOpenEvent) + if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, p); err != nil { + *p = PerfOpenEvent{} + poolOfPerfOpenEvents.Put(p) + return nil + } + return p +} + +func (p *PerfOpenEvent) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.LittleEndian, p) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (p *PerfOpenEvent) Recycle() { + poolOfPerfOpenEvents.Put(p) +} diff --git a/internal/types/types_test.go b/internal/types/types_test.go index 2bc78fe..e0e4162 100644 --- a/internal/types/types_test.go +++ b/internal/types/types_test.go @@ -373,6 +373,95 @@ func TestSleepEventSerialization(t *testing.T) { assertEquals(t, sleepEv1.RequestedNs, sleepEv2.RequestedNs) } +func TestKeyctlEventSerialization(t *testing.T) { + keyctlEv1 := KeyctlEvent{ + EventType: ENTER_KEYCTL_EVENT, + TraceId: SYS_ENTER_KEYCTL, + Time: 7902, + Pid: 46, + Tid: 47, + Option: 1, + KeySerial: 2, + Value: 3, + } + bytes, err := keyctlEv1.Bytes() + if err != nil { + t.Error(err) + } + keyctlEv2 := NewKeyctlEvent(bytes) + + assertEquals(t, keyctlEv1.EventType, keyctlEv2.EventType) + assertEquals(t, keyctlEv1.TraceId, keyctlEv2.TraceId) + assertEquals(t, keyctlEv1.Time, keyctlEv2.Time) + assertEquals(t, keyctlEv1.Pid, keyctlEv2.Pid) + assertEquals(t, keyctlEv1.Tid, keyctlEv2.Tid) + assertEquals(t, keyctlEv1.Option, keyctlEv2.Option) + assertEquals(t, keyctlEv1.KeySerial, keyctlEv2.KeySerial) + assertEquals(t, keyctlEv1.Value, keyctlEv2.Value) +} + +func TestPtraceEventSerialization(t *testing.T) { + ptraceEv1 := PtraceEvent{ + EventType: ENTER_PTRACE_EVENT, + TraceId: SYS_ENTER_PTRACE, + Time: 7903, + Pid: 48, + Tid: 49, + Request: 4, + TargetPid: 10, + Data: 5, + } + bytes, err := ptraceEv1.Bytes() + if err != nil { + t.Error(err) + } + ptraceEv2 := NewPtraceEvent(bytes) + + assertEquals(t, ptraceEv1.EventType, ptraceEv2.EventType) + assertEquals(t, ptraceEv1.TraceId, ptraceEv2.TraceId) + assertEquals(t, ptraceEv1.Time, ptraceEv2.Time) + assertEquals(t, ptraceEv1.Pid, ptraceEv2.Pid) + assertEquals(t, ptraceEv1.Tid, ptraceEv2.Tid) + assertEquals(t, ptraceEv1.Request, ptraceEv2.Request) + assertEquals(t, ptraceEv1.TargetPid, ptraceEv2.TargetPid) + assertEquals(t, ptraceEv1.Data, ptraceEv2.Data) +} + +func TestPerfOpenEventSerialization(t *testing.T) { + perfEv1 := PerfOpenEvent{ + EventType: ENTER_PERF_OPEN_EVENT, + TraceId: SYS_ENTER_PERF_EVENT_OPEN, + Time: 7904, + Pid: 50, + Tid: 51, + AttrType: 1, + AttrSize: 64, + Config: 2, + TargetPid: 0, + Cpu: -1, + GroupFd: -1, + Flags: 0, + } + bytes, err := perfEv1.Bytes() + if err != nil { + t.Error(err) + } + perfEv2 := NewPerfOpenEvent(bytes) + + assertEquals(t, perfEv1.EventType, perfEv2.EventType) + assertEquals(t, perfEv1.TraceId, perfEv2.TraceId) + assertEquals(t, perfEv1.Time, perfEv2.Time) + assertEquals(t, perfEv1.Pid, perfEv2.Pid) + assertEquals(t, perfEv1.Tid, perfEv2.Tid) + assertEquals(t, perfEv1.AttrType, perfEv2.AttrType) + assertEquals(t, perfEv1.AttrSize, perfEv2.AttrSize) + assertEquals(t, perfEv1.Config, perfEv2.Config) + assertEquals(t, perfEv1.TargetPid, perfEv2.TargetPid) + assertEquals(t, perfEv1.Cpu, perfEv2.Cpu) + assertEquals(t, perfEv1.GroupFd, perfEv2.GroupFd) + assertEquals(t, perfEv1.Flags, perfEv2.Flags) +} + func TestEqualsDifferentTypes(t *testing.T) { openEv := OpenEvent{EventType: ENTER_OPEN_EVENT, TraceId: SYS_ENTER_OPENAT, Time: 1, Pid: 1, Tid: 1} nullEv := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 1, Tid: 1} |
