From 3d6f99dd52105e24d4220d0362f9407f94108309 Mon Sep 17 00:00:00 2001 From: Paul Cacheux Date: Fri, 26 Jan 2024 22:00:18 +0100 Subject: [PATCH] add support for reading auxv from Go runtime Extract auxv related code to own file, and use runtime provided auxv fetching function starting from go 1.21. The old file based code is kept for old go version and tests. Signed-off-by: Paul Cacheux --- internal/auxv.go | 68 ++++++++++++++++++++++++++++++++++++++ internal/auxv_go121.go | 44 ++++++++++++++++++++++++ internal/auxv_pre_go121.go | 13 ++++++++ internal/vdso.go | 45 ++++--------------------- internal/vdso_test.go | 8 ++--- 5 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 internal/auxv.go create mode 100644 internal/auxv_go121.go create mode 100644 internal/auxv_pre_go121.go diff --git a/internal/auxv.go b/internal/auxv.go new file mode 100644 index 000000000..99c1e8d66 --- /dev/null +++ b/internal/auxv.go @@ -0,0 +1,68 @@ +package internal + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + + "github.com/cilium/ebpf/internal/unix" +) + +type auxvPairReader interface { + Close() error + ReadAuxvPair() (uint64, uint64, error) +} + +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 +} diff --git a/internal/auxv_go121.go b/internal/auxv_go121.go new file mode 100644 index 000000000..67a77e49d --- /dev/null +++ b/internal/auxv_go121.go @@ -0,0 +1,44 @@ +//go:build go1.21 + +package internal + +import ( + "errors" + "io" + _ "unsafe" // for linkname +) + +//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+1 >= len(r.data) { + return 0, 0, io.EOF + } + + tag, value := r.data[r.index], r.data[r.index+1] + r.index += 2 + return uint64(tag), uint64(value), nil +} + +func auxvReader() (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/auxv_pre_go121.go b/internal/auxv_pre_go121.go new file mode 100644 index 000000000..dfeff94cf --- /dev/null +++ b/internal/auxv_pre_go121.go @@ -0,0 +1,13 @@ +//go:build !go1.21 + +package internal + +import ( + "encoding/binary" + "unsafe" +) + +func auxvReader() (auxvPairReader, error) { + const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4 + return newAuxFileReaderFromPath("/proc/self/auxv", binary.NativeEndian, uintptrIs32bits) +} diff --git a/internal/vdso.go b/internal/vdso.go index c444a41c4..8a2497c27 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,15 @@ 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 := auxvReader() 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,33 +48,9 @@ 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) { +func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) { // See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h const ( _AT_NULL = 0 // End of vector @@ -91,7 +60,7 @@ func vdsoMemoryAddress(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool // 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 } diff --git a/internal/vdso_test.go b/internal/vdso_test.go index d500a61b7..bb6f73b9f 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) }