vdso.go (4314B)
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 }