From 3cd431e7aac28fa7bacc37a7e751a9082287251d Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 29 May 2026 22:56:49 +0300 Subject: test(mknod): lock in mknod/mknodat path-arg classification Audit of mknod(2) found the tracing implementation already correct: sys_enter_mknod captures the real pathname from args[0] (no dirfd), while the sibling sys_enter_mknodat captures it from args[1] (after dirfd). Both are FamilyFS path_events; both exits are ret_event UNCLASSIFIED (int 0/-1). No code or doc changes were needed. Add lock-in tests guarding this behavior against regressions: - TestGenerateMknodMknodatHandlers asserts the generated BPF C reads the path from args[0] for mknod and args[1] for mknodat. - FormatMknodat/FormatExitMknodat testdata mirroring the real tracepoint layout (dfd pushes filename to args[1]). - mknodat rows added to the classify kind (KindPathname) and family (FamilyFS) test tables, matching the existing mknod coverage. Co-Authored-By: Claude Opus 4.8 --- internal/generate/codegen_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'internal/generate/codegen_test.go') 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) -- cgit v1.2.3