Repository: cilium/ebpf
Pull request #1332 (2024-02-07 19:00:24)

dependabot: onboard github actions upgrades
We currently have a lot of github actions missing upgrades, resulting in some 
```
Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: actions/checkout@v3, actions/setup-go@v4 ...
```
[warnings](https://github.com/cilium/ebpf/actions/runs/7817377529).

This PR onboards the github actions workflows into dependabot, which should make our life updating those easier.

brycekahle: Careful of this issue: https://github.com/actions/checkout/issues/1590
paulcacheux: I think this issue only affects workflows that run in a container, which we are not doing. In all cases if we think we do not want a bump we can simply close the dependabot PR

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7290f19..68f0b8e 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,3 +10,7 @@ updates:
       # dependencies only appearing in Pipfile.lock.
       - dependency-name: "*"
         dependency-type: "direct"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"
diff --git a/internal/auxv.go b/internal/auxv.go
new file mode 100644
index 0000000..45fd0d3
--- /dev/null
+++ b/internal/auxv.go
@@ -0,0 +1,60 @@
+package internal
+
+import (
+	"errors"
+	"io"
+	_ "unsafe"
+)
+
+type auxvPairReader interface {
+	Close() error
+	ReadAuxvPair() (uint64, uint64, error)
+}
+
+// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
+const (
+	_AT_NULL         = 0  // End of vector
+	_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
+)
+
+//go:linkname runtime_getAuxv runtime.getAuxv
+func runtime_getAuxv() []uintptr
+
+type auxvRuntimeReader struct {
+	data  []uintptr
+	index int
+}
+
+func (r *auxvRuntimeReader) Close() error {
+	return nil
+}
+
+func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
+	if r.index >= len(r.data)+2 {
+		return 0, 0, io.EOF
+	}
+
+	// we manually add the (_AT_NULL, _AT_NULL) pair at the end
+	// that is not provided by the go runtime
+	var tag, value uintptr
+	if r.index+1 < len(r.data) {
+		tag, value = r.data[r.index], r.data[r.index+1]
+	} else {
+		tag, value = _AT_NULL, _AT_NULL
+	}
+	r.index += 2
+	return uint64(tag), uint64(value), nil
+}
+
+func newAuxvRuntimeReader() (auxvPairReader, error) {
+	data := runtime_getAuxv()
+
+	if len(data)%2 != 0 {
+		return nil, errors.New("malformed auxv passed from runtime")
+	}
+
+	return &auxvRuntimeReader{
+		data:  data,
+		index: 0,
+	}, nil
+}
diff --git a/internal/vdso.go b/internal/vdso.go
index c444a41..1049278 100644
--- a/internal/vdso.go
+++ b/internal/vdso.go
@@ -8,7 +8,6 @@ import (
 	"io"
 	"math"
 	"os"
-	"unsafe"
 
 	"github.com/cilium/ebpf/internal/unix"
 )
