Skip to content

Commit

Permalink
program: support tracing of kernel modules
Browse files Browse the repository at this point in the history
Allow tracing functions in kernel modules via fentry, fexit, fmod_ret
and tp_btf programs. The behaviour follows libbpf and is transparent
to the user: if we can't find a target in vmlinux we attempt to find
it in any loaded kernel module.

Refactor TestProgramTypeLSM to test attaching via BTF. This removes
LSM tests for BPF_F_SLEEPABLE, since they don't excercise library
behaviour beyond passing ProgramSpec.Flags to the kernel.

Updates #705
  • Loading branch information
lmb committed Jul 18, 2022
1 parent 2e191bf commit ce87a5f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 47 deletions.
1 change: 1 addition & 0 deletions internal/cmd/gentypes/main.go
Expand Up @@ -285,6 +285,7 @@ import (
"fd_array",
"core_relos",
),
choose(20, "attach_btf_obj_fd"),
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion internal/sys/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 86 additions & 20 deletions prog.go
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -262,17 +259,21 @@ 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.
return nil, fmt.Errorf("attach %s/%s: %w", spec.Type, spec.AttachType, err)
}

attr.AttachBtfId = uint32(targetID)
if module != nil {
attr.AttachBtfObjFd = uint32(module.FD())
defer module.Close()
}
}

logSize := DefaultVerifierLogSize
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
104 changes: 78 additions & 26 deletions prog_test.go
Expand Up @@ -644,48 +644,100 @@ 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
minVersion string
}{
{
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,
},
{
attachFn: "task_setnice",
flags: unix.BPF_F_SLEEPABLE,
expectedErr: true,
attachTo: "bpf_testmod_test_read",
programType: Tracing,
attachType: AttachTraceFEntry,
},
{
attachFn: "file_open",
flags: unix.BPF_F_SLEEPABLE,
attachTo: "bpf_testmod_test_read",
programType: Tracing,
attachType: AttachTraceFExit,
},
{
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 test.minVersion != "" {
testutils.SkipOnOldKernel(t, test.minVersion, "unknown")
}

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()
})
Expand Down

0 comments on commit ce87a5f

Please sign in to comment.