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