Skip to content

Commit

Permalink
internal: fix reading auxv on 32-bit platforms
Browse files Browse the repository at this point in the history
It turns out that the auxiliary vector has a platform specific size.
Adjust the code to use uintptr to approximate "unsigned long" from C.
auxv32le.bin is from an i686 Debian bookworm machine.

Fixes cilium#1133

Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
  • Loading branch information
lmb committed Oct 11, 2023
1 parent 23be709 commit 353ca88
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 24 deletions.
Binary file added internal/testdata/auxv32le.bin
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
44 changes: 36 additions & 8 deletions internal/vdso.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"math"
"os"
"unsafe"

"github.com/cilium/ebpf/internal/unix"
)
Expand All @@ -19,6 +20,8 @@ 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
Expand All @@ -31,7 +34,7 @@ func vdsoVersion() (uint32, error) {
}
defer av.Close()

vdsoAddr, err := vdsoMemoryAddress(av)
vdsoAddr, err := vdsoMemoryAddress(av, NativeEndian, uintptrIs32bits)
if err != nil {
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
}
Expand All @@ -52,26 +55,51 @@ 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) (uint64, error) {
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
)

// 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, uintptrIs32bits)
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
Expand Down
50 changes: 34 additions & 16 deletions internal/vdso_test.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,72 @@
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 err != nil {
t.Fatal(err)
}
t.Cleanup(func() { av.Close() })
for _, testcase := range []struct {
source string
is32bit bool
address uint64
}{
{"auxv64le.bin", false, 0x7ffd377e5000},
{"auxv32le.bin", true, 0xb7fc3000},
} {
t.Run(testcase.source, func(t *testing.T) {
av, err := os.Open("testdata/" + testcase.source)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { av.Close() })

addr, err := vdsoMemoryAddress(av)
if err != nil {
t.Fatal(err)
}
addr, err := vdsoMemoryAddress(av, binary.LittleEndian, testcase.is32bit)
if err != nil {
t.Fatal(err)
}

expected := uint64(0x7ffd377e5000)
if addr != expected {
t.Errorf("Expected vDSO memory address %x, got %x", expected, addr)
if uint64(addr) != testcase.address {
t.Errorf("Expected vDSO memory address %x, got %x", testcase.address, addr)
}
})
}
}

func TestAuxvNoVDSO(t *testing.T) {
// 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, false)
if want, got := errAuxvNoVDSO, err; !errors.Is(got, want) {
t.Fatalf("expected error '%v', got: %v", want, got)
}
}

func TestVDSOVersion(t *testing.T) {
_, err := vdsoVersion()
qt.Assert(t, err, qt.IsNil)
}

func TestLinuxVersionCodeEmbedded(t *testing.T) {
tests := []struct {
file string
version uint32
}{
{
"../testdata/vdso.bin",
"testdata/vdso.bin",
uint32(328828), // 5.4.124
},
{
"../testdata/vdso_multiple_notes.bin",
"testdata/vdso_multiple_notes.bin",
uint32(328875), // Container Optimized OS v85 with a 5.4.x kernel
},
}
Expand Down

0 comments on commit 353ca88

Please sign in to comment.