diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-01 10:07:16 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-01 10:07:16 +0300 |
| commit | 2bb05af8e0b08910c01045d7cd7cd375e6b83613 (patch) | |
| tree | c06a458a15cb6550ed03480162d86c8a09588aa8 /internal/generate | |
| parent | 7243b7c079f5d63b32e3e8dd639146d2714efa9d (diff) | |
test(generate): remove redundant pure-classification unit tests
Classification correctness (which family/kind/return-class a syscall maps
to) is verified by inspection against the man pages and the classifier
rules, not by dedicated unit tests. The tracing-relevant outcome — which
fd/path/byte-count the generated BPF C actually captures — is covered by
the GenerateTracepointsC codegen tests and the end-to-end integration
tests, all of which are retained.
Removed:
- internal/generate/family_test.go (ClassifySyscallFamily / .Family table)
- internal/generate/retclassify_test.go (ClassifyRet read/write/transfer/
unclassified tables)
- ~70 pure-classification tests trimmed from classify_test.go, keeping only
the GenerateTracepointsC codegen/tracing tests plus the shared helpers
(mustParseAll, mqFormats, phaseAFormats, syntheticEnter/Exit, itoa) used
by codegen_test.go.
- pure-classification funcs interleaved in codegen_test.go
(TestClassifyRet*Unclassified, TestClassifyTkillFallsThroughToNull,
Test{Mkdirat,Rmdir}FamilyAndKindMatchSiblings).
Kept all TestGenerate* handler tests (they assert the generated BPF C
captures the correct fd/path/arg-index/return classification), the
isNoreturnSyscall tests, docs-drift guards, eventloop dispatch tests, and
the integration suite — so every affected syscall still has tracing
coverage. No tracing gaps discovered.
generate package: go test (incl. -race) green; mage build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal/generate')
| -rw-r--r-- | internal/generate/classify_test.go | 3149 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 166 | ||||
| -rw-r--r-- | internal/generate/family_test.go | 365 | ||||
| -rw-r--r-- | internal/generate/retclassify_test.go | 185 |
4 files changed, 13 insertions, 3852 deletions
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 2f74e8a..bd03af4 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -6,1315 +6,16 @@ import ( "testing" ) -func classifyFromData(t *testing.T, data string) ClassificationResult { - t.Helper() - f := mustParseOne(t, data) - return ClassifyFormat(&f) -} - -func TestClassifyFdRead(t *testing.T) { - r := classifyFromData(t, FormatRead) - if r.Kind != KindFd { - t.Errorf("read: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyFdClose(t *testing.T) { - r := classifyFromData(t, FormatClose) - if r.Kind != KindFd { - t.Errorf("close: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyFdPread64(t *testing.T) { - r := classifyFromData(t, FormatPread64) - if r.Kind != KindFd { - t.Errorf("pread64: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyFdWrite(t *testing.T) { - r := classifyFromData(t, FormatWrite) - if r.Kind != KindFd { - t.Errorf("write: got kind %d, want KindFd", r.Kind) - } -} - -// TestClassifyFdLseek pins lseek(2) as a single-fd KindFd event. lseek's -// tracepoint exposes a generic "fd" field of an fd-like type at args[0], so it -// classifies via classifyByField exactly like read/write — the fd is captured -// from args[0], while the off_t offset and whence args are ignored. The return -// value (resulting file offset) is asserted UNCLASSIFIED separately in -// retclassify_test.go (TestClassifyRetUnclassified) and end-to-end in -// TestClassifyRetExitLseek below. -func TestClassifyFdLseek(t *testing.T) { - r := classifyFromData(t, FormatLseek) - if r.Kind != KindFd { - t.Errorf("lseek: got kind %d, want KindFd", r.Kind) - } -} - -// TestClassifyRetExitLseek locks in that sys_exit_lseek is a plain ret_event -// (KindRet) and that ClassifyRet keeps it UNCLASSIFIED. lseek returns the new -// file OFFSET (bytes-from-start), not a transferred byte count, so it must -// never be classified as READ/WRITE/TRANSFER — doing so would inflate I/O byte -// accounting. -func TestClassifyRetExitLseek(t *testing.T) { - r := classifyFromData(t, FormatExitLseek) - if r.Kind != KindRet { - t.Errorf("lseek exit: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_lseek"); got != Unclassified { - t.Errorf("lseek exit: ClassifyRet = %q, want UNCLASSIFIED", got) - } -} - -func TestClassifyFdPidfdGetfd(t *testing.T) { - r := classifyFromData(t, FormatPidfdGetfd) - if r.Kind != KindFd { - t.Errorf("pidfd_getfd: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyOpenOpenat(t *testing.T) { - r := classifyFromData(t, FormatOpenat) - if r.Kind != KindOpen { - t.Errorf("openat: got kind %d, want KindOpen", r.Kind) - } -} - -func TestClassifyOpenOpen(t *testing.T) { - r := classifyFromData(t, FormatOpen) - if r.Kind != KindOpen { - t.Errorf("open: got kind %d, want KindOpen", r.Kind) - } -} - -func TestClassifyOpenOpenat2(t *testing.T) { - r := classifyFromData(t, FormatOpenat2) - if r.Kind != KindOpen { - t.Errorf("openat2: got kind %d, want KindOpen", r.Kind) - } -} - -func TestClassifyPathnameCreat(t *testing.T) { - r := classifyFromData(t, FormatCreat) - if r.Kind != KindPathname { - t.Errorf("creat: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "pathname" { - t.Errorf("creat: PathnameField = %q, want pathname", r.PathnameField) - } -} - -func TestClassifyPathnameUnlink(t *testing.T) { - r := classifyFromData(t, FormatUnlink) - if r.Kind != KindPathname { - t.Errorf("unlink: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "pathname" { - t.Errorf("unlink: PathnameField = %q, want pathname", r.PathnameField) - } -} - -// TestClassifyPathnameUtime locks in that utime's args[0] "filename" is -// captured as a real path. utime(2) changes a file's access/modification -// times; its filename argument is a genuine filesystem path (not a -// domain/host name string), so it must classify as KindPathname with the -// path wired to the "filename" field — matching siblings utimensat/futimesat. -func TestClassifyPathnameUtime(t *testing.T) { - r := classifyFromData(t, FormatUtime) - if r.Kind != KindPathname { - t.Errorf("utime: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "filename" { - t.Errorf("utime: PathnameField = %q, want filename", r.PathnameField) - } -} - -// TestClassifyPathnameAccess locks in that access(2)'s args[0] argument -// (kernel field "filename") is captured as a real filesystem path. access(2) -// checks the calling process's permissions for a file by path; the path is at -// args[0] (there is no dirfd), so it must classify as KindPathname with the -// path wired to the "filename" field. If this regresses to a non-path kind, -// access's pathname would silently stop being captured. -func TestClassifyPathnameAccess(t *testing.T) { - r := classifyFromData(t, FormatAccess) - if r.Kind != KindPathname { - t.Errorf("access: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "filename" { - t.Errorf("access: PathnameField = %q, want filename", r.PathnameField) - } -} - -// TestClassifyPathnameFaccessat locks in that faccessat(2) — access(2)'s -// dirfd-relative sibling — also classifies as KindPathname with the path wired -// to the "filename" field. The path is at args[1] (args[0] is the dirfd); the -// argument-index difference from access(2) is verified separately in the -// codegen tests (TestGenerateAccessFaccessatHandlers). -func TestClassifyPathnameFaccessat(t *testing.T) { - r := classifyFromData(t, FormatFaccessat) - if r.Kind != KindPathname { - t.Errorf("faccessat: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "filename" { - t.Errorf("faccessat: PathnameField = %q, want filename", r.PathnameField) - } -} - -func TestClassifyNameRename(t *testing.T) { - r := classifyFromData(t, FormatRename) - if r.Kind != KindName { - t.Errorf("rename: got kind %d, want KindName", r.Kind) - } -} - -func TestClassifyNameLinkat(t *testing.T) { - r := classifyFromData(t, FormatLinkat) - if r.Kind != KindName { - t.Errorf("linkat: got kind %d, want KindName", r.Kind) - } -} - -func TestClassifyNameSymlink(t *testing.T) { - r := classifyFromData(t, FormatSymlink) - if r.Kind != KindName { - t.Errorf("symlink: got kind %d, want KindName", r.Kind) - } -} - -func TestClassifyFcntl(t *testing.T) { - r := classifyFromData(t, FormatFcntl) - if r.Kind != KindFcntl { - t.Errorf("fcntl: got kind %d, want KindFcntl", r.Kind) - } -} - -func TestClassifyDup(t *testing.T) { - r := classifyFromData(t, FormatDup) - if r.Kind != KindFd { - t.Errorf("dup: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyDup2(t *testing.T) { - r := classifyFromData(t, FormatDup2) - if r.Kind != KindFd { - t.Errorf("dup2: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyDup3(t *testing.T) { - r := classifyFromData(t, FormatDup3) - if r.Kind != KindDup3 { - t.Errorf("dup3: got kind %d, want KindDup3", r.Kind) - } -} - -func TestClassifyOpenByHandleAt(t *testing.T) { - r := classifyFromData(t, FormatOpenByHandleAt) - if r.Kind != KindOpenByHandleAt { - t.Errorf("open_by_handle_at: got kind %d, want KindOpenByHandleAt", r.Kind) - } -} - -func TestClassifyNameToHandleAt(t *testing.T) { - r := classifyFromData(t, FormatNameToHandleAt) - if r.Kind != KindPathname { - t.Errorf("name_to_handle_at: got kind %d, want KindPathname", r.Kind) - } - if r.PathnameField != "name" { - t.Errorf("name_to_handle_at: PathnameField = %q, want name", r.PathnameField) - } -} - -func TestClassifyNullSync(t *testing.T) { - r := classifyFromData(t, FormatSync) - if r.Kind != KindNull { - t.Errorf("sync: got kind %d, want KindNull", r.Kind) - } -} - -func TestClassifyNullSyslog(t *testing.T) { - r := classifyFromData(t, FormatSyslog) - if r.Kind != KindNull { - t.Errorf("syslog: got kind %d, want KindNull", r.Kind) - } -} - -// TestClassifyNullGetcwd pins getcwd as KindNull at enter. -// -// getcwd's args[0] is `char *buf`, an OUTPUT buffer: the kernel writes the -// absolute cwd path into it and the contents only become valid AFTER the -// syscall returns (sys_exit). Reading buf at enter would capture an empty or -// garbage string, so getcwd must NOT be classified as a path-input syscall. -// KindNull is the correct enter kind; the cwd is resolved at exit from -// /proc/<tid>/cwd (see eventLoop.handleNullExit). This test locks that in: -// - the enter kind is KindNull (not KindPathname/KindName), and -// - no pathname field is captured from the buffer at enter. -func TestClassifyNullGetcwd(t *testing.T) { - r := classifyFromData(t, FormatGetcwd) - if r.Kind != KindNull { - t.Errorf("getcwd: got kind %d, want KindNull", r.Kind) - } - if r.Kind == KindPathname || r.Kind == KindName { - t.Errorf("getcwd: enter must not capture output buf as a path, got kind %d", r.Kind) - } - if r.PathnameField != "" { - t.Errorf("getcwd: no enter-time pathname field expected, got %q", r.PathnameField) - } -} - -// TestClassifyByFieldGetcwdBufNotPath is a defense-in-depth lock-in: even if -// the name-only KindNull override for getcwd were removed, the generic -// field-based classifier must not treat `char *buf` as a pathname. Only the -// field names pathname/path/filename/newname are path-like; "buf" is not, so -// classifyByField must report no match for getcwd's output buffer. -func TestClassifyByFieldGetcwdBufNotPath(t *testing.T) { - if r, ok := classifyByField("char *", "buf"); ok { - t.Errorf("getcwd buf: char *buf must not classify as a field kind, got %d", r.Kind) - } -} - -func TestClassifyNullIoUring(t *testing.T) { - r := classifyFromData(t, FormatIoUringEnter) - if r.Kind != KindFd { - t.Errorf("io_uring_enter: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyIoUringRegister(t *testing.T) { - r := classifyFromData(t, FormatIoUringRegister) - if r.Kind != KindFd { - t.Errorf("io_uring_register: got kind %d, want KindFd", r.Kind) - } -} - -func TestClassifyRetExitRead(t *testing.T) { - r := classifyFromData(t, FormatExitRead) - if r.Kind != KindRet { - t.Errorf("exit_read: got kind %d, want KindRet", r.Kind) - } -} - -func TestClassifyRetExitWrite(t *testing.T) { - r := classifyFromData(t, FormatExitWrite) - if r.Kind != KindRet { - t.Errorf("exit_write: got kind %d, want KindRet", r.Kind) - } -} - -func TestClassifyRetExitOpenat(t *testing.T) { - r := classifyFromData(t, FormatExitOpenat) - if r.Kind != KindRet { - t.Errorf("exit_openat: got kind %d, want KindRet", r.Kind) - } -} - -func TestClassifyRetExitPread64(t *testing.T) { - r := classifyFromData(t, FormatExitPread64) - if r.Kind != KindRet { - t.Errorf("exit_pread64: got kind %d, want KindRet", r.Kind) - } -} - -func TestClassifyRetExitSymlink(t *testing.T) { - r := classifyFromData(t, FormatExitSymlink) - if r.Kind != KindRet { - t.Errorf("exit_symlink: got kind %d, want KindRet", r.Kind) - } -} - -func TestClassifyPathnameMknod(t *testing.T) { - r := classifyFromData(t, FormatMknod) - if r.Kind != KindPathname { - t.Errorf("mknod: got kind %d, want KindPathname", r.Kind) - } -} - -func TestClassifyExecExecve(t *testing.T) { - r := classifyFromData(t, FormatExecve) - if r.Kind != KindExec { - t.Errorf("execve: got kind %d, want KindExec", r.Kind) - } -} - -func TestClassifyExecExecveat(t *testing.T) { - r := classifyFromData(t, FormatExecveat) - if r.Kind != KindExec { - t.Errorf("execveat: got kind %d, want KindExec", r.Kind) - } -} - -func TestClassifyAccept(t *testing.T) { - r := classifyFromData(t, FormatAccept) - if r.Kind != KindAccept { - t.Errorf("accept: got kind %d, want KindAccept", r.Kind) - } -} - -func TestClassifyAccept4(t *testing.T) { - r := classifyFromData(t, FormatAccept4) - if r.Kind != KindAccept { - t.Errorf("accept4: got kind %d, want KindAccept", r.Kind) - } -} - -func TestClassifyExitAccept(t *testing.T) { - r := classifyFromData(t, FormatExitAccept) - if r.Kind != KindAccept { - t.Errorf("exit_accept: got kind %d, want KindAccept", r.Kind) - } -} - -func TestClassifyExitAccept4(t *testing.T) { - r := classifyFromData(t, FormatExitAccept4) - if r.Kind != KindAccept { - t.Errorf("exit_accept4: got kind %d, want KindAccept", r.Kind) - } -} - -func TestClassifySocketFdSyscallsByName(t *testing.T) { - tests := []string{ - "bind", - "connect", - "listen", - "shutdown", - "getsockname", - "getpeername", - "getsockopt", - "setsockopt", - } - for _, name := range tests { - t.Run(name, func(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_" + name, - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "int", Name: "sockfd"}, - }, - }) - if r.Kind != KindFd { - t.Errorf("%s: got kind %d, want KindFd", name, r.Kind) - } - }) - } -} - -// TestClassifySyncFamilyFdSyscallsByName locks in that the filesystem-sync -// family (fsync/fdatasync/syncfs/sync_file_range) is classified as KindFd on -// enter. Each of these takes an open file descriptor as args[0]: -// - int fsync(int fd) -// - int fdatasync(int fd) -// - int syncfs(int fd) -// - int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned flags) -// -// so their enter tracepoint carries a leading fd field and must capture -// fd=args[0] into a fd_event (KindFd), matching the generated -// handle_sys_enter_* handlers. (Plain sync() takes no args and is KindNull; -// it is asserted separately in the classification table test.) -func TestClassifySyncFamilyFdSyscallsByName(t *testing.T) { - tests := []string{ - "fsync", - "fdatasync", - "syncfs", - "sync_file_range", - } - for _, name := range tests { - t.Run(name, func(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_" + name, - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "int", Name: "fd"}, - }, - }) - if r.Kind != KindFd { - t.Errorf("%s: got kind %d, want KindFd", name, r.Kind) - } - }) - } -} - -// TestClassifyExitSyncfs locks in that the syncfs exit tracepoint is classified -// as KindRet. syncfs(2) returns int (0 on success, -1 on error) and transfers -// no bytes, so its exit format carries a single "ret" field and must map to a -// plain ret_event (KindRet, Unclassified) — matching the generated -// sys_exit_syncfs handler and its fsync/fdatasync siblings. -func TestClassifyExitSyncfs(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_syncfs", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Errorf("exit_syncfs: got kind %d, want KindRet", r.Kind) - } -} - -// TestClassifyFallocateEnterFd locks in that the fallocate enter tracepoint is -// classified as KindFd with the fd captured at args[0]. -// -// int fallocate(int fd, int mode, off_t offset, off_t len) -// -// fallocate(2) manipulates the allocated disk space for the file referred to -// by fd (args[0]); the remaining mode/offset/len args are NOT captured, exactly -// like its fd-based siblings fadvise64(2)/ftruncate(2)/sync_file_range(2) which -// also carry trailing offset/len/advice args but only record args[0]. The -// leading "fd" external field must select KindFd so the generated -// handle_sys_enter_fallocate emits ev->fd = ctx->args[0] into a fd_event. -func TestClassifyFallocateEnterFd(t *testing.T) { - f := &Format{ - Name: "sys_enter_fallocate", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "int", Name: "fd"}, - {Type: "int", Name: "mode"}, - {Type: "loff_t", Name: "offset"}, - {Type: "loff_t", Name: "len"}, - }, - } - r := ClassifyFormat(f) - if r.Kind != KindFd { - t.Fatalf("enter_fallocate: got kind %d, want KindFd", r.Kind) - } - // fd is the first real argument (args[0]); FieldNumber skips __syscall_nr. - if got := f.FieldNumber("fd"); got != 0 { - t.Errorf("enter_fallocate: fd field number = %d, want 0 (args[0])", got) - } -} - -// TestClassifyExitFallocateUnclassifiedRet locks in that the fallocate exit -// tracepoint is classified as KindRet and Unclassified. fallocate(2) returns -// int (0 on success, -1 on error) — that return is a status code, NOT a -// transferred byte count, so its exit format carries a single "ret" field and -// must map to a plain ret_event (KindRet) whose ret_type stays UNCLASSIFIED. -// Misclassifying it as a READ/WRITE/TRANSFER byte count would be a real bug, -// since fallocate allocates space but reports no transferred bytes. -func TestClassifyExitFallocateUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_fallocate", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_fallocate: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_fallocate"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_fallocate) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifySetuidNullEnter locks in that the setuid enter tracepoint is -// classified as KindNull. setuid(2) is "int setuid(uid_t uid)" — its single -// argument is a numeric user ID, NOT a file descriptor or a path. It must -// therefore map to a null_event (no argument capture); misclassifying it as an -// fd-bearing kind would be a real bug, since the uid is not an fd and capturing -// it as one would attribute the credential change to a bogus file. The whole -// credential-setting cluster (setuid/seteuid/setresuid/setreuid/setfsuid and -// the gid analogues) shares this KindNull treatment with the getuid readers. -func TestClassifySetuidNullEnter(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_setuid", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "uid"}, - }, - }) - if r.Kind != KindNull { - t.Fatalf("enter_setuid: got kind %d, want KindNull", r.Kind) - } - // The uid argument must never be captured as a file descriptor or path. - if r.PathnameField != "" { - t.Errorf("enter_setuid: unexpected PathnameField %q, want empty", r.PathnameField) - } -} - -// TestClassifyExitSetuidUnclassifiedRet locks in that the setuid exit -// tracepoint is classified as KindRet and Unclassified. setuid(2) returns int -// (0 on success, -1 on error) — that return is a status code, NOT a -// transferred byte count, so its exit format carries a single "ret" field and -// must map to a plain ret_event (KindRet) whose ret_type stays UNCLASSIFIED. -// Misclassifying it as a READ/WRITE/TRANSFER byte count would be a real bug. -func TestClassifyExitSetuidUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_setuid", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_setuid: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_setuid"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_setuid) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifySetpgidNullEnter locks in the setpgid(2) enter classification -// using the syscall's REAL tracepoint fields. setpgid(pid_t pid, pid_t pgid) -// sets the process group ID of a process; both arguments are process/process- -// group identifiers (the kernel tracepoint declares them as field type -// "pid_t"), NOT file descriptors and NOT filesystem paths. The audit concern is -// that args[0] ("pid") could be mistaken for an fd: it must not be. setpgid has -// no fd or path argument, so its enter format must classify as KindNull -// (null_event) — matching its session/process-group siblings setsid/getsid/ -// getpgid/getpgrp and the explicit name-only mapping in classify.go. Using the -// real "pid"/"pgid" pid_t fields here (rather than a synthetic arg0) proves the -// generic field heuristics never capture them: isFdType only matches int/ -// unsigned int/unsigned long (not "pid_t"), and the fd heuristic additionally -// requires the field name be "fd", which neither "pid" nor "pgid" is. -func TestClassifySetpgidNullEnter(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_setpgid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - {Type: "pid_t", Name: "pid"}, - {Type: "pid_t", Name: "pgid"}, - }, - }) - if r.Kind != KindNull { - t.Fatalf("enter_setpgid: got kind %d, want KindNull", r.Kind) - } - // Neither pid argument must be captured as a file descriptor or path. - if r.PathnameField != "" { - t.Errorf("enter_setpgid: unexpected PathnameField %q, want empty", r.PathnameField) - } -} - -// TestClassifyExitSetpgidUnclassifiedRet locks in that the setpgid exit -// tracepoint is classified as KindRet and Unclassified. setpgid(2) returns int -// (0 on success, -1 on error) — a status code, NOT a transferred byte count — -// so its exit format carries a single "ret" field and must map to a plain -// ret_event (KindRet) whose ret_type stays UNCLASSIFIED. This matches its -// sibling setsid/getsid (asserted in retclassify_test.go); misclassifying it as -// a READ/WRITE/TRANSFER byte count would be a real bug. -func TestClassifyExitSetpgidUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_setpgid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_setpgid: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_setpgid"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_setpgid) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifyGetgidNullEnter locks in the getgid(2) enter classification using -// the syscall's REAL tracepoint fields. getgid(2) is "gid_t getgid(void)" — it -// takes NO arguments at all, so its enter format carries only the synthetic -// __syscall_nr field and must classify as KindNull (null_event capturing -// nothing). This matches the no-arg id-returning reader cluster -// getuid/geteuid/getegid/getpid/getppid/gettid and the explicit name-only -// mapping in classify.go. With no real argument fields there is nothing the fd -// or path heuristics could latch onto, so PathnameField must stay empty. -func TestClassifyGetgidNullEnter(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_getgid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - }, - }) - if r.Kind != KindNull { - t.Fatalf("enter_getgid: got kind %d, want KindNull", r.Kind) - } - // getgid has no arguments, so nothing must be captured as a path/fd. - if r.PathnameField != "" { - t.Errorf("enter_getgid: unexpected PathnameField %q, want empty", r.PathnameField) - } -} - -// TestClassifyExitGetgidUnclassifiedRet locks in that the getgid exit -// tracepoint is classified as KindRet and Unclassified. getgid(2) returns the -// real group ID (gid_t) of the caller and ALWAYS succeeds — its return is a -// numeric credential identifier, NOT a transferred byte count and never an -// error status. Its exit format carries a single "ret" field and must map to a -// plain ret_event (KindRet) whose ret_type stays UNCLASSIFIED. Misclassifying -// the gid as a READ/WRITE/TRANSFER byte count would be a real bug. This matches -// its no-arg reader siblings getuid/getpid (no byte semantics on their return). -func TestClassifyExitGetgidUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_getgid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_getgid: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_getgid"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_getgid) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifyGettidNullEnter locks in the gettid(2) enter classification using -// the syscall's REAL tracepoint fields. gettid(2) is "pid_t gettid(void)" — it -// takes NO arguments at all, so its enter format carries only the synthetic -// __syscall_nr field and must classify as KindNull (null_event capturing -// nothing). This matches the no-arg id-returning reader cluster -// getuid/geteuid/getegid/getpid/getppid/getgid and the explicit name-only -// mapping in classify.go. With no real argument fields there is nothing the fd -// or path heuristics could latch onto, so PathnameField must stay empty. -func TestClassifyGettidNullEnter(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_gettid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - }, - }) - if r.Kind != KindNull { - t.Fatalf("enter_gettid: got kind %d, want KindNull", r.Kind) - } - // gettid has no arguments, so nothing must be captured as a path/fd. - if r.PathnameField != "" { - t.Errorf("enter_gettid: unexpected PathnameField %q, want empty", r.PathnameField) - } -} - -// TestClassifyExitGettidUnclassifiedRet locks in that the gettid exit -// tracepoint is classified as KindRet and Unclassified. gettid(2) returns the -// caller's thread ID (pid_t) and ALWAYS succeeds — its return is a numeric -// thread identifier, NOT a transferred byte count and never an error status. -// Its exit format carries a single "ret" field and must map to a plain -// ret_event (KindRet) whose ret_type stays UNCLASSIFIED. Misclassifying the tid -// as a READ/WRITE/TRANSFER byte count would be a real bug. This matches its -// no-arg reader siblings getuid/getpid/getgid (no byte semantics on their -// return). -func TestClassifyExitGettidUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_gettid", - ExternalFields: []Field{ - {Type: "int", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_gettid: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_gettid"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_gettid) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifyExitGetpeername locks in that the getpeername exit tracepoint is -// classified as KindRet. getpeername(2) returns int (0 on success, -1 on -// error), so its exit format carries a single "ret" field and must map to a -// plain ret_event, matching the generated sys_exit_getpeername handler. -func TestClassifyExitGetpeername(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_getpeername", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Errorf("exit_getpeername: got kind %d, want KindRet", r.Kind) - } -} - -// TestClassifyExitGetsockname locks in that the getsockname exit tracepoint is -// classified as KindRet. getsockname(2) returns int (0 on success, -1 on -// error), so its exit format carries a single "ret" field and must map to a -// plain ret_event, matching the generated sys_exit_getsockname handler — just -// like its sibling getpeername (see TestClassifyExitGetpeername). -func TestClassifyExitGetsockname(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_getsockname", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Errorf("exit_getsockname: got kind %d, want KindRet", r.Kind) - } -} - -// TestClassifySetsockoptEnterFd locks in that the setsockopt enter tracepoint is -// classified as KindFd with the socket fd captured at args[0]. The signature is: -// -// int setsockopt(int sockfd, int level, int optname, -// const void *optval, socklen_t optlen) -// -// setsockopt(2) sets a socket option on the socket referred to by sockfd -// (args[0]); the remaining level/optname/optval/optlen args are NOT captured. -// optval is a userspace pointer (not a transferred byte buffer we account for), -// so only the leading sockfd matters — exactly like its KindFd network siblings -// bind/connect/getsockname/getpeername/getsockopt and the explicit name-only -// mapping in classify.go. The classification is name-only, so this asserts the -// kind holds even when the enter format carries the real "fd" field. Capturing -// any later arg as the fd, or failing to capture args[0], would be a real bug. -func TestClassifySetsockoptEnterFd(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_enter_setsockopt", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "int", Name: "fd"}, - {Type: "int", Name: "level"}, - {Type: "int", Name: "optname"}, - {Type: "char *", Name: "optval"}, - {Type: "int", Name: "optlen"}, - }, - }) - if r.Kind != KindFd { - t.Fatalf("enter_setsockopt: got kind %d, want KindFd", r.Kind) - } - // optval is a userspace pointer, never a pathname we record. - if r.PathnameField != "" { - t.Errorf("enter_setsockopt: unexpected PathnameField %q, want empty", r.PathnameField) - } -} - -// TestClassifyExitSetsockoptUnclassifiedRet locks in that the setsockopt exit -// tracepoint is classified as KindRet and Unclassified. setsockopt(2) returns -// int (0 on success, -1 on error) — a status code, NOT a transferred byte count -// — so its exit format carries a single "ret" field and must map to a plain -// ret_event (KindRet) whose ret_type stays UNCLASSIFIED, matching the generated -// sys_exit_setsockopt handler and its sibling getsockopt. Misclassifying it as a -// READ/WRITE/TRANSFER byte count would be a real bug. -func TestClassifyExitSetsockoptUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_setsockopt", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_setsockopt: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_setsockopt"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_setsockopt) = %q, want UNCLASSIFIED", got) - } -} - -// TestClassifyExitGetsockoptUnclassifiedRet mirrors the setsockopt exit lock-in -// for its read-side sibling getsockopt(2), which likewise returns int (0/-1) and -// must map to a plain ret_event (KindRet, UNCLASSIFIED) — never a READ byte -// count, even though it copies option data into a userspace buffer via a -// userspace pointer rather than returning a transferred byte total. -func TestClassifyExitGetsockoptUnclassifiedRet(t *testing.T) { - r := ClassifyFormat(&Format{ - Name: "sys_exit_getsockopt", - ExternalFields: []Field{ - {Type: "long", Name: "__syscall_nr"}, - {Type: "long", Name: "ret"}, - }, - }) - if r.Kind != KindRet { - t.Fatalf("exit_getsockopt: got kind %d, want KindRet", r.Kind) - } - if got := ClassifyRet("sys_exit_getsockopt"); got != Unclassified { - t.Errorf("ClassifyRet(sys_exit_getsockopt) = %q, want UNCLASSIFIED", got) - } -} - -func TestClassifySocket(t *testing.T) { - r := classifyFromData(t, FormatSocket) - if r.Kind != KindSocket { - t.Errorf("socket: got kind %d, want KindSocket", r.Kind) - } -} - -func TestClassifySocketpair(t *testing.T) { - r := classifyFromData(t, FormatSocketpair) - if r.Kind != KindSocketpair { - t.Errorf("socketpair: got kind %d, want KindSocketpair", r.Kind) - } -} - -func TestClassifyExitSocketpair(t *testing.T) { - r := classifyFromData(t, FormatExitSocketpair) - if r.Kind != KindSocketpair { - t.Errorf("exit_socketpair: got kind %d, want KindSocketpair", r.Kind) - } -} - -// TestClassifySocketpairNotFd is a regression lock-in for the socketpair(2) -// audit (task c00). socketpair(int domain, int type, int protocol, int sv[2]) -// takes the address-family/domain constant as args[0] (named "family" in the -// tracepoint format), NOT a file descriptor. The created fds are written into -// the OUTPUT array sv[2] (args[3]) and are only valid after the call returns. -// socketpair must therefore be KindSocketpair (read sv[2] at exit), never -// KindFd, which would record the domain integer as a bogus fd. Pin that the -// name-based override wins so a future field-shape change cannot make it fall -// through to the generic KindFd path. -func TestClassifySocketpairNotFd(t *testing.T) { - r := classifyFromData(t, FormatSocketpair) - if r.Kind == KindFd { - t.Fatal("socketpair classified as KindFd: args[0] is the domain constant, not an fd") - } - if r.Kind != KindSocketpair { - t.Errorf("socketpair: got kind %d, want KindSocketpair", r.Kind) - } -} - -func TestClassifyPipe(t *testing.T) { - r := classifyFromData(t, FormatPipe) - if r.Kind != KindPipe { - t.Errorf("pipe: got kind %d, want KindPipe", r.Kind) - } -} - -func TestClassifyPipe2(t *testing.T) { - r := classifyFromData(t, FormatPipe2) - if r.Kind != KindPipe { - t.Errorf("pipe2: got kind %d, want KindPipe", r.Kind) - } -} - -func TestClassifyExitPipe(t *testing.T) { - r := classifyFromData(t, FormatExitPipe) - if r.Kind != KindPipe { - t.Errorf("exit_pipe: got kind %d, want KindPipe", r.Kind) - } -} - -func TestClassifyExitPipe2(t *testing.T) { - r := classifyFromData(t, FormatExitPipe2) - if r.Kind != KindPipe { - t.Errorf("exit_pipe2: got kind %d, want KindPipe", r.Kind) - } -} - -// TestClassifyPipeNotFd locks in that pipe(2) is NOT classified as KindFd. -// pipe's args[0] is an OUTPUT pointer to int[2] (the two created fds are written -// there by the kernel and are only valid AFTER the syscall returns), NOT an fd -// argument. Capturing args[0] as an fd would attribute the pipe to a bogus -// descriptor; pipe must use the pipe-specific KindPipe path that reads the fd -// pair from the userspace buffer at exit. Same pitfall as socketpair (task c00). -func TestClassifyPipeNotFd(t *testing.T) { - for _, name := range []string{"pipe", "pipe2"} { - r := classifyFromData(t, map[string]string{ - "pipe": FormatPipe, - "pipe2": FormatPipe2, - }[name]) - if r.Kind == KindFd { - t.Fatalf("%s classified as KindFd: args[0] is an output ptr, not an fd", name) - } - if r.Kind != KindPipe { - t.Errorf("%s: got kind %d, want KindPipe", name, r.Kind) - } - } -} - -// TestClassifyPipeUnclassifiedRet locks in that the pipe and pipe2 exit -// tracepoints stay UNCLASSIFIED. pipe(2)/pipe2(2) return int (0 on success, -// -1 on error) — a status code, NOT a transferred byte count. They must not be -// in retClassifications and must never map to READ/WRITE/TRANSFER, which would -// misreport phantom bytes. The created fds are surfaced via fd0/fd1 in the -// pipe_event, not via the return value. -func TestClassifyPipeUnclassifiedRet(t *testing.T) { - for _, name := range []string{"sys_exit_pipe", "sys_exit_pipe2"} { - if got := ClassifyRet(name); got != Unclassified { - t.Errorf("ClassifyRet(%s) = %q, want UNCLASSIFIED", name, got) - } - } -} - -func TestClassifyEventfd(t *testing.T) { - r := classifyFromData(t, FormatEventfd) - if r.Kind != KindEventfd { - t.Errorf("eventfd: got kind %d, want KindEventfd", r.Kind) - } -} - -func TestClassifyEventfd2(t *testing.T) { - r := classifyFromData(t, FormatEventfd2) - if r.Kind != KindEventfd { - t.Errorf("eventfd2: got kind %d, want KindEventfd", r.Kind) - } -} - -func TestClassifyExitEventfd(t *testing.T) { - r := classifyFromData(t, FormatExitEventfd) - if r.Kind != KindEventfd { - t.Errorf("exit_eventfd: got kind %d, want KindEventfd", r.Kind) |