@@ -20,21 +19,14 @@ var (
 // vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
 // linked into the current process image.
 func vdsoVersion() (uint32, error) {
-	const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4
-
-	// Read data from the auxiliary vector, which is normally passed directly
-	// to the process. Go does not expose that data, so we must read it from procfs.
-	// https://man7.org/linux/man-pages/man3/getauxval.3.html
-	av, err := os.Open("/proc/self/auxv")
-	if errors.Is(err, unix.EACCES) {
-		return 0, fmt.Errorf("opening auxv: %w (process may not be dumpable due to file capabilities)", err)
-	}
+	av, err := newAuxvRuntimeReader()
 	if err != nil {
-		return 0, fmt.Errorf("opening auxv: %w", err)
+		return 0, err
 	}
+
 	defer av.Close()
 
-	vdsoAddr, err := vdsoMemoryAddress(av, NativeEndian, uintptrIs32bits)
+	vdsoAddr, err := vdsoMemoryAddress(av)
 	if err != nil {
 		return 0, fmt.Errorf("finding vDSO memory address: %w", err)
 	}
@@ -55,43 +47,13 @@ func vdsoVersion() (uint32, error) {
 	return c, nil
 }
 
-type auxvPair32 struct {
-	Tag, Value uint32
-}
-
-type auxvPair64 struct {
-	Tag, Value uint64
-}
-
-func readAuxvPair(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool) (tag, value uint64, _ error) {
-	if uintptrIs32bits {
-		var aux auxvPair32
-		if err := binary.Read(r, order, &aux); err != nil {
-			return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
-		}
-		return uint64(aux.Tag), uint64(aux.Value), nil
-	}
-
-	var aux auxvPair64
-	if err := binary.Read(r, order, &aux); err != nil {
-		return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
-	}
-	return aux.Tag, aux.Value, nil
-}
-
 // vdsoMemoryAddress returns the memory address of the vDSO library
 // linked into the current process image. r is an io.Reader into an auxv blob.
-func vdsoMemoryAddress(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool) (uintptr, error) {
-	// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
-	const (
-		_AT_NULL         = 0  // End of vector
-		_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
-	)
-
+func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) {
 	// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
 	// the address of a page containing the virtual Dynamic Shared Object (vDSO).
 	for {
-		tag, value, err := readAuxvPair(r, order, uintptrIs32bits)
+		tag, value, err := r.ReadAuxvPair()
 		if err != nil {
 			return 0, err
 		}


Test patch:
diff --git a/internal/auxv_test.go b/internal/auxv_test.go
new file mode 100644
index 0000000..ac9d043
--- /dev/null
+++ b/internal/auxv_test.go
@@ -0,0 +1,105 @@
+package internal
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"os"
+	"testing"
+	"unsafe"
+
+	"github.com/cilium/ebpf/internal/unix"
+)
+
+type auxvFileReader struct {
+	file            *os.File
+	order           binary.ByteOrder
+	uintptrIs32bits bool
+}
+
+func (r *auxvFileReader) Close() error {
+	return r.file.Close()
+}
+
+type auxvPair32 struct {
+	Tag, Value uint32
+}
+
+type auxvPair64 struct {
+	Tag, Value uint64
+}
+
+func (r *auxvFileReader) ReadAuxvPair() (tag, value uint64, _ error) {
+	if r.uintptrIs32bits {
+		var aux auxvPair32
+		if err := binary.Read(r.file, r.order, &aux); err != nil {
+			return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
+		}
+		return uint64(aux.Tag), uint64(aux.Value), nil
+	}
+
+	var aux auxvPair64
+	if err := binary.Read(r.file, r.order, &aux); err != nil {
+		return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
+	}
+	return aux.Tag, aux.Value, nil
+}
+
+func newAuxFileReader(path string, order binary.ByteOrder, uintptrIs32bits bool) (auxvPairReader, error) {
+	// Read data from the auxiliary vector, which is normally passed directly
+	// to the process. Go does not expose that data before go 1.21, so we must read it from procfs.
+	// https://man7.org/linux/man-pages/man3/getauxval.3.html
+	av, err := os.Open(path)
+	if errors.Is(err, unix.EACCES) {
+		return nil, fmt.Errorf("opening auxv: %w (process may not be dumpable due to file capabilities)", err)
+	}
+	if err != nil {
+		return nil, fmt.Errorf("opening auxv: %w", err)
+	}
+
+	return &auxvFileReader{
+		file:            av,
+		order:           order,
+		uintptrIs32bits: uintptrIs32bits,
+	}, nil
+}
+
+func newDefaultAuxvFileReader() (auxvPairReader, error) {
+	const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4
+	return newAuxFileReader("/proc/self/auxv", NativeEndian, uintptrIs32bits)
+}
+
+func TestAuxvBothSourcesEqual(t *testing.T) {
+	runtimeBased, err := newAuxvRuntimeReader()
+	if err != nil {
+		t.Fatal(err)
+	}
+	fileBased, err := newDefaultAuxvFileReader()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for {
+		runtimeTag, runtimeValue, err := runtimeBased.ReadAuxvPair()
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		fileTag, fileValue, err := fileBased.ReadAuxvPair()
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if runtimeTag != fileTag {
+			t.Errorf("mismatching tags: runtime=%v, file=%v", runtimeTag, fileTag)
+		}
+
+		if runtimeValue != fileValue {
+			t.Errorf("mismatching values: runtime=%v, file=%v", runtimeValue, fileValue)
+		}
+
+		if runtimeTag == _AT_NULL {
+			break
+		}
+	}
+}
diff --git a/internal/vdso_test.go b/internal/vdso_test.go
index d500a61..bb6f73b 100644
--- a/internal/vdso_test.go
+++ b/internal/vdso_test.go
@@ -19,13 +19,13 @@ func TestAuxvVDSOMemoryAddress(t *testing.T) {
 		{"auxv32le.bin", true, 0xb7fc3000},
 	} {
 		t.Run(testcase.source, func(t *testing.T) {
-			av, err := os.Open("testdata/" + testcase.source)
+			av, err := newAuxFileReader("testdata/"+testcase.source, binary.LittleEndian, testcase.is32bit)
 			if err != nil {
 				t.Fatal(err)
 			}
 			t.Cleanup(func() { av.Close() })
 
-			addr, err := vdsoMemoryAddress(av, binary.LittleEndian, testcase.is32bit)
+			addr, err := vdsoMemoryAddress(av)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -39,13 +39,13 @@ func TestAuxvVDSOMemoryAddress(t *testing.T) {
 
 func TestAuxvNoVDSO(t *testing.T) {
 	// Copy of auxv.bin with the vDSO pointer removed.
-	av, err := os.Open("testdata/auxv64le_no_vdso.bin")
+	av, err := newAuxFileReader("testdata/auxv64le_no_vdso.bin", binary.LittleEndian, false)
 	if err != nil {
 		t.Fatal(err)
 	}
 	t.Cleanup(func() { av.Close() })
 
-	_, err = vdsoMemoryAddress(av, binary.LittleEndian, false)
+	_, err = vdsoMemoryAddress(av)
 	if want, got := errAuxvNoVDSO, err; !errors.Is(got, want) {
 		t.Fatalf("expected error '%v', got: %v", want, got)
 	}