tetragon

Форк
0
/
validation.go 
480 строк · 11.4 Кб
1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright Authors of Tetragon
3

4
package btf
5

6
import (
7
	"errors"
8
	"fmt"
9
	"os"
10
	"reflect"
11
	"strings"
12

13
	"github.com/cilium/ebpf/btf"
14
	"github.com/cilium/tetragon/pkg/arch"
15
	"github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1"
16
	"github.com/cilium/tetragon/pkg/logger"
17
	"github.com/cilium/tetragon/pkg/syscallinfo"
18
)
19

20
// ValidationWarn is used to mark that validation was not successful but it's not
21
// clear that the spec is problematic. Callers may use this error to issue a
22
// warning instead of aborting
23
type ValidationWarn struct {
24
	s string
25
}
26

27
func (e *ValidationWarn) Error() string {
28
	return e.s
29
}
30

31
// ValidationFailed is used to mark that validation was not successful and that
32
// the we should not continue with loading this spec.
33
type ValidationFailed struct {
34
	s string
35
}
36

37
func (e *ValidationFailed) Error() string {
38
	return e.s
39
}
40

41
// ValidateKprobeSpec validates a kprobe spec based on BTF information
42
//
43
// NB: turns out we need more than BTF information for the validation (see
44
// syscalls). We still keep this code in the btf package for now, and we can
45
// move it once we found a better home for it.
46
func ValidateKprobeSpec(bspec *btf.Spec, call string, kspec *v1alpha1.KProbeSpec) error {
47
	var fn *btf.Func
48

49
	origCall := call
50
	err := bspec.TypeByName(call, &fn)
51
	if err != nil && kspec.Syscall {
52
		// Try with system call prefix
53
		call, err = arch.AddSyscallPrefix(call)
54
		if err == nil {
55
			err = bspec.TypeByName(call, &fn)
56
		}
57
	}
58

59
	// BTF include multiple candidates
60
	if errors.Is(err, btf.ErrMultipleMatches) {
61
		var allTypes, fnTypes []btf.Type
62
		allTypes, err = bspec.AnyTypesByName(call)
63
		if err == nil {
64
			for _, typ := range allTypes {
65
				// Assert again the appropriate type
66
				if _, ok := typ.(*btf.Func); ok {
67
					fnTypes = append(fnTypes, typ)
68
				}
69
			}
70
			// TypeByName() above ensures btf.Func type, but Check again so semantically we are correct
71
			if len(fnTypes) > 0 {
72
				logger.GetLogger().Infof("BTF metadata includes '%d' matched candidates on call %q, using first one", len(fnTypes), call)
73
				// take first one.
74
				reflect.ValueOf(&fn).Elem().Set(reflect.ValueOf(fnTypes[0]))
75
			}
76
		}
77
	}
78

79
	if err != nil {
80
		if kspec.Syscall {
81
			return &ValidationFailed{
82
				s: fmt.Sprintf("syscall %q (or %q) %v", origCall, call, err),
83
			}
84
		}
85
		return &ValidationFailed{s: fmt.Sprintf("call %q %v", call, err)}
86
	}
87

88
	proto, ok := fn.Type.(*btf.FuncProto)
89
	if !ok {
90
		return fmt.Errorf("kprobe spec validation failed: proto for call %s not found", call)
91
	}
92

93
	// Syscalls are special.
94
	// We (at least in recent kernels) hook into __x64_sys_FOO for
95
	// syscalls, but this function's signature does not allow us to check
96
	// arguments. Moreover, the does not seem to be a reliable way of doing
97
	// so with our BTF files.
98
	if kspec.Syscall {
99
		ret, ok := proto.Return.(*btf.Int)
100
		if !ok {
101
			return fmt.Errorf("kprobe spec validation failed: syscall return type is not Int")
102
		}
103
		if ret.Name != "long int" {
104
			return fmt.Errorf("kprobe spec validation failed: syscall return type is not long int")
105
		}
106

107
		if len(proto.Params) != 1 {
108
			return fmt.Errorf("kprobe spec validation failed: syscall with more than one arg")
109
		}
110

111
		ptr, ok := proto.Params[0].Type.(*btf.Pointer)
112
		if !ok {
113
			return fmt.Errorf("kprobe spec validation failed: syscall arg is not pointer")
114
		}
115

116
		cnst, ok := ptr.Target.(*btf.Const)
117
		if !ok {
118
			return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer")
119
		}
120

121
		arg, ok := cnst.Type.(*btf.Struct)
122
		if !ok {
123
			return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer to struct")
124
		}
125

126
		if arg.Name != "pt_regs" {
127
			return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer to struct pt_regs")
128
		}
129

130
		// next try to deduce the syscall name.
131
		// NB: this might change in different kernels so if we fail we treat it as a warning
132
		prefix := "__x64_sys_"
133
		if !strings.HasPrefix(call, prefix) {
134
			return &ValidationWarn{s: fmt.Sprintf("could not get the function prototype for %s: arguments will not be verified", call)}
135
		}
136
		syscall := strings.TrimPrefix(call, prefix)
137
		return validateSycall(kspec, syscall)
138
	}
139

140
	fnNArgs := uint32(len(proto.Params))
141
	for i := range kspec.Args {
142
		specArg := &kspec.Args[i]
143
		if specArg.Index >= fnNArgs {
144
			return fmt.Errorf("kprobe arg %d has an invalid index: %d based on prototype: %s", i, specArg.Index, proto)
145
		}
146
		arg := proto.Params[int(specArg.Index)]
147
		paramTyStr := getKernelType(arg.Type)
148
		if !typesCompatible(specArg.Type, paramTyStr) {
149
			return &ValidationWarn{s: fmt.Sprintf("type (%s) of argument %d does not match spec type (%s)\n", paramTyStr, specArg.Index, specArg.Type)}
150
		}
151
	}
152

153
	if kspec.Return {
154
		retTyStr := getKernelType(proto.Return)
155
		if kspec.ReturnArg == nil {
156
			return &ValidationWarn{s: "return is set to true, but there is no return arg specified"}
157
		}
158
		if !typesCompatible(kspec.ReturnArg.Type, retTyStr) {
159
			return &ValidationWarn{s: fmt.Sprintf("return type (%s) does not match spec return type (%s)\n", retTyStr, kspec.ReturnArg.Type)}
160
		}
161
	}
162

163
	return nil
164
}
165

