summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 22:43:32 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 22:43:32 +0300
commit6ca4d5ddacaff05d8bd82a5e9a6dfbb39ac111c9 (patch)
treea0b4469a9eb96bfb0b5a09d5f086219782040982
parent7a9839917461b12c810329ccb8fd3c6de06902d2 (diff)
feat: add keyctl ptrace perf_event_open tracing (task 77)
-rw-r--r--cmd/ioworkload/scenario_security.go134
-rw-r--r--cmd/ioworkload/scenario_security_test.go38
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/security_test.go13
-rw-r--r--internal/c/generated_tracepoints.c61
-rw-r--r--internal/c/generated_tracepoints_result.txt10
-rw-r--r--internal/c/types.h44
-rw-r--r--internal/eventloop_exit.go55
-rw-r--r--internal/eventloop_runtime.go25
-rw-r--r--internal/eventloop_security_test.go86
-rw-r--r--internal/generate/bpfhandler.go27
-rw-r--r--internal/generate/classify.go13
-rw-r--r--internal/generate/classify_test.go101
-rw-r--r--internal/generate/codegen_test.go61
-rw-r--r--internal/generate/kindregistry.go3
-rw-r--r--internal/types/fastdecode.go65
-rw-r--r--internal/types/fastdecode_test.go55
-rw-r--r--internal/types/generated_types.go221
-rw-r--r--internal/types/types_test.go89
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,