vdso.go 4.3 KB

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