166
func getKernelType(arg btf.Type) string {
167
	suffix := ""
168
	ptr, ok := arg.(*btf.Pointer)
169
	if ok {
170
		arg = ptr.Target
171
		_, ok = arg.(*btf.Void)
172
		if ok {
173
			return "void *"
174
		}
175
		suffix = suffix + " *"
176
	}
177
	num, ok := arg.(*btf.Int)
178
	if ok {
179
		return num.Name + suffix
180
	}
181
	strct, ok := arg.(*btf.Struct)
182
	if ok {
183
		return "struct " + strct.Name + suffix
184
	}
185

186
	union, ok := arg.(*btf.Union)
187
	if ok {
188
		return "union " + union.Name + suffix
189
	}
190

191
	enum, ok := arg.(*btf.Enum)
192
	if ok {
193
		prefix := "u"
194
		if enum.Signed {
195
			prefix = "s"
196
		}
197
		switch enum.Size {
198
		case 1:
199
		case 2:
200
		case 4:
201
		case 8:
202
		default:
203
			// Not sure what to do here, so just dump the type name
204
			return arg.TypeName() + suffix
205
		}
206
		return fmt.Sprintf("%s%d%s", prefix, 8*enum.Size, suffix)
207
	}
208

209
	cnst, ok := arg.(*btf.Const)
210
	if ok {
211
		// NB: ignore const
212
		ty := cnst.Type
213
		if ptr != nil {
214
			// NB: if this was a pointer, reconstruct the type without const
215
			ty = &btf.Pointer{
216
				Target: ty,
217
			}
218
		}
219
		return getKernelType(ty)
220
	}
221

222
	// TODO - add more types, above is enough to make validation_test pass
223
	return arg.TypeName() + suffix
224
}
225

