diff --git a/pkg/btf/btf.go b/pkg/btf/btf.go index 6996ee21f97..def1814e529 100644 --- a/pkg/btf/btf.go +++ b/pkg/btf/btf.go @@ -6,6 +6,7 @@ package btf import ( + "errors" "fmt" "math" "os" @@ -262,6 +263,7 @@ func ResolveBTFPath( pathToFound []string, i int, ) (*btf.Type, error) { + currentType = ResolveNestedTypes(currentType) switch t := currentType.(type) { case *btf.Struct: return processMembers(btfArgs, currentType, t.Members, pathToFound, i) @@ -276,9 +278,9 @@ func ResolveBTFPath( if idx, err := parseArrayIdxStr(pathToFound[i]); err == nil { // To stay ahead on the dereferecing, we mark the current btfArg as pointer btfArgs[i].IsPointer = uint16(1) - return processArray(btfArgs, ResolveNestedTypes(t.Target), pathToFound, i, idx) + return processArray(btfArgs, t.Target, pathToFound, i, idx) } - return ResolveBTFPath(btfArgs, ResolveNestedTypes(t.Target), pathToFound, i) + return ResolveBTFPath(btfArgs, t.Target, pathToFound, i) case *btf.Array: idx, err := parseArrayIdxStr(pathToFound[i]) if err != nil { @@ -287,7 +289,7 @@ func ResolveBTFPath( if idx >= t.Nelems { return nil, fmt.Errorf("array index out of bound. Nelems=%d, got=%d", t.Nelems, idx) } - return processArray(btfArgs, ResolveNestedTypes(t.Type), pathToFound, i, idx) + return processArray(btfArgs, t.Type, pathToFound, i, idx) default: ty := currentType.TypeName() if len(ty) == 0 { @@ -301,6 +303,15 @@ func ResolveBTFPath( } } +type resolveError struct { + idx int + str string +} + +func (e *resolveError) Error() string { + return e.str +} + func processMembers( btfArgs *[api.MaxBTFArgDepth]api.ConfigBTFArg, currentType btf.Type, @@ -308,57 +319,50 @@ func processMembers( pathToFound []string, i int, ) (*btf.Type, error) { - var lastError error - memberWasFound := false + var lastError *resolveError for _, member := range members { - if len(member.Name) == 0 { // If anonymous struct, fallthrough - btfArgs[i].Offset = member.Offset.Bytes() - btfArgs[i].IsInitialized = uint16(1) - lastTy, err := ResolveBTFPath(btfArgs, ResolveNestedTypes(member.Type), pathToFound, i) + if len(member.Name) == 0 { // anonymous struct/union, fallthrough + lastTy, err := ResolveBTFPath(btfArgs, member.Type, pathToFound, i) if err != nil { - if lastError != nil { - idx := i + 1 - // If the error raised originates from a depth greater than the current one, we stop the search. - if idx < len(pathToFound) && strings.Contains(lastError.Error(), pathToFound[idx]) { - break + // Propagate the deepest error for both resolve and non-resolve error. + if err2, ok := errors.AsType[*resolveError](err); ok { + if lastError == nil || lastError.idx < err2.idx { + lastError = err2 } + } else if lastError == nil || lastError.idx <= i { + lastError = &resolveError{i, err.Error()} } - lastError = err continue } + btfArgs[i].Offset += member.Offset.Bytes() return lastTy, nil } - if member.Name == pathToFound[i] { - memberWasFound = true - btfArgs[i].Offset = member.Offset.Bytes() - btfArgs[i].IsInitialized = uint16(1) - isNotLastChild := i < len(pathToFound)-1 && i < api.MaxBTFArgDepth - if isNotLastChild { - return ResolveBTFPath(btfArgs, ResolveNestedTypes(member.Type), pathToFound, i+1) - } - currentType = ResolveNestedTypes(member.Type) - break + if member.Name != pathToFound[i] { + continue } - } - if !memberWasFound { - if lastError != nil { - return nil, lastError + btfArgs[i].Offset = member.Offset.Bytes() + btfArgs[i].IsInitialized = uint16(1) + if i < len(pathToFound)-1 && i < api.MaxBTFArgDepth { + return ResolveBTFPath(btfArgs, member.Type, pathToFound, i+1) + } + memberType := ResolveNestedTypes(member.Type) + switch t := memberType.(type) { + case *btf.Pointer: + btfArgs[i].IsPointer = uint16(1) + memberType = t.Target + case *btf.Int, *btf.Enum: + btfArgs[i].IsPointer = uint16(1) } - return nil, fmt.Errorf( - "attribute %q not found in structure %q found %v", - pathToFound[i], - currentType.TypeName(), - members, - ) + return &memberType, nil } - switch t := currentType.(type) { - case *btf.Pointer: - btfArgs[i].IsPointer = uint16(1) - currentType = t.Target - case *btf.Int, *btf.Enum: - btfArgs[i].IsPointer = uint16(1) + if lastError != nil { + return nil, lastError } - return ¤tType, nil + return nil, &resolveError{i, fmt.Sprintf( + "attribute %q not found in structure %q", + pathToFound[i], + currentType.TypeName(), + )} } func processArray( @@ -368,6 +372,7 @@ func processArray( i int, idx uint32, ) (*btf.Type, error) { + targetType = ResolveNestedTypes(targetType) btfArgs[i].IsInitialized = uint16(1) btfArgs[i].Offset = getSizeofType(targetType) * idx if len(pathToFound) > i+1 { diff --git a/pkg/btf/btf_test.go b/pkg/btf/btf_test.go index a2694bbdf67..7ab370f19c0 100644 --- a/pkg/btf/btf_test.go +++ b/pkg/btf/btf_test.go @@ -290,29 +290,29 @@ func getBTFPointer(ty btf.Type) (*btf.Pointer, error) { return nil, fmt.Errorf("Invalid type for \"%v\", expected \"*btf.Pointer\", got %q", t, reflect.TypeOf(ty).String()) } -func findMemberInBTFStruct(structTy *btf.Struct, memberName string) (*btf.Member, error) { +func findMemberInBTFStruct(structTy *btf.Struct, memberName string) (*btf.Member, uint32, error) { for _, member := range structTy.Members { if member.Name == memberName { - return &member, nil + return &member, 0, nil } if anonymousStructTy, ok := member.Type.(*btf.Struct); ok && len(member.Name) == 0 { for _, m := range anonymousStructTy.Members { if m.Name == memberName { - return &m, nil + return &m, uint32(member.Offset.Bytes()), nil } } } - if unionTy, ok := member.Type.(*btf.Union); ok { + if unionTy, ok := member.Type.(*btf.Union); ok && len(member.Name) == 0 { for _, m := range unionTy.Members { if m.Name == memberName { - return &m, nil + return &m, uint32(member.Offset.Bytes()), nil } } } } - return nil, fmt.Errorf("Member %q not found in struct %v", memberName, structTy) + return nil, 0, fmt.Errorf("Member %q not found in struct %v", memberName, structTy) } func getBTFPointerAndSetConfig(ty btf.Type, btfConfig *api.ConfigBTFArg) (*btf.Pointer, error) { @@ -329,12 +329,12 @@ func getBTFPointerAndSetConfig(ty btf.Type, btfConfig *api.ConfigBTFArg) (*btf.P func getConfigAndNextType(structTy *btf.Struct, memberName string) (*btf.Type, *api.ConfigBTFArg, error) { btfConfig := api.ConfigBTFArg{} - member, err := findMemberInBTFStruct(structTy, memberName) + member, parentOffset, err := findMemberInBTFStruct(structTy, memberName) if err != nil { return nil, nil, err } - btfConfig.Offset = uint32(member.Offset.Bytes()) + btfConfig.Offset = uint32(member.Offset.Bytes()) + parentOffset btfConfig.IsInitialized = uint16(1) ty := ResolveNestedTypes(member.Type) @@ -352,12 +352,12 @@ func getConfigAndNextType(structTy *btf.Struct, memberName string) (*btf.Type, * func getConfigAndNextStruct(structTy *btf.Struct, memberName string) (*btf.Struct, *api.ConfigBTFArg, error) { btfConfig := api.ConfigBTFArg{} - member, err := findMemberInBTFStruct(structTy, memberName) + member, parentOffset, err := findMemberInBTFStruct(structTy, memberName) if err != nil { return nil, nil, err } - btfConfig.Offset = uint32(member.Offset.Bytes()) + btfConfig.Offset = uint32(member.Offset.Bytes()) + parentOffset btfConfig.IsInitialized = uint16(1) ty := ResolveNestedTypes(member.Type) @@ -680,3 +680,159 @@ func TestParseArrayIdxStr(t *testing.T) { }) } } + +func TestProcessMembersErrorPrecedence(t *testing.T) { + intTy := &btf.Int{Name: "int", Size: 4} + + // deeper resolveError beats shallower resolveError + t.Run("deepest_resolve_error_wins", func(t *testing.T) { + // struct outer { + // struct { int a; }; // anon1: "x" not found → resolveError{depth=0} + // struct { struct inner { int b; } x; }; // anon2: "x" found, "y" not found → resolveError{depth=1} + // }; + inner := &btf.Struct{ + Name: "inner", + Size: 4, + Members: []btf.Member{ + {Name: "b", Type: intTy, Offset: 0}, + }, + } + anon1 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "a", Type: intTy, Offset: 0}, + }, + } + anon2 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "x", Type: inner, Offset: 0}, + }, + } + outer := &btf.Struct{ + Name: "outer", + Size: 8, + Members: []btf.Member{ + {Name: "", Type: anon1, Offset: 0}, + {Name: "", Type: anon2, Offset: btf.Bits(32)}, + }, + } + + var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg + _, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0) + require.Error(t, err) + assert.ErrorContains(t, err, `attribute "y" not found in structure "inner"`) + }) + + // non-resolveError (attribute found, wrong type) beats resolveError at the same depth + t.Run("non_resolve_error_beats_resolve_error_at_same_depth", func(t *testing.T) { + // struct outer { + // struct { int q; }; // anon1: "x" not found → resolveError{depth=0} + // struct { int x; }; // anon2: "x" found, int has no subfields → "unexpected type" (non-resolveError at depth=0) + // }; + // + // anon2 got further (found "x"), so its error should win + anon1 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "q", Type: intTy, Offset: 0}, + }, + } + anon2 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "x", Type: intTy, Offset: 0}, + }, + } + outer := &btf.Struct{ + Name: "outer", + Size: 8, + Members: []btf.Member{ + {Name: "", Type: anon1, Offset: 0}, + {Name: "", Type: anon2, Offset: btf.Bits(32)}, + }, + } + + var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg + // Two-element path: "x" is not the last child, so finding it in anon2 triggers + // ResolveBTFPath(int, path, 1) which returns a non-resolveError. + _, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0) + require.Error(t, err) + assert.ErrorContains(t, err, `unexpected type : "x" has type "int"`) + }) + + // deeper resolveError from a later branch beats a non-resolveError from an earlier branch + t.Run("deep_resolve_error_beats_non_resolve_error", func(t *testing.T) { + // struct outer { + // struct { int x; }; // anon1: "x" found, int type → non-resolveError stored at depth=0 + // struct { struct inner { int b; } x; }; // anon2: "x" found, "y" not in inner → resolveError{depth=1} + // }; + // + // anon2's resolveError at depth 1 is deeper than anon1's non-resolveError at depth 0. + inner := &btf.Struct{ + Name: "inner", + Size: 4, + Members: []btf.Member{ + {Name: "b", Type: intTy, Offset: 0}, + }, + } + anon1 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "x", Type: intTy, Offset: 0}, + }, + } + anon2 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "x", Type: inner, Offset: 0}, + }, + } + outer := &btf.Struct{ + Name: "outer", + Size: 8, + Members: []btf.Member{ + {Name: "", Type: anon1, Offset: 0}, + {Name: "", Type: anon2, Offset: btf.Bits(32)}, + }, + } + + var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg + _, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0) + require.Error(t, err) + assert.ErrorContains(t, err, `attribute "y" not found in structure "inner"`) + }) + + // success through one anonymous branch when another fails + t.Run("success_through_anonymous_branch", func(t *testing.T) { + // struct outer { + // struct { int q; }; // anon1: "x" not found, int type + // struct { int x; }; // anon2: "x" found + // }; + anon1 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "q", Type: intTy, Offset: 0}, + }, + } + anon2 := &btf.Struct{ + Size: 4, + Members: []btf.Member{ + {Name: "x", Type: intTy, Offset: 0}, + }, + } + outer := &btf.Struct{ + Name: "outer", + Size: 8, + Members: []btf.Member{ + {Name: "", Type: anon1, Offset: 0}, + {Name: "", Type: anon2, Offset: btf.Bits(32)}, + }, + } + + var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg + ty, err := ResolveBTFPath(&btfArgs, outer, []string{"x"}, 0) + require.NoError(t, err) + require.NotNil(t, ty) + }) +} diff --git a/pkg/sensors/tracing/args_linux.go b/pkg/sensors/tracing/args_linux.go index 2eff6ffa267..1e7370be228 100644 --- a/pkg/sensors/tracing/args_linux.go +++ b/pkg/sensors/tracing/args_linux.go @@ -134,9 +134,9 @@ func getArgStatus(r *bytes.Reader) (*api.MsgGenericKprobeArgError, error) { if status != 0 { if status == ^uint32(0) { - arg.Message = "Bad address" + arg.Message = "Bad address for basic type" } else { - arg.Message = strconv.FormatUint(uint64(status), 10) + arg.Message = "Bad address encountered during resolve dereference at depth " + strconv.FormatUint(uint64(status-1), 10) } return &arg, nil } diff --git a/pkg/sensors/tracing/uprobe_reg_test.go b/pkg/sensors/tracing/uprobe_reg_test.go index 3795daf94b6..6a851038fdc 100644 --- a/pkg/sensors/tracing/uprobe_reg_test.go +++ b/pkg/sensors/tracing/uprobe_reg_test.go @@ -202,35 +202,35 @@ func TestUprobeResolve(t *testing.T) { ec.NewKprobeArgumentChecker().WithSizeArg(10), // uint64(10) ec.NewKprobeArgumentChecker().WithUintArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 1"))), }}, {"uint32", 1234, "enum_u32", []*ec.KprobeArgumentChecker{ ec.NewKprobeArgumentChecker().WithSizeArg(0), ec.NewKprobeArgumentChecker().WithUintArg(1234), // uint32(1234) ec.NewKprobeArgumentChecker().WithUintArg(0), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 1"))), }}, {"uint32", 12, "sub.v32", []*ec.KprobeArgumentChecker{ ec.NewKprobeArgumentChecker().WithSizeArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), ec.NewKprobeArgumentChecker().WithUintArg(12), // uint32(12) - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 1"))), }}, {"uint64", 13, "arr[2].v64", []*ec.KprobeArgumentChecker{ ec.NewKprobeArgumentChecker().WithSizeArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), ec.NewKprobeArgumentChecker().WithSizeArg(13), // uint64(13) - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), }}, {"uint64", 14, "dyn[6].v64", []*ec.KprobeArgumentChecker{ ec.NewKprobeArgumentChecker().WithSizeArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), ec.NewKprobeArgumentChecker().WithUintArg(0), - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), ec.NewKprobeArgumentChecker().WithSizeArg(14), // uint64(14) }}, } @@ -1336,13 +1336,13 @@ func TestUprobeResolveNull(t *testing.T) { kpArgs []*ec.KprobeArgumentChecker }{ {"first", []*ec.KprobeArgumentChecker{ - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("1"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 0"))), }}, {"second", []*ec.KprobeArgumentChecker{ - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("2"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 1"))), }}, {"third", []*ec.KprobeArgumentChecker{ - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("3"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address encountered during resolve dereference at depth 2"))), }}, {"nonull", []*ec.KprobeArgumentChecker{ ec.NewKprobeArgumentChecker().WithIntArg(0), diff --git a/pkg/sensors/tracing/uprobe_test.go b/pkg/sensors/tracing/uprobe_test.go index cc150e98a00..c0414b8b104 100644 --- a/pkg/sensors/tracing/uprobe_test.go +++ b/pkg/sensors/tracing/uprobe_test.go @@ -1254,7 +1254,7 @@ spec: WithBinary(sm.Full(uprobeTest1))).WithSymbol(sm.Full("uprobe_test_lib_string_arg_null")).WithArgs(ec.NewKprobeArgumentListMatcher(). WithOperator(lc.Ordered). WithValues( - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address for basic type"))), ec.NewKprobeArgumentChecker().WithIntArg(0)), ) checker := ec.NewUnorderedEventChecker(upChecker) diff --git a/tests/policytests/kprobes.go b/tests/policytests/kprobes.go index 78675703e85..4a5759e7d37 100644 --- a/tests/policytests/kprobes.go +++ b/tests/policytests/kprobes.go @@ -70,7 +70,7 @@ spec: WithBinary(sm.Full(myBin))).WithArgs(ec.NewKprobeArgumentListMatcher(). WithOperator(lc.Ordered). WithValues( - ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address"))), + ec.NewKprobeArgumentChecker().WithErrorArg(ec.NewKprobeErrorChecker().WithMessage(sm.Full("Bad address for basic type"))), )).WithReturn(ec.NewKprobeArgumentChecker().WithIntArg(0)) return &policytest.Scenario{