package integrationtests import ( "strings" "testing" ) var securityTraceArgs = []string{"-trace-syscalls", "keyctl,add_key,request_key,ptrace,perf_event_open,close"} func TestSecurityKeysPtracePerf(t *testing.T) { result, _ := runScenarioResultWithIorArgs(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}, }, securityTraceArgs) // Key and ptrace operations are not fd/path based and should stay untracked. assertTracepointPathPrefix(t, result, "enter_keyctl", "N:file") assertTracepointPathPrefix(t, result, "enter_add_key", "N:file") assertTracepointPathPrefix(t, result, "enter_request_key", "N:file") assertTracepointPathPrefix(t, result, "enter_ptrace", "N:file") for _, tracepoint := range []string{ "enter_keyctl", "enter_add_key", "enter_request_key", "enter_ptrace", "enter_perf_event_open", } { assertEventDurationPositive(t, result, ExpectedEvent{ Tracepoint: tracepoint, Comm: "ioworkload", }) } // perf_event_open may fail (e.g. EPERM), so assert conditional behavior: // if a tracked perf descriptor appears, we must also observe a close on it. perfOpenTracked := totalTracepointPathCount(result, "enter_perf_event_open", "perf:") perfCloseTracked := totalTracepointPathCount(result, "enter_close", "perf:") if perfOpenTracked == 0 { if perfCloseTracked != 0 { t.Fatalf("unexpected tracked perf close events without tracked perf open: close=%d", perfCloseTracked) } return } assertTracepointPathPrefix(t, result, "enter_perf_event_open", "perf:") assertTracepointPathPrefix(t, result, "enter_close", "perf:") if perfCloseTracked < perfOpenTracked { t.Fatalf("tracked perf close count too small: close=%d open=%d", perfCloseTracked, perfOpenTracked) } // Tracked perf descriptor path should be stable between open and close records. openPaths := uniqueTracepointPathsWithPrefix(result, "enter_perf_event_open", "perf:") closePaths := uniqueTracepointPathsWithPrefix(result, "enter_close", "perf:") for path := range openPaths { if _, ok := closePaths[path]; !ok { t.Fatalf("tracked perf descriptor %q seen on open but not close", path) } } } var landlockTraceArgs = []string{"-trace-syscalls", "landlock_create_ruleset,close"} // TestSecurityLandlockCreateRuleset asserts end-to-end tracing of the // Security-family landlock_create_ruleset syscall. The security-landlock // scenario calls landlock_create_ruleset(&attr, sizeof(attr), 0) and closes // the returned ruleset fd (it deliberately never calls landlock_restrict_self, // which would irreversibly sandbox the shared test runner). // // The sys_enter tracepoint fires before any ENOSYS/EOPNOTSUPP error, so the // enter event is observed regardless of whether Landlock is enabled on the // running kernel; we therefore assert the enter MinCount unconditionally. // landlock_create_ruleset is KindEventfd (it captures flags at args[2]); when // the ruleset fd is successfully created and registered, it resolves to the // "landlockfd:" path label, which is also seen on the matching close. func TestSecurityLandlockCreateRuleset(t *testing.T) { result, _ := runScenarioResultWithIorArgs(t, "security-landlock", []ExpectedEvent{ {Tracepoint: "enter_landlock_create_ruleset", Comm: "ioworkload", MinCount: 1}, }, landlockTraceArgs) assertEventDurationPositive(t, result, ExpectedEvent{ Tracepoint: "enter_landlock_create_ruleset", Comm: "ioworkload", }) // landlock_create_ruleset may fail (ENOSYS on kernels < 5.13, or // EOPNOTSUPP when the Landlock LSM is disabled). If a tracked ruleset fd // appears, it must carry the "landlockfd:" label and be closed under the // same label; otherwise we must observe no tracked landlock close events. landlockOpenTracked := totalTracepointPathCount(result, "enter_landlock_create_ruleset", "landlockfd:") landlockCloseTracked := totalTracepointPathCount(result, "enter_close", "landlockfd:") if landlockOpenTracked == 0 { if landlockCloseTracked != 0 { t.Fatalf("unexpected tracked landlock close events without tracked ruleset open: close=%d", landlockCloseTracked) } return } assertTracepointPathPrefix(t, result, "enter_landlock_create_ruleset", "landlockfd:") assertTracepointPathPrefix(t, result, "enter_close", "landlockfd:") if landlockCloseTracked < landlockOpenTracked { t.Fatalf("tracked landlock close count too small: close=%d open=%d", landlockCloseTracked, landlockOpenTracked) } // The tracked ruleset descriptor path should be stable between the // create_ruleset record and its matching close record. openPaths := uniqueTracepointPathsWithPrefix(result, "enter_landlock_create_ruleset", "landlockfd:") closePaths := uniqueTracepointPathsWithPrefix(result, "enter_close", "landlockfd:") for path := range openPaths { if _, ok := closePaths[path]; !ok { t.Fatalf("tracked landlock descriptor %q seen on create but not close", path) } } } func uniqueTracepointPathsWithPrefix(result TestResult, tracepoint, wantPrefix string) map[string]struct{} { paths := make(map[string]struct{}) for _, rec := range result.Records { if !strings.Contains(rec.TraceID.String(), tracepoint) { continue } if !strings.HasPrefix(rec.Path, wantPrefix) { continue } paths[rec.Path] = struct{}{} } return paths }