diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-06 10:05:22 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-06 10:05:22 +0300 |
| commit | 92ca9482e44432b85ce09ebdd8a1b4d199b1c77b (patch) | |
| tree | 353e3bf366b6d3e5f8a5fd7e623bd60a6c2cf7ba | |
| parent | d807c1ad9eb8b176e36300c6ea41744431a05bf0 (diff) | |
test: add end-to-end coverage for getrandom (READ byte count) and flock (KindFd)
Two previously-untested syscalls now have integration coverage:
- getrandom (Security family, READ_CLASSIFIED): new security-getrandom
scenario fills a 32-byte buffer via unix.Getrandom, looping past any
signal-interrupted short reads so the cumulative byte count is strictly
positive. TestSecurityGetrandom asserts enter_getrandom MinCount>=1,
bytes>=1 (locking in the READ byte-count classification end-to-end), and
a positive duration.
- flock (FamilyFS, KindFd@args[0], UNCLASSIFIED): new flock-basic scenario
opens a temp file, takes LOCK_EX then LOCK_UN via syscall.Flock, and
closes it. TestFlockBasic asserts enter_flock with PathContains the temp
filename, confirming the fd resolves to the file path via the procfd
cache.
Both scenarios use raw unix/syscall calls so the exact sys_enter tracepoints
fire, and are registered in cmd/ioworkload/scenarios.go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | cmd/ioworkload/scenario_flock.go | 39 | ||||
| -rw-r--r-- | cmd/ioworkload/scenario_security.go | 34 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 2 | ||||
| -rw-r--r-- | integrationtests/flock_test.go | 22 | ||||
| -rw-r--r-- | integrationtests/security_test.go | 23 |
5 files changed, 120 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_flock.go b/cmd/ioworkload/scenario_flock.go new file mode 100644 index 0000000..a54f5d0 --- /dev/null +++ b/cmd/ioworkload/scenario_flock.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "path/filepath" + "syscall" +) + +// flockBasic exercises the flock syscall end-to-end. It opens a temp file, +// acquires an exclusive advisory lock (LOCK_EX), releases it (LOCK_UN), then +// closes the file and removes the temp dir. +// +// flock is unprivileged (FamilyFS) and captured as KindFd at args[0]; ior +// resolves that fd to the underlying file path via the procfd cache, so the +// enter_flock record carries the temp filename. Its return value is +// UNCLASSIFIED (0 on success), so this scenario only locks in the enter path. +func flockBasic() error { + dir, cleanup, err := makeTempDir("flock-basic") + if err != nil { + return err + } + defer cleanup() + + path := filepath.Join(dir, "flockfile.txt") + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if err != nil { + return fmt.Errorf("open: %w", err) + } + defer syscall.Close(fd) + + // Use raw syscall.Flock so the exact sys_enter_flock tracepoint fires. + if err := syscall.Flock(fd, syscall.LOCK_EX); err != nil { + return fmt.Errorf("flock LOCK_EX: %w", err) + } + if err := syscall.Flock(fd, syscall.LOCK_UN); err != nil { + return fmt.Errorf("flock LOCK_UN: %w", err) + } + return nil +} diff --git a/cmd/ioworkload/scenario_security.go b/cmd/ioworkload/scenario_security.go index f3cf9ba..6e14f0c 100644 --- a/cmd/ioworkload/scenario_security.go +++ b/cmd/ioworkload/scenario_security.go @@ -11,6 +11,40 @@ import ( var keySpecProcessKeyringArg = ^uintptr(1) +// getrandomBufLen is the requested length of the getrandom buffer. getrandom +// reports the number of random bytes written into buf as its return value, +// which ior READ-classifies as a byte count. +const getrandomBufLen = 32 + +// securityGetrandom exercises the getrandom syscall end-to-end. getrandom +// (FamilyTime/Security, READ_CLASSIFIED) fills buf with random bytes and +// returns the count placed there, so ior records that count as the exit byte +// total. +// +// getrandom may return fewer bytes than requested only when interrupted by a +// signal; to keep the byte count deterministic we loop until the full buffer +// is filled, accumulating any short reads. The enter tracepoint is null-kind +// (no fd/path), so this scenario only locks in the READ byte-count classifi- +// cation, not a path/fd dimension. +func securityGetrandom() error { + buf := make([]byte, getrandomBufLen) + for off := 0; off < len(buf); { + // Use unix.Getrandom so the exact sys_enter_getrandom tracepoint fires. + n, err := unix.Getrandom(buf[off:], 0) + if err != nil { + if err == unix.EINTR { + continue + } + return fmt.Errorf("getrandom: %w", err) + } + if n <= 0 { + return fmt.Errorf("getrandom returned non-positive count %d", n) + } + off += n + } + return nil +} + func securityKeysPtracePerf() error { nr, err := securitySyscallNumbers(runtime.GOARCH) if err != nil { diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 50f84e1..869c9ec 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -148,6 +148,8 @@ var scenarios = map[string]func() error{ "pidfd-getfd-failure": pidfdGetfdFailure, "security-keys-ptrace-perf": securityKeysPtracePerf, "security-landlock": securityLandlockCreateRuleset, + "security-getrandom": securityGetrandom, + "flock-basic": flockBasic, "iouring-setup": iouringSetup, "iouring-enter": iouringEnter, "iouring-register": iouringRegister, diff --git a/integrationtests/flock_test.go b/integrationtests/flock_test.go new file mode 100644 index 0000000..667f0fb --- /dev/null +++ b/integrationtests/flock_test.go @@ -0,0 +1,22 @@ +package integrationtests + +import "testing" + +// TestFlockBasic asserts end-to-end tracing of the FamilyFS flock syscall. The +// flock-basic scenario opens a temp file, takes an exclusive advisory lock +// (LOCK_EX) and releases it (LOCK_UN), then closes the file. +// +// flock is captured as KindFd at args[0]; ior resolves that fd to the +// underlying file path via the procfd cache, so the enter_flock record carries +// the temp filename. Its return value is UNCLASSIFIED, so we only assert the +// enter path (path + count), not a byte total. +func TestFlockBasic(t *testing.T) { + runScenario(t, "flock-basic", []ExpectedEvent{ + { + PathContains: "flockfile.txt", + Tracepoint: "enter_flock", + Comm: "ioworkload", + MinCount: 1, + }, + }) +} diff --git a/integrationtests/security_test.go b/integrationtests/security_test.go index 8d41691..f4d38ea 100644 --- a/integrationtests/security_test.go +++ b/integrationtests/security_test.go @@ -62,6 +62,29 @@ func TestSecurityKeysPtracePerf(t *testing.T) { } } +var getrandomTraceArgs = []string{"-trace-syscalls", "getrandom"} + +// TestSecurityGetrandom asserts end-to-end tracing of the getrandom syscall +// (Security family, READ_CLASSIFIED). The security-getrandom scenario fills a +// 32-byte buffer via unix.Getrandom, looping until the full buffer is filled. +// +// getrandom reports the number of random bytes written into buf as its return +// value, which ior records as the exit byte count. The scenario loops past any +// signal-interrupted short reads, so the cumulative byte count is strictly +// positive; we assert bytes>=1 (the per-call count can be split across reads, +// so a conservative >=1 minimum is the safe invariant) plus a positive +// duration. The enter tracepoint is null-kind (no fd/path dimension), so only +// the READ byte-count classification is locked in here. +func TestSecurityGetrandom(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "security-getrandom", []ExpectedEvent{ + {Tracepoint: "enter_getrandom", Comm: "ioworkload", MinCount: 1}, + }, getrandomTraceArgs) + + exp := ExpectedEvent{Tracepoint: "enter_getrandom", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, exp, 1) + assertEventDurationPositive(t, result, exp) +} + var landlockTraceArgs = []string{"-trace-syscalls", "landlock_create_ruleset,landlock_add_rule,close"} // TestSecurityLandlockCreateRuleset asserts end-to-end tracing of the |
