read.go (6264B)
1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package goversion reports the Go version used to build program executables. 6 // 7 // This is a copy of rsc.io/goversion/version. We renamed the package to goversion 8 // to differentiate between version package in standard library that we also use. 9 package goversion 10 11 import ( 12 "bytes" 13 "encoding/hex" 14 "errors" 15 "fmt" 16 "regexp" 17 "strings" 18 ) 19 20 // Version is the information reported by ReadExe. 21 type Version struct { 22 Release string // Go version (runtime.Version in the program) 23 ModuleInfo string // program's module information 24 BoringCrypto bool // program uses BoringCrypto 25 StandardCrypto bool // program uses standard crypto (replaced by BoringCrypto) 26 FIPSOnly bool // program imports "crypto/tls/fipsonly" 27 } 28 29 // ReadExe reports information about the Go version used to build 30 // the program executable named by file. 31 func ReadExe(file string) (Version, error) { 32 var v Version 33 f, err := openExe(file) 34 if err != nil { 35 return v, err 36 } 37 defer f.Close() 38 isGo := false 39 for _, name := range f.SectionNames() { 40 if name == ".note.go.buildid" { 41 isGo = true 42 } 43 } 44 syms, symsErr := f.Symbols() 45 isGccgo := false 46 for _, sym := range syms { 47 name := sym.Name 48 if name == "runtime.main" || name == "main.main" { 49 isGo = true 50 } 51 if strings.HasPrefix(name, "runtime.") && strings.HasSuffix(name, "$descriptor") { 52 isGccgo = true 53 } 54 if name == "runtime.buildVersion" { 55 isGo = true 56 release, err := readBuildVersion(f, sym.Addr, sym.Size) 57 if err != nil { 58 return v, err 59 } 60 v.Release = release 61 } 62 // Note: Using strings.HasPrefix because Go 1.17+ adds ".abi0" to many of these symbols. 63 if strings.Contains(name, "_Cfunc__goboringcrypto_") || strings.HasPrefix(name, "crypto/internal/boring/sig.BoringCrypto") { 64 v.BoringCrypto = true 65 } 66 if strings.HasPrefix(name, "crypto/internal/boring/sig.FIPSOnly") { 67 v.FIPSOnly = true 68 } 69 for _, re := range standardCryptoNames { 70 if re.MatchString(name) { 71 v.StandardCrypto = true 72 } 73 } 74 if strings.HasPrefix(name, "crypto/internal/boring/sig.StandardCrypto") { 75 v.StandardCrypto = true 76 } 77 } 78 79 if DebugMatch { 80 v.Release = "" 81 } 82 if err := findModuleInfo(&v, f); err != nil { 83 return v, err 84 } 85 if v.Release == "" { 86 g, release := readBuildVersionX86Asm(f) 87 if g { 88 isGo = true 89 v.Release = release 90 if err := findCryptoSigs(&v, f); err != nil { 91 return v, err 92 } 93 } 94 } 95 if isGccgo && v.Release == "" { 96 isGo = true 97 v.Release = "gccgo (version unknown)" 98 } 99 if !isGo && symsErr != nil { 100 return v, symsErr 101 } 102 103 if !isGo { 104 return v, errors.New("not a Go executable") 105 } 106 if v.Release == "" { 107 v.Release = "unknown Go version" 108 } 109 return v, nil 110 } 111 112 var re = regexp.MustCompile 113 114 var standardCryptoNames = []*regexp.Regexp{ 115 re(`^crypto/sha1\.\(\*digest\)`), 116 re(`^crypto/sha256\.\(\*digest\)`), 117 re(`^crypto/rand\.\(\*devReader\)`), 118 re(`^crypto/rsa\.encrypt(\.abi.)?$`), 119 re(`^crypto/rsa\.decrypt(\.abi.)?$`), 120 } 121 122 func readBuildVersion(f exe, addr, size uint64) (string, error) { 123 if size == 0 { 124 size = uint64(f.AddrSize() * 2) 125 } 126 if size != 8 && size != 16 { 127 return "", fmt.Errorf("invalid size for runtime.buildVersion") 128 } 129 data, err := f.ReadData(addr, size) 130 if err != nil { 131 return "", fmt.Errorf("reading runtime.buildVersion: %v", err) 132 } 133 134 if size == 8 { 135 addr = uint64(f.ByteOrder().Uint32(data)) 136 size = uint64(f.ByteOrder().Uint32(data[4:])) 137 } else { 138 addr = f.ByteOrder().Uint64(data) 139 size = f.ByteOrder().Uint64(data[8:]) 140 } 141 if size > 1000 { 142 return "", fmt.Errorf("implausible string size %d for runtime.buildVersion", size) 143 } 144 145 data, err = f.ReadData(addr, size) 146 if err != nil { 147 return "", fmt.Errorf("reading runtime.buildVersion string data: %v", err) 148 } 149 return string(data), nil 150 } 151 152 // Code signatures that indicate BoringCrypto or crypto/internal/fipsonly. 153 // These are not byte literals in order to avoid the actual 154 // byte signatures appearing in the goversion binary, 155 // because on some systems you can't tell rodata from text. 156 var ( 157 sigBoringCrypto, _ = hex.DecodeString("EB1DF448F44BF4B332F52813A3B450D441CC2485F001454E92101B1D2F1950C3") 158 sigStandardCrypto, _ = hex.DecodeString("EB1DF448F44BF4BAEE4DFA9851CA56A91145E83E99C59CF911CB8E80DAF12FC3") 159 sigFIPSOnly, _ = hex.DecodeString("EB1DF448F44BF4363CB9CE9D68047D31F28D325D5CA5873F5D80CAF6D6151BC3") 160 ) 161 162 func findCryptoSigs(v *Version, f exe) error { 163 const maxSigLen = 1 << 10 164 start, end := f.TextRange() 165 for addr := start; addr < end; { 166 size := uint64(1 << 20) 167 if end-addr < size { 168 size = end - addr 169 } 170 data, err := f.ReadData(addr, size) 171 if err != nil { 172 return fmt.Errorf("reading text: %v", err) 173 } 174 if haveSig(data, sigBoringCrypto) { 175 v.BoringCrypto = true 176 } 177 if haveSig(data, sigFIPSOnly) { 178 v.FIPSOnly = true 179 } 180 if haveSig(data, sigStandardCrypto) { 181 v.StandardCrypto = true 182 } 183 if addr+size < end { 184 size -= maxSigLen 185 } 186 addr += size 187 } 188 return nil 189 } 190 191 func haveSig(data, sig []byte) bool { 192 const align = 16 193 for { 194 i := bytes.Index(data, sig) 195 if i < 0 { 196 return false 197 } 198 if i&(align-1) == 0 { 199 return true 200 } 201 // Found unaligned match; unexpected but 202 // skip to next aligned boundary and keep searching. 203 data = data[(i+align-1)&^(align-1):] 204 } 205 } 206 207 func findModuleInfo(v *Version, f exe) error { 208 const maxModInfo = 128 << 10 209 start, end := f.RODataRange() 210 for addr := start; addr < end; { 211 size := uint64(4 << 20) 212 if end-addr < size { 213 size = end - addr 214 } 215 data, err := f.ReadData(addr, size) 216 if err != nil { 217 return fmt.Errorf("reading text: %v", err) 218 } 219 if haveModuleInfo(data, v) { 220 return nil 221 } 222 if addr+size < end { 223 size -= maxModInfo 224 } 225 addr += size 226 } 227 return nil 228 } 229 230 var ( 231 infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") 232 infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") 233 ) 234 235 func haveModuleInfo(data []byte, v *Version) bool { 236 i := bytes.Index(data, infoStart) 237 if i < 0 { 238 return false 239 } 240 j := bytes.Index(data[i:], infoEnd) 241 if j < 0 { 242 return false 243 } 244 v.ModuleInfo = string(data[i+len(infoStart) : i+j]) 245 return true 246 }