...

Source file src/github.com/cilium/ebpf/internal/vdso.go

Documentation: github.com/cilium/ebpf/internal

     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  }
   151  

View as plain text