Repository: cilium/ebpf
Pull request #555 (2022-02-01 14:45:38)

btf: remove symbol value bounds check to support vmlinux ELFs
In vmlinux ELFs, symbol values are (virtual) memory addresses and don't necessarily correspond to offsets within ELF sections or even files.

For example:

```
~ llvm-objdump -t /usr/lib/modules/*/build/vmlinux | head -5

/usr/lib/modules/5.15.17-1-lts/build/vmlinux:	file format elf64-x86-64

SYMBOL TABLE:
ffffffff81000000 l    d  .text	0000000000000000 .text
```

Explanation on why this is the case: https://0xax.gitbooks.io/linux-insides/content/Theory/linux-theory-2.html#vmlinux

Remove the check as the MaxU32 assumption no longer holds. This allows the caller to specify an unstripped vmlinux TargetBTF and would result in correct fallback behaviour to one of the files in /boot, /lib and /usr as specified in loadKernelSpec(), which is currently also broken as a result.

lmb: Is there a way to add a test for this?
ti-mo: Loading any vmlinux ELF will do, will try to come up with something.

Patch:
diff --git a/internal/btf/btf.go b/internal/btf/btf.go
index 4b88559..df4f78e 100644
--- a/internal/btf/btf.go
+++ b/internal/btf/btf.go
@@ -90,6 +90,17 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 	}
 	defer file.Close()
 
+	return loadSpecFromELF(file)
+}
+
+// variableOffsets extracts all symbols offsets from an ELF and indexes them by
+// section and variable name.
+//
+// References to variables in BTF data sections carry unsigned 32-bit offsets.
+// Some ELF symbols (e.g. in vmlinux) may point to virtual memory that is well
+// beyond this range. Since these symbols cannot be described by BTF info,
+// ignore them here.
+func variableOffsets(file *internal.SafeELFFile) (map[variable]uint32, error) {
 	symbols, err := file.Symbols()
 	if err != nil {
 		return nil, fmt.Errorf("can't read symbols: %v", err)
@@ -102,22 +113,23 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 			continue
 		}
 
+		if symbol.Value > math.MaxUint32 {
+			// VarSecinfo offset is u32, cannot reference symbols in higher regions.
+			continue
+		}
+
 		if int(symbol.Section) >= len(file.Sections) {
 			return nil, fmt.Errorf("symbol %s: invalid section %d", symbol.Name, symbol.Section)
 		}
 
 		secName := file.Sections[symbol.Section].Name
-		if symbol.Value > math.MaxUint32 {
-			return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
-		}
-
 		variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
 	}
 
-	return loadSpecFromELF(file, variableOffsets)
+	return variableOffsets, nil
 }
 
-func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]uint32) (*Spec, error) {
+func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
 	var (
 		btfSection    *elf.Section
 		btfExtSection *elf.Section
@@ -147,7 +159,12 @@ func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]ui
 		return nil, fmt.Errorf("btf: %w", ErrNotFound)
 	}
 
-	spec, err := loadRawSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
+	vars, err := variableOffsets(file)
+	if err != nil {
+		return nil, err
+	}
+
+	spec, err := loadRawSpec(btfSection.Open(), file.ByteOrder, sectionSizes, vars)
 	if err != nil {
 		return nil, err
 	}
@@ -295,6 +312,9 @@ func LoadKernelSpec() (*Spec, error) {
 	return kernelBTF.Spec, err
 }
 
