1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright Authors of Tetragon
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"
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 {
27
func (e *ValidationWarn) Error() string {
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 {
37
func (e *ValidationFailed) Error() string {
41
// ValidateKprobeSpec validates a kprobe spec based on BTF information
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 {
50
err := bspec.TypeByName(call, &fn)
51
if err != nil && kspec.Syscall {
52
// Try with system call prefix
53
call, err = arch.AddSyscallPrefix(call)
55
err = bspec.TypeByName(call, &fn)
59
// BTF include multiple candidates
60
if errors.Is(err, btf.ErrMultipleMatches) {
61
var allTypes, fnTypes []btf.Type
62
allTypes, err = bspec.AnyTypesByName(call)
64
for _, typ := range allTypes {
65
// Assert again the appropriate type
66
if _, ok := typ.(*btf.Func); ok {
67
fnTypes = append(fnTypes, typ)
70
// TypeByName() above ensures btf.Func type, but Check again so semantically we are correct
72
logger.GetLogger().Infof("BTF metadata includes '%d' matched candidates on call %q, using first one", len(fnTypes), call)
74
reflect.ValueOf(&fn).Elem().Set(reflect.ValueOf(fnTypes[0]))
81
return &ValidationFailed{
82
s: fmt.Sprintf("syscall %q (or %q) %v", origCall, call, err),
85
return &ValidationFailed{s: fmt.Sprintf("call %q %v", call, err)}
88
proto, ok := fn.Type.(*btf.FuncProto)
90
return fmt.Errorf("kprobe spec validation failed: proto for call %s not found", call)
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.
99
ret, ok := proto.Return.(*btf.Int)
101
return fmt.Errorf("kprobe spec validation failed: syscall return type is not Int")
103
if ret.Name != "long int" {
104
return fmt.Errorf("kprobe spec validation failed: syscall return type is not long int")
107
if len(proto.Params) != 1 {
108
return fmt.Errorf("kprobe spec validation failed: syscall with more than one arg")
111
ptr, ok := proto.Params[0].Type.(*btf.Pointer)
113
return fmt.Errorf("kprobe spec validation failed: syscall arg is not pointer")
116
cnst, ok := ptr.Target.(*btf.Const)
118
return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer")
121
arg, ok := cnst.Type.(*btf.Struct)
123
return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer to struct")
126
if arg.Name != "pt_regs" {
127
return fmt.Errorf("kprobe spec validation failed: syscall arg is not const pointer to struct pt_regs")
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)}
136
syscall := strings.TrimPrefix(call, prefix)
137
return validateSycall(kspec, syscall)
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)
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)}
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"}
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)}
166
func getKernelType(arg btf.Type) string {
168
ptr, ok := arg.(*btf.Pointer)
171
_, ok = arg.(*btf.Void)
175
suffix = suffix + " *"
177
num, ok := arg.(*btf.Int)
179
return num.Name + suffix
181
strct, ok := arg.(*btf.Struct)
183
return "struct " + strct.Name + suffix
186
union, ok := arg.(*btf.Union)
188
return "union " + union.Name + suffix
191
enum, ok := arg.(*btf.Enum)
203
// Not sure what to do here, so just dump the type name
204
return arg.TypeName() + suffix
206
return fmt.Sprintf("%s%d%s", prefix, 8*enum.Size, suffix)
209
cnst, ok := arg.(*btf.Const)
214
// NB: if this was a pointer, reconstruct the type without const
219
return getKernelType(ty)
222
// TODO - add more types, above is enough to make validation_test pass
223
return arg.TypeName() + suffix
226
func typesCompatible(specTy string, kernelTy string) bool {
233
case "u64", "void *", "long unsigned int":
248
case "s16", "short int":
253
case "u16", "short unsigned int":
258
case "u8", "unsigned char":
266
case "char_buf", "string", "int8":
268
case "const char *", "char *", "char":
273
case "const struct iovec *", "struct iovec *":
278
case "unsigned int", "int", "unsigned long", "long":
283
case "unsigned int", "int", "unsigned long", "long", "uid_t", "gid_t", "u32", "s32":
288
case "struct filename *":
293
case "struct file *":
298
case "struct path *":
303
case "union bpf_attr *":
308
case "struct perf_event *":
313
case "struct bpf_map *":
316
case "user_namespace":
318
case "struct user_namespace *":
328
case "struct cred *":
333
case "struct linux_binprm *":
338
case "struct load_info *":
343
case "struct module *":
348
case "struct sock *":
353
case "struct sk_buff *":
358
case "struct net_device *":
361
case "kernel_cap_t", "cap_inheritable", "cap_permitted", "cap_effective":
363
case "struct kernel_cap_t *":
371
func validateSycall(kspec *v1alpha1.KProbeSpec, name string) error {
373
if kspec.ReturnArg == nil {
374
return fmt.Errorf("missing information for syscall %s: returnArg is missing", name)
376
if !typesCompatible(kspec.ReturnArg.Type, "long") {
377
return fmt.Errorf("unexpected syscall spec return type: %s", kspec.ReturnArg.Type)
381
argsInfo, ok := syscallinfo.GetSyscallArgs(name)
383
return &ValidationWarn{s: fmt.Sprintf("missing information for syscall %s: arguments will not be verified", name)}
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))
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)}
401
// AvailableSyscalls returns the list of available syscalls.
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)
412
btfFile = tetragonBtfEnv
414
bspec, err := btf.LoadSpec(btfFile)
416
return nil, fmt.Errorf("BTF load failed: %v", err)
420
for key, value := range syscallinfo.SyscallsNames() {
422
return nil, fmt.Errorf("syscall name for %q is empty", key)
425
sym, err := arch.AddSyscallPrefix(value)
431
if err = bspec.TypeByName(sym, &fn); err != nil {
435
ret = append(ret, value)
441
func GetSyscallsList() ([]string, error) {
442
btfFile := "/sys/kernel/btf/vmlinux"
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)
449
btfFile = tetragonBtfEnv
452
bspec, err := btf.LoadSpec(btfFile)
454
return []string{}, fmt.Errorf("BTF load failed: %v", err)
459
for key, value := range syscallinfo.SyscallsNames() {
463
return nil, fmt.Errorf("syscall name for %q is empty", key)
466
sym, err := arch.AddSyscallPrefix(value)
468
return []string{}, err
471
err = bspec.TypeByName(sym, &fn)
476
list = append(list, sym)