github.com/cilium/ebpf@v0.16.0/internal/vdso.go (about)

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