vdso.go 4.2 KB

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