226
func typesCompatible(specTy string, kernelTy string) bool {
227
	switch specTy {
228
	case "nop":
229
		return true
230

231
	case "uint64":
232
		switch kernelTy {
233
		case "u64", "void *", "long unsigned int":
234
			return true
235
		}
236
	case "int64":
237
		switch kernelTy {
238
		case "s64":
239
			return true
240
		}
241
	case "int32":
242
		switch kernelTy {
243
		case "s32", "int":
244
			return true
245
		}
246
	case "int16":
247
		switch kernelTy {
248
		case "s16", "short int":
249
			return true
250
		}
251
	case "uint16":
252
		switch kernelTy {
253
		case "u16", "short unsigned int":
254
			return true
255
		}
256
	case "uint8":
257
		switch kernelTy {
258
		case "u8", "unsigned char":
259
			return true
260
		}
261
	case "size_t":
262
		switch kernelTy {
263
		case "size_t":
264
			return true
265
		}
266
	case "char_buf", "string", "int8":
267
		switch kernelTy {
268
		case "const char *", "char *", "char":
269
			return true
270
		}
271
	case "char_iovec":
272
		switch kernelTy {
273
		case "const struct iovec *", "struct iovec *":
274
			return true
275
		}
276
	case "fd":
277
		switch kernelTy {
278
		case "unsigned int", "int", "unsigned long", "long":
279
			return true
280
		}
281
	case "int":
282
		switch kernelTy {
283
		case "unsigned int", "int", "unsigned long", "long", "uid_t", "gid_t", "u32", "s32":
284
			return true
285
		}
286
	case "filename":
287
		switch kernelTy {
288
		case "struct filename *":
289
			return true
290
		}
291
	case "file":
292
		switch kernelTy {
293
		case "struct file *":
294
			return true
295
		}
296
	case "path":
297
		switch kernelTy {
298
		case "struct path *":
299
			return true
300
		}
301
	case "bpf_attr":
302
		switch kernelTy {
303
		case "union bpf_attr *":
304
			return true
305
		}
306
	case "perf_event":
307
		switch kernelTy {
308
		case "struct perf_event *":
309
			return true
310
		}
311
	case "bpf_map":
312
		switch kernelTy {
313
		case "struct bpf_map *":
314
			return true
315
		}
316
	case "user_namespace":
317
		switch kernelTy {
318
		case "struct user_namespace *":
319
			return true
320
		}
321
	case "capability":
322
		switch kernelTy {
323
		case "int":
324
			return true
325
		}
326
	case "cred":
327
		switch kernelTy {
328
		case "struct cred *":
329
			return true
330
		}
331
	case "linux_binprm":
332
		switch kernelTy {
333
		case "struct linux_binprm *":
334
			return true
335
		}
336
	case "load_info":
337
		switch kernelTy {
338
		case "struct load_info *":
339
			return true
340
		}
341
	case "module":
342
		switch kernelTy {
343
		case "struct module *":
344
			return true
345
		}
346
	case "sock":
347
		switch kernelTy {
348
		case "struct sock *":
349
			return true
350
		}
351
	case "skb":
352
		switch kernelTy {
353
		case "struct sk_buff *":
354
			return true
355
		}
356
	case "net_device":
357
		switch kernelTy {
358
		case "struct net_device *":
359
			return true
360
		}
361
	case "kernel_cap_t", "cap_inheritable", "cap_permitted", "cap_effective":
362
		switch kernelTy {
363
		case "struct kernel_cap_t *":
364
			return true
365
		}
366
	}
367

368
	return false
369
}
370

