| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package goversion reports the Go version used to build program executables. |
| // |
| // This is a copy of rsc.io/goversion/version. We renamed the package to goversion |
| // to differentiate between version package in standard library that we also use. |
| package goversion |
| |
| import ( |
| "bytes" |
| "encoding/hex" |
| "errors" |
| "fmt" |
| "regexp" |
| "strings" |
| ) |
| |
| // Version is the information reported by ReadExe. |
| type Version struct { |
| Release string // Go version (runtime.Version in the program) |
| ModuleInfo string // program's module information |
| BoringCrypto bool // program uses BoringCrypto |
| StandardCrypto bool // program uses standard crypto (replaced by BoringCrypto) |
| FIPSOnly bool // program imports "crypto/tls/fipsonly" |
| } |
| |
| // ReadExe reports information about the Go version used to build |
| // the program executable named by file. |
| func ReadExe(file string) (Version, error) { |
| var v Version |
| f, err := openExe(file) |
| if err != nil { |
| return v, err |
| } |
| defer f.Close() |
| isGo := false |
| for _, name := range f.SectionNames() { |
| if name == ".note.go.buildid" { |
| isGo = true |
| } |
| } |
| syms, symsErr := f.Symbols() |
| isGccgo := false |
| for _, sym := range syms { |
| name := sym.Name |
| if name == "runtime.main" || name == "main.main" { |
| isGo = true |
| } |
| if strings.HasPrefix(name, "runtime.") && strings.HasSuffix(name, "$descriptor") { |
| isGccgo = true |
| } |
| if name == "runtime.buildVersion" { |
| isGo = true |
| release, err := readBuildVersion(f, sym.Addr, sym.Size) |
| if err != nil { |
| return v, err |
| } |
| v.Release = release |
| } |
| // Note: Using strings.HasPrefix because Go 1.17+ adds ".abi0" to many of these symbols. |
| if strings.Contains(name, "_Cfunc__goboringcrypto_") || strings.HasPrefix(name, "crypto/internal/boring/sig.BoringCrypto") { |
| v.BoringCrypto = true |
| } |
| if strings.HasPrefix(name, "crypto/internal/boring/sig.FIPSOnly") { |
| v.FIPSOnly = true |
| } |
| for _, re := range standardCryptoNames { |
| if re.MatchString(name) { |
| v.StandardCrypto = true |
| } |
| } |
| if strings.HasPrefix(name, "crypto/internal/boring/sig.StandardCrypto") { |
| v.StandardCrypto = true |
| } |
| } |
| |
| if DebugMatch { |
| v.Release = "" |
| } |
| if err := findModuleInfo(&v, f); err != nil { |
| return v, err |
| } |
| if v.Release == "" { |
| g, release := readBuildVersionX86Asm(f) |
| if g { |
| isGo = true |
| v.Release = release |
| if err := findCryptoSigs(&v, f); err != nil { |
| return v, err |
| } |
| } |
| } |
| if isGccgo && v.Release == "" { |
| isGo = true |
| v.Release = "gccgo (version unknown)" |
| } |
| if !isGo && symsErr != nil { |
| return v, symsErr |
| } |
| |
| if !isGo { |
| return v, errors.New("not a Go executable") |
| } |
| if v.Release == "" { |
| v.Release = "unknown Go version" |
| } |
| return v, nil |
| } |
| |
| var re = regexp.MustCompile |
| |
| var standardCryptoNames = []*regexp.Regexp{ |
| re(`^crypto/sha1\.\(\*digest\)`), |
| re(`^crypto/sha256\.\(\*digest\)`), |
| re(`^crypto/rand\.\(\*devReader\)`), |
| re(`^crypto/rsa\.encrypt(\.abi.)?$`), |
| re(`^crypto/rsa\.decrypt(\.abi.)?$`), |
| } |
| |
| func readBuildVersion(f exe, addr, size uint64) (string, error) { |
| if size == 0 { |
| size = uint64(f.AddrSize() * 2) |
| } |
| if size != 8 && size != 16 { |
| return "", fmt.Errorf("invalid size for runtime.buildVersion") |
| } |
| data, err := f.ReadData(addr, size) |
| if err != nil { |
| return "", fmt.Errorf("reading runtime.buildVersion: %v", err) |
| } |
| |
| if size == 8 { |
| addr = uint64(f.ByteOrder().Uint32(data)) |
| size = uint64(f.ByteOrder().Uint32(data[4:])) |
| } else { |
| addr = f.ByteOrder().Uint64(data) |
| size = f.ByteOrder().Uint64(data[8:]) |
| } |
| if size > 1000 { |
| return "", fmt.Errorf("implausible string size %d for runtime.buildVersion", size) |
| } |
| |
| data, err = f.ReadData(addr, size) |
| if err != nil { |
| return "", fmt.Errorf("reading runtime.buildVersion string data: %v", err) |
| } |
| return string(data), nil |
| } |
| |
| // Code signatures that indicate BoringCrypto or crypto/internal/fipsonly. |
| // These are not byte literals in order to avoid the actual |
| // byte signatures appearing in the goversion binary, |
| // because on some systems you can't tell rodata from text. |
| var ( |
| sigBoringCrypto, _ = hex.DecodeString("EB1DF448F44BF4B332F52813A3B450D441CC2485F001454E92101B1D2F1950C3") |
| sigStandardCrypto, _ = hex.DecodeString("EB1DF448F44BF4BAEE4DFA9851CA56A91145E83E99C59CF911CB8E80DAF12FC3") |
| sigFIPSOnly, _ = hex.DecodeString("EB1DF448F44BF4363CB9CE9D68047D31F28D325D5CA5873F5D80CAF6D6151BC3") |
| ) |
| |
| func findCryptoSigs(v *Version, f exe) error { |
| const maxSigLen = 1 << 10 |
| start, end := f.TextRange() |
| for addr := start; addr < end; { |
| size := uint64(1 << 20) |
| if end-addr < size { |
| size = end - addr |
| } |
| data, err := f.ReadData(addr, size) |
| if err != nil { |
| return fmt.Errorf("reading text: %v", err) |
| } |
| if haveSig(data, sigBoringCrypto) { |
| v.BoringCrypto = true |
| } |
| if haveSig(data, sigFIPSOnly) { |
| v.FIPSOnly = true |
| } |
| if haveSig(data, sigStandardCrypto) { |
| v.StandardCrypto = true |
| } |
| if addr+size < end { |
| size -= maxSigLen |
| } |
| addr += size |
| } |
| return nil |
| } |
| |
| func haveSig(data, sig []byte) bool { |
| const align = 16 |
| for { |
| i := bytes.Index(data, sig) |
| if i < 0 { |
| return false |
| } |
| if i&(align-1) == 0 { |
| return true |
| } |
| // Found unaligned match; unexpected but |
| // skip to next aligned boundary and keep searching. |
| data = data[(i+align-1)&^(align-1):] |
| } |
| } |
| |
| func findModuleInfo(v *Version, f exe) error { |
| const maxModInfo = 128 << 10 |
| start, end := f.RODataRange() |
| for addr := start; addr < end; { |
| size := uint64(4 << 20) |
| if end-addr < size { |
| size = end - addr |
| } |
| data, err := f.ReadData(addr, size) |
| if err != nil { |
| return fmt.Errorf("reading text: %v", err) |
| } |
| if haveModuleInfo(data, v) { |
| return nil |
| } |
| if addr+size < end { |
| size -= maxModInfo |
| } |
| addr += size |
| } |
| return nil |
| } |
| |
| var ( |
| infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") |
| infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") |
| ) |
| |
| func haveModuleInfo(data []byte, v *Version) bool { |
| i := bytes.Index(data, infoStart) |
| if i < 0 { |
| return false |
| } |
| j := bytes.Index(data[i:], infoEnd) |
| if j < 0 { |
| return false |
| } |
| v.ModuleInfo = string(data[i+len(infoStart) : i+j]) |
| return true |
| } |