gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

vdso.go (4314B)


      1 package internal
      2 
      3 import (
      4 	"debug/elf"
      5 	"encoding/binary"
      6 	"errors"
      7 	"fmt"
      8 	"io"
      9 	"math"
     10 	"os"
     11 
     12 	"github.com/cilium/ebpf/internal/unix"
     13 )
     14 
     15 var (
     16 	errAuxvNoVDSO = errors.New("no vdso address found in auxv")
     17 )
     18 
     19 // vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
     20 // linked into the current process image.
     21 func vdsoVersion() (uint32, error) {
     22 	// Read data from the auxiliary vector, which is normally passed directly
     23 	// to the process. Go does not expose that data, so we must read it from procfs.
     24 	// https://man7.org/linux/man-pages/man3/getauxval.3.html
     25 	av, err := os.Open("/proc/self/auxv")
     26 	if err != nil {
     27 		return 0, fmt.Errorf("opening auxv: %w", err)
     28 	}
     29 	defer av.Close()
     30 
     31 	vdsoAddr, err := vdsoMemoryAddress(av)
     32 	if err != nil {
     33 		return 0, fmt.Errorf("finding vDSO memory address: %w", err)
     34 	}
     35 
     36 	// Use /proc/self/mem rather than unsafe.Pointer tricks.
     37 	mem, err := os.Open("/proc/self/mem")
     38 	if err != nil {
     39 		return 0, fmt.Errorf("opening mem: %w", err)
     40 	}
     41 	defer mem.Close()
     42 
     43 	// Open ELF at provided memory address, as offset into /proc/self/mem.
     44 	c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
     45 	if err != nil {
     46 		return 0, fmt.Errorf("reading linux version code: %w", err)
     47 	}
     48 
     49 	return c, nil
     50 }
     51 
     52 // vdsoMemoryAddress returns the memory address of the vDSO library
     53 // linked into the current process image. r is an io.Reader into an auxv blob.
     54 func vdsoMemoryAddress(r io.Reader) (uint64, error) {
     55 	const (
     56 		_AT_NULL         = 0  // End of vector
     57 		_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
     58 	)
     59 
     60 	// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
     61 	// the address of a page containing the virtual Dynamic Shared Object (vDSO).
     62 	aux := struct{ Tag, Val uint64 }{}
     63 	for {
     64 		if err := binary.Read(r, NativeEndian, &aux); err != nil {
     65 			return 0, fmt.Errorf("reading auxv entry: %w", err)
     66 		}
     67 
     68 		switch aux.Tag {
     69 		case _AT_SYSINFO_EHDR:
     70 			if aux.Val != 0 {
     71 				return aux.Val, nil
     72 			}
     73 			return 0, fmt.Errorf("invalid vDSO address in auxv")
     74 		// _AT_NULL is always the last tag/val pair in the aux vector
     75 		// and can be treated like EOF.
     76 		case _AT_NULL:
     77 			return 0, errAuxvNoVDSO
     78 		}
     79 	}
     80 }
     81 
     82 // format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
     83 type elfNoteHeader struct {
     84 	NameSize int32
     85 	DescSize int32
     86 	Type     int32
     87 }
     88 
     89 // vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
     90 // the ELF notes section of the binary provided by the reader.
     91 func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
     92 	hdr, err := NewSafeELFFile(r)
     93 	if err != nil {
     94 		return 0, fmt.Errorf("reading vDSO ELF: %w", err)
     95 	}
     96 
     97 	sections := hdr.SectionsByType(elf.SHT_NOTE)
     98 	if len(sections) == 0 {
     99 		return 0, fmt.Errorf("no note section found in vDSO ELF")
    100 	}
    101 
    102 	for _, sec := range sections {
    103 		sr := sec.Open()
    104 		var n elfNoteHeader
    105 
    106 		// Read notes until we find one named 'Linux'.
    107 		for {
    108 			if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
    109 				if errors.Is(err, io.EOF) {
    110 					// We looked at all the notes in this section
    111 					break
    112 				}
    113 				return 0, fmt.Errorf("reading note header: %w", err)
    114 			}
    115 
    116 			// If a note name is defined, it follows the note header.
    117 			var name string
    118 			if n.NameSize > 0 {
    119 				// Read the note name, aligned to 4 bytes.
    120 				buf := make([]byte, Align(int(n.NameSize), 4))
    121 				if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
    122 					return 0, fmt.Errorf("reading note name: %w", err)
    123 				}
    124 
    125 				// Read nul-terminated string.
    126 				name = unix.ByteSliceToString(buf[:n.NameSize])
    127 			}
    128 
    129 			// If a note descriptor is defined, it follows the name.
    130 			// It is possible for a note to have a descriptor but not a name.
    131 			if n.DescSize > 0 {
    132 				// LINUX_VERSION_CODE is a uint32 value.
    133 				if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
    134 					var version uint32
    135 					if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
    136 						return 0, fmt.Errorf("reading note descriptor: %w", err)
    137 					}
    138 					return version, nil
    139 				}
    140 
    141 				// Discard the note descriptor if it exists but we're not interested in it.
    142 				if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil {
    143 					return 0, err
    144 				}
    145 			}
    146 		}
    147 	}
    148 
    149 	return 0, fmt.Errorf("no Linux note in ELF")
    150 }