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