+// loadKernelSpec attempts to load the raw vmlinux BTF blob at
+// /sys/kernel/btf/vmlinux and falls back to scanning the file system
+// for vmlinux ELFs.
 func loadKernelSpec() (*Spec, error) {
 	fh, err := os.Open("/sys/kernel/btf/vmlinux")
 	if err == nil {
@@ -303,13 +323,21 @@ func loadKernelSpec() (*Spec, error) {
 		return loadRawSpec(fh, internal.NativeEndian, nil, nil)
 	}
 
-	var uname unix.Utsname
-	if err := unix.Uname(&uname); err != nil {
-		return nil, fmt.Errorf("uname failed: %w", err)
+	file, err := findVMLinux()
+	if err != nil {
+		return nil, err
 	}
+	defer file.Close()
+
+	return loadSpecFromELF(file)
+}
 
-	end := bytes.IndexByte(uname.Release[:], 0)
-	release := string(uname.Release[:end])
+// findVMLinux scans multiple well-known paths for vmlinux kernel images.
+func findVMLinux() (*internal.SafeELFFile, error) {
+	release, err := internal.KernelRelease()
+	if err != nil {
+		return nil, err
+	}
 
 	// use same list of locations as libbpf
 	// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
@@ -324,24 +352,14 @@ func loadKernelSpec() (*Spec, error) {
 	}
 
 	for _, loc := range locations {
-		path := fmt.Sprintf(loc, release)
-
-		fh, err := os.Open(path)
+		fh, err := os.Open(fmt.Sprintf(loc, release))
 		if err != nil {
 			continue
 		}
-		defer fh.Close()
-
-		file, err := internal.NewSafeELFFile(fh)
-		if err != nil {
-			return nil, err
-		}
-		defer file.Close()
-
-		return loadSpecFromELF(file, nil)
+		return internal.NewSafeELFFile(fh)
 	}
 
-	return nil, fmt.Errorf("no BTF for kernel version %s: %w", release, internal.ErrNotSupported)
+	return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported)
 }
 
 // parseBTFHeader parses the header of the .BTF section.
diff --git a/internal/version.go b/internal/version.go
index 690c552..370e01e 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -3,6 +3,8 @@ package internal
 import (
 	"fmt"
 	"sync"
+
+	"github.com/cilium/ebpf/internal/unix"
 )
 
 const (
@@ -105,3 +107,16 @@ func detectKernelVersion() (Version, error) {
 	}
 	return NewVersionFromCode(vc), nil
 }
+
+// KernelRelease returns the release string of the running kernel.
+// Its format depends on the Linux distribution and corresponds to directory
+// names in /lib/modules by convention. Some examples are 5.15.17-1-lts and
+// 4.19.0-16-amd64.
+func KernelRelease() (string, error) {
+	var uname unix.Utsname
+	if err := unix.Uname(&uname); err != nil {
+		return "", fmt.Errorf("uname failed: %w", err)
+	}
+
+	return unix.ByteSliceToString(uname.Release[:]), nil
+}


Test patch:
diff --git a/internal/btf/btf_test.go b/internal/btf/btf_test.go
index f4e881d..65e8440 100644
--- a/internal/btf/btf_test.go
+++ b/internal/btf/btf_test.go
@@ -186,6 +186,23 @@ func TestParseCurrentKernelBTF(t *testing.T) {
 	}
 }
 
+func TestFindVMLinux(t *testing.T) {
+	file, err := findVMLinux()
+	testutils.SkipIfNotSupported(t, err)
+	if err != nil {
+		t.Fatal("Can't find vmlinux:", err)
+	}
+
+	spec, err := loadSpecFromELF(file)
+	if err != nil {
+		t.Fatal("Can't load BTF:", err)
+	}
+
+	if len(spec.namedTypes) == 0 {
+		t.Fatal("Empty kernel BTF")
+	}
+}
+
 func TestLoadSpecFromElf(t *testing.T) {
 	testutils.Files(t, testutils.Glob(t, "../../testdata/loader-e*.elf"), func(t *testing.T, file string) {
 		spec := parseELFBTF(t, file)
diff --git a/internal/version_test.go b/internal/version_test.go
index 95ead52..9febb60 100644
--- a/internal/version_test.go
+++ b/internal/version_test.go
@@ -84,3 +84,14 @@ func TestVersionFromCode(t *testing.T) {
 		})
 	}
 }
+
+func TestKernelRelease(t *testing.T) {
+	r, err := KernelRelease()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if r == "" {
+		t.Fatal("unexpected empty kernel release")
+	}
+}