diff options
| -rw-r--r-- | internal/generate/classify_test.go | 2 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 29 | ||||
| -rw-r--r-- | internal/generate/testdata.go | 38 |
3 files changed, 69 insertions, 0 deletions
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 25c7068..5b233c2 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -1692,6 +1692,7 @@ func TestClassifySyscallPairAccepted(t *testing.T) { {"pread64", FormatPread64, FormatExitPread64, KindFd}, {"symlink", FormatSymlink, FormatExitSymlink, KindName}, {"mknod", FormatMknod, FormatExitMknod, KindPathname}, + {"mknodat", FormatMknodat, FormatExitMknodat, KindPathname}, {"execve", FormatExecve, FormatExitExecve, KindExec}, {"execveat", FormatExecveat, FormatExitExecveat, KindExec}, {"accept", FormatAccept, FormatExitAccept, KindAccept}, @@ -1806,6 +1807,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { family SyscallFamily }{ {"mknod", FormatMknod, FormatExitMknod, FamilyFS}, + {"mknodat", FormatMknodat, FormatExitMknodat, FamilyFS}, {"execve", FormatExecve, FormatExitExecve, FamilyProcess}, {"execveat", FormatExecveat, FormatExitExecveat, FamilyProcess}, {"accept", FormatAccept, FormatExitAccept, FamilyNetwork}, diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 5179fee..7e0e122 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -631,6 +631,35 @@ func TestGenerateAccessFaccessatHandlers(t *testing.T) { requireContains(t, faccessatOut, "bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[1]);") } +// TestGenerateMknodMknodatHandlers locks in the generated BPF C for mknod(2) +// and its dirfd-relative sibling mknodat(2). Both create a filesystem node and +// capture a real path into a path_event's pathname member, but from DIFFERENT +// argument slots: mknod(2) has no dirfd so its path is at args[0], whereas +// mknodat(2) takes dfd at args[0] and the path at args[1]. This guards against +// a regression that would read the wrong arg (e.g. capturing mknodat's dirfd +// as a path, or dropping mknod's path entirely). The exit side is a ret_event +// (int 0/-1, UNCLASSIFIED) — verified via the shared ret_event handler shape. +func TestGenerateMknodMknodatHandlers(t *testing.T) { + exitMknod := strings.Replace(FormatExitRead, "sys_exit_read", "sys_exit_mknod", 1) + exitMknod = strings.Replace(exitMknod, "ID: 843", "ID: 893", 1) + mknodOut := generateFromPair(t, FormatMknod, exitMknod) + requireContains(t, mknodOut, `SEC("tracepoint/syscalls/sys_enter_mknod")`) + requireContains(t, mknodOut, "struct path_event *ev") + requireContains(t, mknodOut, "ev->event_type = ENTER_PATH_EVENT;") + requireContains(t, mknodOut, "ev->trace_id = SYS_ENTER_MKNOD;") + // mknod(2): path (filename) is at args[0] — no dirfd precedes it. + requireContains(t, mknodOut, "bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[0]);") + + exitMknodat := strings.Replace(FormatExitRead, "sys_exit_read", "sys_exit_mknodat", 1) + exitMknodat = strings.Replace(exitMknodat, "ID: 843", "ID: 895", 1) + mknodatOut := generateFromPair(t, FormatMknodat, exitMknodat) + requireContains(t, mknodatOut, `SEC("tracepoint/syscalls/sys_enter_mknodat")`) + requireContains(t, mknodatOut, "struct path_event *ev") + requireContains(t, mknodatOut, "ev->trace_id = SYS_ENTER_MKNODAT;") + // mknodat(2): dfd is at args[0], so the path (filename) is at args[1]. + requireContains(t, mknodatOut, "bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[1]);") +} + func TestGenerateFcntlHandler(t *testing.T) { output := generateFromPair(t, FormatFcntl, FormatExitFcntl) diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go index b8f8f17..25392b1 100644 --- a/internal/generate/testdata.go +++ b/internal/generate/testdata.go @@ -1228,6 +1228,44 @@ format: print fmt: "filename: 0x%08lx, mode: 0x%08lx, dev: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->mode)), ((unsigned long)(REC->dev)) ` +// FormatMknodat mirrors the real sys_enter_mknodat tracepoint format. Unlike +// mknod(2), mknodat(2) takes a directory fd (dfd) as its first argument, which +// pushes the filename (the real path) to args[1]. The classifier must capture +// the path from args[1] here, not args[0] (which is the dirfd). +const FormatMknodat = `name: sys_enter_mknodat +ID: 896 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:int __syscall_nr; offset:8; size:4; signed:1; + field:int dfd; offset:16; size:8; signed:0; + field:const char * filename; offset:24; size:8; signed:0; + field:umode_t mode; offset:32; size:8; signed:0; + field:unsigned dev; offset:40; size:8; signed:0; + +print fmt: "dfd: 0x%08lx, filename: 0x%08lx, mode: 0x%08lx, dev: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->mode)), ((unsigned long)(REC->dev)) +` + +// FormatExitMknodat mirrors the real sys_exit_mknodat tracepoint format. Like +// mknod, mknodat returns a plain int (0 on success, -1 on error) and is +// therefore classified as a ret_event (UNCLASSIFIED return value). +const FormatExitMknodat = `name: sys_exit_mknodat +ID: 895 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:int __syscall_nr; offset:8; size:4; signed:1; + field:long ret; offset:16; size:8; signed:1; + +print fmt: "0x%lx", REC->ret +` + const FormatExitMknod = `name: sys_exit_mknod ID: 893 format: |
