151 lines
4.2 KiB
Go
151 lines
4.2 KiB
Go
package internal
|
|
|
|
import (
|
|
"debug/elf"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
|
|
"github.com/cilium/ebpf/internal/unix"
|
|
)
|
|
|
|
var (
|
|
errAuxvNoVDSO = errors.New("no vdso address found in auxv")
|
|
)
|
|
|
|
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
|
|
// linked into the current process image.
|
|
func vdsoVersion() (uint32, error) {
|
|
// 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 err != nil {
|
|
return 0, fmt.Errorf("opening auxv: %w", err)
|
|
}
|
|
defer av.Close()
|
|
|
|
vdsoAddr, err := vdsoMemoryAddress(av)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
|
|
}
|
|
|
|
// Use /proc/self/mem rather than unsafe.Pointer tricks.
|
|
mem, err := os.Open("/proc/self/mem")
|
|
if err != nil {
|
|
return 0, fmt.Errorf("opening mem: %w", err)
|
|
}
|
|
defer mem.Close()
|
|
|
|
// Open ELF at provided memory address, as offset into /proc/self/mem.
|
|
c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading linux version code: %w", err)
|
|
}
|
|
|
|
return c, 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) {
|
|
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)
|
|
}
|
|
|
|
switch aux.Tag {
|
|
case _AT_SYSINFO_EHDR:
|
|
if aux.Val != 0 {
|
|
return aux.Val, nil
|
|
}
|
|
return 0, fmt.Errorf("invalid vDSO address in auxv")
|
|
// _AT_NULL is always the last tag/val pair in the aux vector
|
|
// and can be treated like EOF.
|
|
case _AT_NULL:
|
|
return 0, errAuxvNoVDSO
|
|
}
|
|
}
|
|
}
|
|
|
|
// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
|
|
type elfNoteHeader struct {
|
|
NameSize int32
|
|
DescSize int32
|
|
Type int32
|
|
}
|
|
|
|
// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
|
|
// the ELF notes section of the binary provided by the reader.
|
|
func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
|
|
hdr, err := NewSafeELFFile(r)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading vDSO ELF: %w", err)
|
|
}
|
|
|
|
sections := hdr.SectionsByType(elf.SHT_NOTE)
|
|
if len(sections) == 0 {
|
|
return 0, fmt.Errorf("no note section found in vDSO ELF")
|
|
}
|
|
|
|
for _, sec := range sections {
|
|
sr := sec.Open()
|
|
var n elfNoteHeader
|
|
|
|
// Read notes until we find one named 'Linux'.
|
|
for {
|
|
if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
// We looked at all the notes in this section
|
|
break
|
|
}
|
|
return 0, fmt.Errorf("reading note header: %w", err)
|
|
}
|
|
|
|
// If a note name is defined, it follows the note header.
|
|
var name string
|
|
if n.NameSize > 0 {
|
|
// Read the note name, aligned to 4 bytes.
|
|
buf := make([]byte, Align(int(n.NameSize), 4))
|
|
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
|
|
return 0, fmt.Errorf("reading note name: %w", err)
|
|
}
|
|
|
|
// Read nul-terminated string.
|
|
name = unix.ByteSliceToString(buf[:n.NameSize])
|
|
}
|
|
|
|
// If a note descriptor is defined, it follows the name.
|
|
// It is possible for a note to have a descriptor but not a name.
|
|
if n.DescSize > 0 {
|
|
// LINUX_VERSION_CODE is a uint32 value.
|
|
if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
|
|
var version uint32
|
|
if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
|
|
return 0, fmt.Errorf("reading note descriptor: %w", err)
|
|
}
|
|
return version, nil
|
|
}
|
|
|
|
// Discard the note descriptor if it exists but we're not interested in it.
|
|
if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0, fmt.Errorf("no Linux note in ELF")
|
|
}
|