src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }