1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
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 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
// Security-family landlock_create_ruleset and landlock_add_rule syscalls. The
// security-landlock scenario calls landlock_create_ruleset(&attr, sizeof(attr),
// 0), adds a PATH_BENEATH rule via landlock_add_rule(ruleset_fd, rule_type,
// &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 tracepoints fire before any ENOSYS/EOPNOTSUPP error, so both
// enter events are observed regardless of whether Landlock is enabled on the
// running kernel; we therefore assert the enter MinCounts 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.
// landlock_add_rule captures ruleset_fd (KindFd) at args[0]; its return value
// (0 or -1) is UNCLASSIFIED, not a byte count.
func TestSecurityLandlockCreateRuleset(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "security-landlock", []ExpectedEvent{
{Tracepoint: "enter_landlock_create_ruleset", Comm: "ioworkload", MinCount: 1},
{Tracepoint: "enter_landlock_add_rule", Comm: "ioworkload", MinCount: 1},
}, landlockTraceArgs)
for _, tracepoint := range []string{
"enter_landlock_create_ruleset",
"enter_landlock_add_rule",
} {
assertEventDurationPositive(t, result, ExpectedEvent{
Tracepoint: tracepoint,
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
}
|