diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go index 73f6ba03c..376d405e0 100644 --- a/internal/cmd/gentypes/main.go +++ b/internal/cmd/gentypes/main.go @@ -285,6 +285,7 @@ import ( "fd_array", "core_relos", ), + choose(20, "attach_btf_obj_fd"), }, }, { diff --git a/internal/sys/types.go b/internal/sys/types.go index 291e3a619..a5f90e70c 100644 --- a/internal/sys/types.go +++ b/internal/sys/types.go @@ -967,7 +967,7 @@ type ProgLoadAttr struct { LineInfo Pointer LineInfoCnt uint32 AttachBtfId uint32 - AttachProgFd uint32 + AttachBtfObjFd uint32 CoreReloCnt uint32 FdArray Pointer CoreRelos Pointer diff --git a/prog.go b/prog.go index a6c8a034a..33fe6b66a 100644 --- a/prog.go +++ b/prog.go @@ -43,8 +43,7 @@ type ProgramOptions struct { // Controls the output buffer size for the verifier. Defaults to // DefaultVerifierLogSize. LogSize int - // Type information used for CO-RE relocations and when attaching to - // kernel functions. + // Type information used for CO-RE relocations. // // This is useful in environments where the kernel BTF is not available // (containers) or where it is in a non-standard location. Defaults to @@ -206,14 +205,12 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand attr.ProgName = sys.NewObjName(spec.Name) } - kernelTypes := opts.KernelTypes - insns := make(asm.Instructions, len(spec.Instructions)) copy(insns, spec.Instructions) var btfDisabled bool if spec.BTF != nil { - if err := applyRelocations(insns, spec.BTF, kernelTypes); err != nil { + if err := applyRelocations(insns, spec.BTF, opts.KernelTypes); err != nil { return nil, fmt.Errorf("apply CO-RE relocations: %w", err) } @@ -262,10 +259,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand } attr.AttachBtfId = uint32(targetID) - attr.AttachProgFd = uint32(spec.AttachTarget.FD()) + attr.AttachBtfObjFd = uint32(spec.AttachTarget.FD()) defer runtime.KeepAlive(spec.AttachTarget) } else if spec.AttachTo != "" { - targetID, err := findTargetInKernel(kernelTypes, spec.AttachTo, spec.Type, spec.AttachType) + module, targetID, err := findTargetInKernel(spec.AttachTo, spec.Type, spec.AttachType) if err != nil && !errors.Is(err, errUnrecognizedAttachType) { // We ignore errUnrecognizedAttachType since AttachTo may be non-empty // for programs that don't attach anywhere. @@ -273,6 +270,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand } attr.AttachBtfId = uint32(targetID) + if module != nil { + attr.AttachBtfObjFd = uint32(module.FD()) + defer module.Close() + } } logSize := DefaultVerifierLogSize @@ -770,11 +771,15 @@ var errUnrecognizedAttachType = errors.New("unrecognized attach type") // find an attach target type in the kernel. // -// spec may be nil and defaults to the canonical kernel BTF. name together with -// progType and attachType determine which type we need to attach to. +// name, progType and attachType determine which type we need to attach to. // -// Returns errUnrecognizedAttachType. -func findTargetInKernel(spec *btf.Spec, name string, progType ProgramType, attachType AttachType) (btf.TypeID, error) { +// The attach target may be in a loaded kernel module. +// In that case the returned handle will be non-nil. +// The caller is responsible for closing the handle. +// +// Returns errUnrecognizedAttachType if the combination of progType and attachType +// is not recognised. +func findTargetInKernel(name string, progType ProgramType, attachType AttachType) (*btf.Handle, btf.TypeID, error) { type match struct { p ProgramType a AttachType @@ -811,24 +816,85 @@ func findTargetInKernel(spec *btf.Spec, name string, progType ProgramType, attac featureName = fmt.Sprintf("raw_tp %s", name) target = (*btf.Typedef)(nil) default: - return 0, errUnrecognizedAttachType + return nil, 0, errUnrecognizedAttachType } - spec, err := maybeLoadKernelBTF(spec) + // maybeLoadKernelBTF may return external BTF if /sys/... is not available. + // Ideally we shouldn't use external BTF here, since we might try to use + // it for parsing kmod split BTF later on. That seems unlikely to work. + spec, err := maybeLoadKernelBTF(nil) if err != nil { - return 0, fmt.Errorf("load kernel spec: %w", err) + return nil, 0, fmt.Errorf("load kernel spec: %w", err) } - if err := spec.TypeByName(typeName, &target); err != nil { + err = spec.TypeByName(typeName, &target) + if errors.Is(err, btf.ErrNotFound) { + module, id, err := findTargetInModule(spec, typeName, target) if errors.Is(err, btf.ErrNotFound) { - return 0, &internal.UnsupportedFeatureError{ - Name: featureName, - } + return nil, 0, &internal.UnsupportedFeatureError{Name: featureName} + } + if err != nil { + return nil, 0, fmt.Errorf("find target for %s in modules: %w", featureName, err) + } + return module, id, nil + } + if err != nil { + return nil, 0, fmt.Errorf("find target for %s in vmlinux: %w", featureName, err) + } + + id, err := spec.TypeID(target) + return nil, id, err +} + +// find an attach target type in a kernel module. +// +// vmlinux must contain the kernel's types and is used to parse kmod BTF. +// +// Returns btf.ErrNotFound if the target can't be found in any module. +func findTargetInModule(vmlinux *btf.Spec, typeName string, target btf.Type) (*btf.Handle, btf.TypeID, error) { + var handle *btf.Handle + defer handle.Close() + + it := new(btf.HandleIterator) + for it.Next(&handle) { + info, err := handle.Info() + if err != nil { + return nil, 0, fmt.Errorf("get info for BTF ID %d: %w", it.ID, err) } - return 0, fmt.Errorf("find target for %s: %w", featureName, err) + + if !info.IsModule() { + continue + } + + spec, err := handle.Spec(vmlinux) + if err != nil { + return nil, 0, fmt.Errorf("parse types for module %s: %w", info.Name, err) + } + + err = spec.TypeByName(typeName, &target) + if errors.Is(err, btf.ErrNotFound) { + continue + } + if err != nil { + return nil, 0, fmt.Errorf("lookup type in module %s: %w", info.Name, err) + } + + id, err := spec.TypeID(target) + if err != nil { + return nil, 0, fmt.Errorf("lookup type id in module %s: %w", info.Name, err) + } + + // Avoid closing the returned handle via handle.Close(). + module := handle + handle = nil + + return module, id, nil + } + if err := it.Err(); err != nil { + return nil, 0, fmt.Errorf("iterate modules: %w", err) } - return spec.TypeID(target) + return nil, 0, btf.ErrNotFound } // find an attach target type in a program. diff --git a/prog_test.go b/prog_test.go index fff5338ed..f2006351b 100644 --- a/prog_test.go +++ b/prog_test.go @@ -644,48 +644,95 @@ func TestProgramSpecTag(t *testing.T) { } } -func TestProgramTypeLSM(t *testing.T) { - lsmTests := []struct { - attachFn string +func TestProgramAttachToKernel(t *testing.T) { + // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 + testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id") + + haveTestmod := false + if !testutils.MustKernelVersion().Less(internal.Version{5, 11}) { + // See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744 + testmod, err := btf.FindHandle(func(info *btf.HandleInfo) bool { + return info.IsModule() && info.Name == "bpf_testmod" + }) + if err != nil && !errors.Is(err, btf.ErrNotFound) { + t.Fatal(err) + } + haveTestmod = testmod != nil + testmod.Close() + } + + tests := []struct { + attachTo string + programType ProgramType + attachType AttachType flags uint32 - expectedErr bool }{ { - attachFn: "task_getpgid", + attachTo: "task_getpgid", + programType: LSM, + attachType: AttachLSMMac, + }, + { + attachTo: "inet_dgram_connect", + programType: Tracing, + attachType: AttachTraceFEntry, + }, + { + attachTo: "inet_dgram_connect", + programType: Tracing, + attachType: AttachTraceFExit, + }, + { + attachTo: "bpf_modify_return_test", + programType: Tracing, + attachType: AttachModifyReturn, + }, + { + attachTo: "kfree_skb", + programType: Tracing, + attachType: AttachTraceRawTp, + }, + { + attachTo: "bpf_testmod_test_read", + programType: Tracing, + attachType: AttachTraceFEntry, }, { - attachFn: "task_setnice", - flags: unix.BPF_F_SLEEPABLE, - expectedErr: true, + attachTo: "bpf_testmod_test_read", + programType: Tracing, + attachType: AttachTraceFExit, }, { - attachFn: "file_open", - flags: unix.BPF_F_SLEEPABLE, + attachTo: "bpf_testmod_test_read", + programType: Tracing, + attachType: AttachModifyReturn, + }, + { + attachTo: "bpf_testmod_test_read", + programType: Tracing, + attachType: AttachTraceRawTp, }, } - for _, tt := range lsmTests { - t.Run(tt.attachFn, func(t *testing.T) { + for _, test := range tests { + name := fmt.Sprintf("%s:%s", test.attachType, test.attachTo) + t.Run(name, func(t *testing.T) { + if strings.HasPrefix(test.attachTo, "bpf_testmod_") && !haveTestmod { + t.Skip("bpf_testmod not loaded") + } + prog, err := NewProgram(&ProgramSpec{ - AttachTo: tt.attachFn, - AttachType: AttachLSMMac, + AttachTo: test.attachTo, + AttachType: test.attachType, Instructions: asm.Instructions{ asm.LoadImm(asm.R0, 0, asm.DWord), asm.Return(), }, License: "GPL", - Type: LSM, - Flags: tt.flags, + Type: test.programType, + Flags: test.flags, }) - testutils.SkipIfNotSupported(t, err) - - if tt.flags&unix.BPF_F_SLEEPABLE != 0 { - testutils.SkipOnOldKernel(t, "5.11", "BPF_F_SLEEPABLE for LSM progs") - } - if tt.expectedErr && err == nil { - t.Errorf("Test case '%s': expected error", tt.attachFn) - } - if !tt.expectedErr && err != nil { - t.Errorf("Test case '%s': expected success", tt.attachFn) + if err != nil { + t.Fatal("Can't load program:", err) } prog.Close() })