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, |
