From 1ece66bd0bf6907cc24ce73cd54fd87155fdefe6 Mon Sep 17 00:00:00 2001 From: Djalal Harouni Date: Mon, 3 Jun 2024 16:23:07 +0100 Subject: [PATCH] btf: take first entry on multiple function matches [ OSS upstream 0636406b5f02afcc ] TypeByName() can fail with ErrMultipleMatches if we have multiple candidates. If so, let's try again and take first match as it is. This can help solve our immediate issue of having multiple definitions per system calls, however the long-term fix would be to iterate over all candidate, match their proto and arguments definitions, then attach to the corresponding ones. Example output: time="2024-05-31T15:53:17+01:00" level=info msg="BTF includes '2' matched candidates on call \"__x64_sys_init_module\", using first one" Signed-off-by: Djalal Harouni --- pkg/btf/validation.go | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/pkg/btf/validation.go b/pkg/btf/validation.go index 27132feaf11..0549d0ad980 100644 --- a/pkg/btf/validation.go +++ b/pkg/btf/validation.go @@ -4,13 +4,16 @@ package btf import ( + "errors" "fmt" "os" + "reflect" "strings" "github.com/cilium/ebpf/btf" "github.com/cilium/tetragon/pkg/arch" "github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1" + "github.com/cilium/tetragon/pkg/logger" "github.com/cilium/tetragon/pkg/syscallinfo" ) @@ -43,21 +46,43 @@ func (e *ValidationFailed) Error() string { func ValidateKprobeSpec(bspec *btf.Spec, call string, kspec *v1alpha1.KProbeSpec) error { var fn *btf.Func + origCall := call err := bspec.TypeByName(call, &fn) - if err != nil { - if !kspec.Syscall { - return &ValidationFailed{s: fmt.Sprintf("call %q not found", call)} - } - origCall := call + if err != nil && kspec.Syscall { + // Try with system call prefix call, err = arch.AddSyscallPrefix(call) if err == nil { err = bspec.TypeByName(call, &fn) } - if err != nil { + } + + // BTF include multiple candidates + if errors.Is(err, btf.ErrMultipleMatches) { + var allTypes, fnTypes []btf.Type + allTypes, err = bspec.AnyTypesByName(call) + if err == nil { + for _, typ := range allTypes { + // Assert again the appropriate type + if _, ok := typ.(*btf.Func); ok { + fnTypes = append(fnTypes, typ) + } + } + // TypeByName() above ensures btf.Func type, but Check again so semantically we are correct + if len(fnTypes) > 0 { + logger.GetLogger().Infof("BTF metadata includes '%d' matched candidates on call %q, using first one", len(fnTypes), call) + // take first one. + reflect.ValueOf(&fn).Elem().Set(reflect.ValueOf(fnTypes[0])) + } + } + } + + if err != nil { + if kspec.Syscall { return &ValidationFailed{ - s: fmt.Sprintf("syscall %q (or %q) not found.", origCall, call), + s: fmt.Sprintf("syscall %q (or %q) %v", origCall, call, err), } } + return &ValidationFailed{s: fmt.Sprintf("call %q %v", call, err)} } proto, ok := fn.Type.(*btf.FuncProto)