371
func validateSycall(kspec *v1alpha1.KProbeSpec, name string) error {
372
	if kspec.Return {
373
		if kspec.ReturnArg == nil {
374
			return fmt.Errorf("missing information for syscall %s: returnArg is missing", name)
375
		}
376
		if !typesCompatible(kspec.ReturnArg.Type, "long") {
377
			return fmt.Errorf("unexpected syscall spec return type: %s", kspec.ReturnArg.Type)
378
		}
379
	}
380

381
	argsInfo, ok := syscallinfo.GetSyscallArgs(name)
382
	if !ok {
383
		return &ValidationWarn{s: fmt.Sprintf("missing information for syscall %s: arguments will not be verified", name)}
384
	}
385

386
	for i := range kspec.Args {
387
		specArg := &kspec.Args[i]
388
		if specArg.Index >= uint32(len(argsInfo)) {
389
			return fmt.Errorf("kprobe arg %d has an invalid index: %d based on prototype: %s", i, specArg.Index, argsInfo.Proto(name))
390
		}
391

392
		argTy := argsInfo[specArg.Index].Type
393
		if !typesCompatible(specArg.Type, argTy) {
394
			return &ValidationWarn{s: fmt.Sprintf("type (%s) of syscall argument %d does not match spec type (%s)\n", argTy, specArg.Index, specArg.Type)}
395
		}
396
	}
397

398
	return nil
399
}
400

401
// AvailableSyscalls returns the list of available syscalls.
402
//
403
// It uses syscallinfo.SyscallsNames() and filters calls via information in BTF.
404
func AvailableSyscalls() ([]string, error) {
405
	// NB(kkourt): we should have a single function for this (see observerFindBTF)
406
	btfFile := "/sys/kernel/btf/vmlinux"
407
	tetragonBtfEnv := os.Getenv("TETRAGON_BTF")
408
	if tetragonBtfEnv != "" {
409
		if _, err := os.Stat(tetragonBtfEnv); err != nil {
410
			return nil, fmt.Errorf("Failed to find BTF: %s", tetragonBtfEnv)
411
		}
412
		btfFile = tetragonBtfEnv
413
	}
414
	bspec, err := btf.LoadSpec(btfFile)
415
	if err != nil {
416
		return nil, fmt.Errorf("BTF load failed: %v", err)
417
	}
418

419
	ret := []string{}
420
	for key, value := range syscallinfo.SyscallsNames() {
421
		if value == "" {
422
			return nil, fmt.Errorf("syscall name for %q is empty", key)
423
		}
424

425
		sym, err := arch.AddSyscallPrefix(value)
426
		if err != nil {
427
			return nil, err
428
		}
429

430
		var fn *btf.Func
431
		if err = bspec.TypeByName(sym, &fn); err != nil {
432
			continue
433
		}
434

435
		ret = append(ret, value)
436
	}
437

438
	return ret, nil
439
}
440

441
func GetSyscallsList() ([]string, error) {
442
	btfFile := "/sys/kernel/btf/vmlinux"
443

444
	tetragonBtfEnv := os.Getenv("TETRAGON_BTF")
445
	if tetragonBtfEnv != "" {
446
		if _, err := os.Stat(tetragonBtfEnv); err != nil {
447
			return []string{}, fmt.Errorf("Failed to find BTF: %s", tetragonBtfEnv)
448
		}
449
		btfFile = tetragonBtfEnv
450
	}
451

452
	bspec, err := btf.LoadSpec(btfFile)
453
	if err != nil {
454
		return []string{}, fmt.Errorf("BTF load failed: %v", err)
455
	}
456

457
	var list []string
458

459
	for key, value := range syscallinfo.SyscallsNames() {
460
		var fn *btf.Func
461

462
		if value == "" {
463
			return nil, fmt.Errorf("syscall name for %q is empty", key)
464
		}
465

466
		sym, err := arch.AddSyscallPrefix(value)
467
		if err != nil {
468
			return []string{}, err
469
		}
470

471
		err = bspec.TypeByName(sym, &fn)
472
		if err != nil {
473
			continue
474
		}
475

476
		list = append(list, sym)
477
	}
478

479
	return list, nil
480
}
481

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.