Repository: cilium/ebpf
Pull request #737 (2022-07-18 15:21:57)

program: support tracing of kernel modules
program: support tracing of kernel modules

    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

btf: add FindHandle

    Add a helper that iterates all BTF objects in the kernel and returns
    the first one for which a user supplied callback returns true.

btf: allow passing *Type to Spec.TypeByName


Patch:
diff --git a/btf/btf.go b/btf/btf.go
index a596933..59485ab 100644
--- a/btf/btf.go
+++ b/btf/btf.go
@@ -609,6 +609,9 @@ func (s *Spec) AnyTypeByName(name string) (Type, error) {
 // Type exists in the Spec. If multiple candidates are found,
 // an error is returned.
 func (s *Spec) TypeByName(name string, typ interface{}) error {
+	typeInterface := reflect.TypeOf((*Type)(nil)).Elem()
+
+	// typ may be **T or *Type
 	typValue := reflect.ValueOf(typ)
 	if typValue.Kind() != reflect.Ptr {
 		return fmt.Errorf("%T is not a pointer", typ)
@@ -620,7 +623,12 @@ func (s *Spec) TypeByName(name string, typ interface{}) error {
 	}
 
 	wanted := typPtr.Type()
-	if !wanted.AssignableTo(reflect.TypeOf((*Type)(nil)).Elem()) {
+	if wanted == typeInterface {
+		// This is *Type. Unwrap the value's type.
+		wanted = typPtr.Elem().Type()
+	}
+
+	if !wanted.AssignableTo(typeInterface) {
 		return fmt.Errorf("%T does not satisfy Type interface", typ)
 	}
 
@@ -643,7 +651,7 @@ func (s *Spec) TypeByName(name string, typ interface{}) error {
 	}
 
 	if candidate == nil {
-		return fmt.Errorf("type %s: %w", name, ErrNotFound)
+		return fmt.Errorf("%s %s: %w", wanted, name, ErrNotFound)
 	}
 
 	typPtr.Set(reflect.ValueOf(candidate))
diff --git a/btf/handle.go b/btf/handle.go
index 128e9b3..3d540e4 100644
--- a/btf/handle.go
+++ b/btf/handle.go
@@ -71,51 +71,89 @@ func (i *HandleInfo) IsModule() bool {
 
 // HandleIterator allows enumerating BTF blobs loaded into the kernel.
 type HandleIterator struct {
-	// The ID of the last retrieved handle. Only valid after a call to Next.
-	ID  ID
-	err error
+	// The ID of the current handle. Only valid after a call to Next.
+	ID ID
+	// The current Handle. Only valid until a call to Next.
+	// See Take if you want to retain the handle.
+	Handle *Handle
+	err    error
 }
 
-// Next retrieves a handle for the next BTF blob.
-//
-// [Handle.Close] is called if *handle is non-nil to avoid leaking fds.
+// Next retrieves a handle for the next BTF object.
 //
-// Returns true if another BTF blob was found. Call [HandleIterator.Err] after
+// Returns true if another BTF object was found. Call [HandleIterator.Err] after
 // the function returns false.
-func (it *HandleIterator) Next(handle **Handle) bool {
-	if *handle != nil {
-		(*handle).Close()
-		*handle = nil
-	}
-
+func (it *HandleIterator) Next() bool {
 	id := it.ID
 	for {
 		attr := &sys.BtfGetNextIdAttr{Id: id}
 		err := sys.BtfGetNextId(attr)
 		if errors.Is(err, os.ErrNotExist) {
 			// There are no more BTF objects.
-			return false
+			break
 		} else if err != nil {
 			it.err = fmt.Errorf("get next BTF ID: %w", err)
-			return false
+			break
 		}
 
 		id = attr.NextId
-		*handle, err = NewHandleFromID(id)
+		handle, err := NewHandleFromID(id)
 		if errors.Is(err, os.ErrNotExist) {
 			// Try again with the next ID.
 			continue
 		} else if err != nil {
 			it.err = fmt.Errorf("retrieve handle for ID %d: %w", id, err)
-			return false
+			break
 		}
 
-		it.ID = id
+		it.Handle.Close()
+		it.ID, it.Handle = id, handle
 		return true
 	}
+
+	// No more handles or we encountered an error.
+	it.Handle.Close()
+	it.Handle = nil
+	return false
+}
+
+// Take the ownership of the current handle.
+//
+// It's the callers responsibility to close the handle.
+func (it *HandleIterator) Take() *Handle {
+	handle := it.Handle
+	it.Handle = nil
+	return handle
 }
 
 // Err returns an error if iteration failed for some reason.
 func (it *HandleIterator) Err() error {
 	return it.err
 }
+
+// FindHandle returns the first handle for which predicate returns true.
+//
+// Requires CAP_SYS_ADMIN.
+//
+// Returns an error wrapping ErrNotFound if predicate never returns true or if
+// there is no BTF loaded into the kernel.
+func FindHandle(predicate func(info *HandleInfo) bool) (*Handle, error) {
+	it := new(HandleIterator)
+	defer it.Handle.Close()
+
+	for it.Next() {
+		info, err := it.Handle.Info()
+		if err != nil {
+			return nil, fmt.Errorf("info for ID %d: %w", it.ID, err)
+		}
+
+		if predicate(info) {
+			return it.Take(), nil
+		}
+	}
+	if err := it.Err(); err != nil {
+		return nil, fmt.Errorf("iterate handles: %w", err)
+	}
+
+	return nil, fmt.Errorf("find handle: %w", ErrNotFound)
+}
diff --git a/elf_reader.go b/elf_reader.go
index df27889..4d8d4be 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -1027,22 +1027,33 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
 			continue
 		}
 
-		data, err := sec.Data()
-		if err != nil {
-			return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
-		}
-
-		if uint64(len(data)) > math.MaxUint32 {
-			return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
-		}
-
 		mapSpec := &MapSpec{
 			Name:       SanitizeName(sec.Name, -1),
 			Type:       Array,
 			KeySize:    4,
-			ValueSize:  uint32(len(data)),
+			ValueSize:  uint32(sec.Size),
 			MaxEntries: 1,
-			Contents:   []MapKV{{uint32(0), data}},
+		}
+
+		switch sec.Type {
+		// Only open the section if we know there's actual data to be read.
+		case elf.SHT_PROGBITS:
+			data, err := sec.Data()
+			if err != nil {
+				return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
+			}
+
+			if uint64(len(data)) > math.MaxUint32 {
+				return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
+			}
+			mapSpec.Contents = []MapKV{{uint32(0), data}}
+
+		case elf.SHT_NOBITS:
+			// NOBITS sections like .bss contain only zeroes, and since data sections
+			// are Arrays, the kernel already preallocates them. Skip reading zeroes
+			// from the ELF.
+		default:
+			return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type)
 		}
 
 		// It is possible for a data section to exist without a corresponding BTF Datasec
@@ -1057,13 +1068,9 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
 			}
 		}
 
-		switch n := sec.Name; {
-		case strings.HasPrefix(n, ".rodata"):
+		if strings.HasPrefix(sec.Name, ".rodata") {
 			mapSpec.Flags = unix.BPF_F_RDONLY_PROG
 			mapSpec.Freeze = true
-		case n == ".bss":
-			// The kernel already zero-initializes the map
-			mapSpec.Contents = nil
 		}
 
 		maps[sec.Name] = mapSpec
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 73f6ba0..376d405 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 291e3a6..a5f90e7 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 675edc7..844e1f9 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
@@ -782,59 +787,109 @@ func findTargetInKernel(spec *btf.Spec, name string, progType ProgramType, attac
 
 	var (
 		typeName, featureName string
-		isBTFTypeFunc         = true
+		target                btf.Type
 	)
 
 	switch (match{progType, attachType}) {
 	case match{LSM, AttachLSMMac}:
 		typeName = "bpf_lsm_" + name
 		featureName = name + " LSM hook"
+		target = (*btf.Func)(nil)
 	case match{Tracing, AttachTraceIter}:
 		typeName = "bpf_iter_" + name
 		featureName = name + " iterator"
+		target = (*btf.Func)(nil)
 	case match{Tracing, AttachTraceFEntry}:
 		typeName = name
 		featureName = fmt.Sprintf("fentry %s", name)
+		target = (*btf.Func)(nil)
 	case match{Tracing, AttachTraceFExit}:
 		typeName = name
 		featureName = fmt.Sprintf("fexit %s", name)
+		target = (*btf.Func)(nil)
 	case match{Tracing, AttachModifyReturn}:
 		typeName = name
 		featureName = fmt.Sprintf("fmod_ret %s", name)
+		target = (*btf.Func)(nil)
 	case match{Tracing, AttachTraceRawTp}:
 		typeName = fmt.Sprintf("btf_trace_%s", name)
 		featureName = fmt.Sprintf("raw_tp %s", name)
-		isBTFTypeFunc = false
+		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)
 	}
 
-	var target btf.Type
-	if isBTFTypeFunc {
-		var targetFunc *btf.Func
-		err = spec.TypeByName(typeName, &targetFunc)
-		target = targetFunc
-	} else {
-		var targetTypedef *btf.Typedef
-		err = spec.TypeByName(typeName, &targetTypedef)
-		target = targetTypedef
+	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 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) {
+	it := new(btf.HandleIterator)
+	defer it.Handle.Close()
+
+	for it.Next() {
+		info, err := it.Handle.Info()
+		if err != nil {
+			return nil, 0, fmt.Errorf("get info for BTF ID %d: %w", it.ID, err)
+		}
+
+		if !info.IsModule() {
+			continue
+		}
+
+		spec, err := it.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) {
-			return 0, &internal.UnsupportedFeatureError{
-				Name: featureName,
-			}
+			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)
 		}
-		return 0, fmt.Errorf("find target for %s: %w", featureName, err)
+
+		return it.Take(), 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.


Test patch:
diff --git a/btf/btf_test.go b/btf/btf_test.go
index 05077f3..29328c7 100644
--- a/btf/btf_test.go
+++ b/btf/btf_test.go
@@ -130,6 +130,12 @@ func TestTypeByName(t *testing.T) {
 		t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses")
 	}
 
+	// It's valid to pass a *Type to TypeByName.
+	typ := Type(iphdr2)
+	if err := spec.TypeByName("iphdr", &typ); err != nil {
+		t.Fatal("Can't look up using *Type:", err)
+	}
+
 	// Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr
 	//
 	// struct iphdr {
diff --git a/btf/handle_test.go b/btf/handle_test.go
index fd6db85..d322ce5 100644
--- a/btf/handle_test.go
+++ b/btf/handle_test.go
@@ -14,20 +14,19 @@ func TestHandleIterator(t *testing.T) {
 	// See https://github.com/torvalds/linux/commit/5329722057d41aebc31e391907a501feaa42f7d9
 	testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
 
-	var h *btf.Handle
-	defer h.Close()
-
 	it := new(btf.HandleIterator)
-	if !it.Next(&h) {
+	defer it.Handle.Close()
+
+	if !it.Next() {
 		t.Fatalf("No BTF loaded")
 	}
-	if h == nil {
+	if it.Handle == nil {
 		t.Fatal("Next doesn't assign handle")
 	}
 	prev := it.ID
-	for it.Next(&h) {
+	for it.Next() {
 		// Iterate all loaded BTF.
-		if h == nil {
+		if it.Handle == nil {
 			t.Fatal("Next doesn't assign handle")
 		}
 		if it.ID == prev {
@@ -39,7 +38,7 @@ func TestHandleIterator(t *testing.T) {
 		t.Fatal("Iteration returned an error:", err)
 	}
 
-	if h != nil {
+	if it.Handle != nil {
 		t.Fatal("Next doesn't clean up handle on last iteration")
 	}
 	if prev != it.ID {
@@ -51,54 +50,25 @@ func TestParseModuleSplitSpec(t *testing.T) {
 	// See TestNewHandleFromID for reasoning.
 	testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
 
-	var module *btf.Handle
-	defer module.Close()
-
-	it := new(btf.HandleIterator)
-	for it.Next(&module) {
-		info, err := module.Info()
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if !info.IsModule() {
-			continue
+	module, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
+		if info.IsModule() {
+			t.Log("Using module", info.Name)
+			return true
 		}
-
-		t.Log("Using module", info.Name)
-		break
-	}
-	if err := it.Err(); err != nil {
+		return false
+	})
+	if err != nil {
 		t.Fatal(err)
 	}
+	defer module.Close()
 
-	if module == nil {
-		t.Fatal("No BTF for kernel module found")
-	}
-
-	var vmlinux *btf.Handle
-	defer vmlinux.Close()
-
-	it = new(btf.HandleIterator)
-	for it.Next(&vmlinux) {
-		info, err := vmlinux.Info()
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if !info.IsVmlinux() {
-			continue
-		}
-
-		break
-	}
-	if err := it.Err(); err != nil {
+	vmlinux, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
+		return info.IsVmlinux()
+	})
+	if err != nil {
 		t.Fatal(err)
 	}
-
-	if vmlinux == nil {
-		t.Fatal("No BTF for kernel found")
-	}
+	defer vmlinux.Close()
 
 	vmlinuxSpec, err := vmlinux.Spec(nil)
 	if err != nil {
@@ -117,13 +87,16 @@ func TestParseModuleSplitSpec(t *testing.T) {
 }
 
 func ExampleHandleIterator() {
-	var handle *btf.Handle
-	// Ensure that handle is cleaned up. This is valid for nil handles as well.
-	defer handle.Close()
-
 	it := new(btf.HandleIterator)
-	for it.Next(&handle) {
-		fmt.Printf("Found handle with ID %d\n", it.ID)
+	defer it.Handle.Close()
+
+	for it.Next() {
+		info, err := it.Handle.Info()
+		if err != nil {
+			panic(err)
+		}
+
+		fmt.Printf("Found handle with ID %d and name %s\n", it.ID, info.Name)
 	}
 	if err := it.Err(); err != nil {
 		panic(err)
diff --git a/prog_test.go b/prog_test.go
index fff5338..f200635 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()
 		})
diff --git a/run-tests.sh b/run-tests.sh
index c21cca9..3507ece 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -111,7 +111,7 @@ readonly branch="${BRANCH:-master}"
 fetch() {
     echo Fetching "${1}"
     pushd "${tmp_dir}" > /dev/null
-    curl -s -L -O --fail --etag-compare "${1}.etag" --etag-save "${1}.etag" "https://github.com/cilium/ci-kernels/raw/${branch}/${1}"
+    curl --no-progress-meter -L -O --fail --etag-compare "${1}.etag" --etag-save "${1}.etag" "https://github.com/cilium/ci-kernels/raw/${branch}/${1}"
     local ret=$?
     popd > /dev/null
     return $ret