From c5677d5e92fda1dbea40eaf46c865b457b9759ce Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Mon, 2 Oct 2023 14:18:28 +0100 Subject: [PATCH] internal: fix reading auxv on 32-bit platforms It turns out that the auxiliary vector has a platform specific size. Adjust the code to use uintptr to approximate "unsigned long" from C. Fixes https://github.com/cilium/ebpf/issues/1133 Signed-off-by: Lorenz Bauer --- internal/vdso.go | 44 ++++++++++++++---- internal/vdso_test.go | 30 ++++++++++-- testdata/{auxv.bin => auxv64le.bin} | Bin ...{auxv_no_vdso.bin => auxv64le_no_vdso.bin} | Bin 4 files changed, 61 insertions(+), 13 deletions(-) rename testdata/{auxv.bin => auxv64le.bin} (100%) rename testdata/{auxv_no_vdso.bin => auxv64le_no_vdso.bin} (100%) diff --git a/internal/vdso.go b/internal/vdso.go index 10e639bf0..ce1349f50 100644 --- a/internal/vdso.go +++ b/internal/vdso.go @@ -8,6 +8,7 @@ import ( "io" "math" "os" + "unsafe" "github.com/cilium/ebpf/internal/unix" ) @@ -16,6 +17,8 @@ var ( errAuxvNoVDSO = errors.New("no vdso address found in auxv") ) +const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4 + // vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library // linked into the current process image. func vdsoVersion() (uint32, error) { @@ -31,7 +34,7 @@ func vdsoVersion() (uint32, error) { } defer av.Close() - vdsoAddr, err := vdsoMemoryAddress(av) + vdsoAddr, err := vdsoMemoryAddress(av, NativeEndian) if err != nil { return 0, fmt.Errorf("finding vDSO memory address: %w", err) } @@ -52,9 +55,34 @@ 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) (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) (uint64, error) { +func vdsoMemoryAddress(r io.Reader, order binary.ByteOrder) (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 @@ -62,16 +90,16 @@ func vdsoMemoryAddress(r io.Reader) (uint64, 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). - aux := struct{ Tag, Val uint64 }{} for { - if err := binary.Read(r, NativeEndian, &aux); err != nil { - return 0, fmt.Errorf("reading auxv entry: %w", err) + tag, value, err := readAuxvPair(r, order) + if err != nil { + return 0, err } - switch aux.Tag { + switch tag { case _AT_SYSINFO_EHDR: - if aux.Val != 0 { - return aux.Val, nil + if value != 0 { + return uintptr(value), nil } return 0, fmt.Errorf("invalid vDSO address in auxv") // _AT_NULL is always the last tag/val pair in the aux vector diff --git a/internal/vdso_test.go b/internal/vdso_test.go index 53fe9c996..b70a4d0d0 100644 --- a/internal/vdso_test.go +++ b/internal/vdso_test.go @@ -1,43 +1,63 @@ package internal import ( + "encoding/binary" "errors" "os" "testing" + + qt "github.com/frankban/quicktest" ) func TestAuxvVDSOMemoryAddress(t *testing.T) { - av, err := os.Open("../testdata/auxv.bin") + if uintptrIs32bits { + t.Skip("System pointer size is not 8 bytes") + } + + av, err := os.Open("../testdata/auxv64le.bin") if err != nil { t.Fatal(err) } t.Cleanup(func() { av.Close() }) - addr, err := vdsoMemoryAddress(av) + addr, err := vdsoMemoryAddress(av, binary.LittleEndian) if err != nil { t.Fatal(err) } expected := uint64(0x7ffd377e5000) - if addr != expected { + if uint64(addr) != expected { t.Errorf("Expected vDSO memory address %x, got %x", expected, addr) } } func TestAuxvNoVDSO(t *testing.T) { + if uintptrIs32bits { + t.Skip("System pointer size is not 8 bytes") + } + // Copy of auxv.bin with the vDSO pointer removed. - av, err := os.Open("../testdata/auxv_no_vdso.bin") + av, err := os.Open("../testdata/auxv64le_no_vdso.bin") if err != nil { t.Fatal(err) } t.Cleanup(func() { av.Close() }) - _, err = vdsoMemoryAddress(av) + _, err = vdsoMemoryAddress(av, binary.LittleEndian) if want, got := errAuxvNoVDSO, err; !errors.Is(got, want) { t.Fatalf("expected error '%v', got: %v", want, got) } } +func TestProcAuxv(t *testing.T) { + av, err := os.Open("/proc/self/auxv") + qt.Assert(t, err, qt.IsNil) + defer av.Close() + + _, err = vdsoMemoryAddress(av, binary.LittleEndian) + qt.Assert(t, err, qt.IsNil) +} + func TestLinuxVersionCodeEmbedded(t *testing.T) { tests := []struct { file string diff --git a/testdata/auxv.bin b/testdata/auxv64le.bin similarity index 100% rename from testdata/auxv.bin rename to testdata/auxv64le.bin diff --git a/testdata/auxv_no_vdso.bin b/testdata/auxv64le_no_vdso.bin similarity index 100% rename from testdata/auxv_no_vdso.bin rename to testdata/auxv64le_no_vdso.bin