summaryrefslogtreecommitdiff
path: root/cmd/ioworkload/scenario_security.go
blob: 9b06a12f028132c11bf9816e15058c0dc7930e32 (plain)
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package main

import (
	"fmt"
	"runtime"
	"syscall"
	"unsafe"
)

var keySpecProcessKeyringArg = ^uintptr(1)

func securityKeysPtracePerf() error {
	nr, err := securitySyscallNumbers(runtime.GOARCH)
	if err != nil {
		return err
	}

	// Best-effort probes: these syscalls may fail with EPERM/EACCES depending on
	// policy, but the tracepoints are still exercised.
	runKeySyscalls(nr)
	runPtraceSyscall(nr)
	runPerfEventOpenSyscall(nr)
	return nil
}

type securitySyscalls struct {
	addKey        uintptr
	requestKey    uintptr
	keyctl        uintptr
	ptrace        uintptr
	perfEventOpen uintptr
}

func securitySyscallNumbers(arch string) (securitySyscalls, error) {
	switch arch {
	case "amd64":
		return securitySyscalls{
			addKey:        248,
			requestKey:    249,
			keyctl:        250,
			ptrace:        101,
			perfEventOpen: 298,
		}, nil
	case "arm64":
		return securitySyscalls{
			addKey:        217,
			requestKey:    218,
			keyctl:        219,
			ptrace:        117,
			perfEventOpen: 241,
		}, nil
	default:
		return securitySyscalls{}, fmt.Errorf("security syscall numbers not defined for GOARCH=%s", arch)
	}
}

func runKeySyscalls(nr securitySyscalls) {
	keyType, _ := syscall.BytePtrFromString("user")
	desc, _ := syscall.BytePtrFromString("ior-key")
	payload := []byte("ior")

	var payloadPtr uintptr
	if len(payload) > 0 {
		payloadPtr = uintptr(unsafe.Pointer(&payload[0]))
	}

	_, _, _ = syscall.Syscall6(
		nr.addKey,
		uintptr(unsafe.Pointer(keyType)),
		uintptr(unsafe.Pointer(desc)),
		payloadPtr,
		uintptr(len(payload)),
		keySpecProcessKeyringArg,
		0,
	)

	_, _, _ = syscall.Syscall6(
		nr.requestKey,
		uintptr(unsafe.Pointer(keyType)),
		uintptr(unsafe.Pointer(desc)),
		0,
		keySpecProcessKeyringArg,
		0,
		0,
	)

	_, _, _ = syscall.Syscall6(
		nr.keyctl,
		0,
		keySpecProcessKeyringArg,
		0,
		0,
		0,
		0,
	)
}

func runPtraceSyscall(nr securitySyscalls) {
	_, _, _ = syscall.Syscall6(
		nr.ptrace,
		uintptr(syscall.PTRACE_PEEKDATA),
		^uintptr(0),
		0,
		0,
		0,
		0,
	)
}

type perfEventAttr struct {
	Type   uint32
	Size   uint32
	Config uint64
}

func runPerfEventOpenSyscall(nr securitySyscalls) {
	attr := perfEventAttr{
		Type:   1, // PERF_TYPE_SOFTWARE
		Size:   uint32(unsafe.Sizeof(perfEventAttr{})),
		Config: 0, // PERF_COUNT_SW_CPU_CLOCK
	}
	fd, _, _ := syscall.Syscall6(
		nr.perfEventOpen,
		uintptr(unsafe.Pointer(&attr)),
		0,
		^uintptr(0), // cpu = -1
		^uintptr(0), // group_fd = -1
		0,
		0,
	)
	if int64(fd) >= 0 {
		_ = syscall.Close(int(fd))
	}
}

// landlockSyscallNumber is the landlock_create_ruleset syscall number.
// It is 444 on both amd64 and arm64 (and most modern arches).
func landlockSyscallNumber(arch string) (uintptr, error) {
	switch arch {
	case "amd64", "arm64":
		return 444, nil
	default:
		return 0, fmt.Errorf("landlock_create_ruleset syscall number not defined for GOARCH=%s", arch)
	}
}

// landlockRulesetAttr mirrors struct landlock_ruleset_attr (uapi/linux/landlock.h).
// handled_access_fs is the set of filesystem access rights the ruleset will
// govern; handled_access_net (added in Landlock ABI v4) governs TCP access.
// We declare both fields so unsafe.Sizeof yields the current kernel struct size.
type landlockRulesetAttr struct {
	handledAccessFs  uint64
	handledAccessNet uint64
}

// LANDLOCK_ACCESS_FS_READ_FILE (uapi/linux/landlock.h) — a benign, always-valid
// filesystem access right used to populate a minimal, valid ruleset attribute.
const landlockAccessFsReadFile = 0x4

// securityLandlockCreateRuleset exercises the landlock_create_ruleset syscall
// end-to-end. It builds a minimal valid struct landlock_ruleset_attr (handling
// only LANDLOCK_ACCESS_FS_READ_FILE), calls landlock_create_ruleset(&attr,
// sizeof(attr), 0) to obtain a fresh ruleset fd, and closes it.
//
// SAFETY: this scenario deliberately does NOT call landlock_restrict_self.
// landlock_restrict_self irreversibly sandboxes the calling process for its
// entire lifetime, which would break the shared integration-test runner.
// Creating and closing a ruleset fd has no process-wide side effects.
//
// The call is tolerated to fail with ENOSYS/EOPNOTSUPP (kernel < 5.13 or
// Landlock LSM disabled): the sys_enter_landlock_create_ruleset tracepoint
// fires before any such error, so the tracer still observes the enter event.
func securityLandlockCreateRuleset() error {
	nr, err := landlockSyscallNumber(runtime.GOARCH)
	if err != nil {
		return err
	}

	attr := landlockRulesetAttr{
		handledAccessFs: landlockAccessFsReadFile,
	}
	fd, _, _ := syscall.Syscall(
		nr,
		uintptr(unsafe.Pointer(&attr)),
		unsafe.Sizeof(attr),
		0, // flags = 0: create a real ruleset (not the ABI-version query)
	)
	if int64(fd) >= 0 {
		_ = syscall.Close(int(fd))
	}
	return nil
}