github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/golang/scan_binary.go (about) 1 package golang 2 3 import ( 4 "debug/buildinfo" 5 "fmt" 6 "io" 7 "runtime/debug" 8 9 "github.com/kastenhq/goversion/version" 10 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" 13 ) 14 15 type extendedBuildInfo struct { 16 *debug.BuildInfo 17 cryptoSettings []string 18 arch string 19 } 20 21 // scanFile scans file to try to report the Go and module versions. 22 func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildInfo { 23 // NOTE: multiple readers are returned to cover universal binaries, which are files 24 // with more than one binary 25 readers, err := unionreader.GetReaders(reader) 26 if err != nil { 27 log.WithFields("error", err).Warnf("failed to open a golang binary") 28 return nil 29 } 30 31 var builds []*extendedBuildInfo 32 for _, r := range readers { 33 bi, err := getBuildInfo(r) 34 if err != nil { 35 log.WithFields("file", filename, "error", err).Trace("unable to read golang buildinfo") 36 continue 37 } 38 // it's possible the reader just isn't a go binary, in which case just skip it 39 if bi == nil { 40 continue 41 } 42 43 v, err := getCryptoInformation(r) 44 if err != nil { 45 log.WithFields("file", filename, "error", err).Trace("unable to read golang version info") 46 // don't skip this build info. 47 // we can still catalog packages, even if we can't get the crypto information 48 } 49 arch := getGOARCH(bi.Settings) 50 if arch == "" { 51 arch, err = getGOARCHFromBin(r) 52 if err != nil { 53 log.WithFields("file", filename, "error", err).Trace("unable to read golang arch info") 54 // don't skip this build info. 55 // we can still catalog packages, even if we can't get the arch information 56 } 57 } 58 59 builds = append(builds, &extendedBuildInfo{bi, v, arch}) 60 } 61 return builds 62 } 63 64 func getCryptoInformation(reader io.ReaderAt) ([]string, error) { 65 v, err := version.ReadExeFromReader(reader) 66 if err != nil { 67 return nil, err 68 } 69 70 return getCryptoSettingsFromVersion(v), nil 71 } 72 73 func getCryptoSettingsFromVersion(v version.Version) []string { 74 cryptoSettings := []string{} 75 if v.StandardCrypto { 76 cryptoSettings = append(cryptoSettings, "standard-crypto") 77 } 78 if v.BoringCrypto { 79 cryptoSettings = append(cryptoSettings, "boring-crypto") 80 } 81 if v.FIPSOnly { 82 cryptoSettings = append(cryptoSettings, "crypto/tls/fipsonly") 83 } 84 return cryptoSettings 85 } 86 87 func getBuildInfo(r io.ReaderAt) (bi *debug.BuildInfo, err error) { 88 defer func() { 89 if r := recover(); r != nil { 90 // this can happen in cases where a malformed binary is passed in can be initially parsed, but not 91 // used without error later down the line. This is the case with : 92 // https://github.com/llvm/llvm-project/blob/llvmorg-15.0.6/llvm/test/Object/Inputs/macho-invalid-dysymtab-bad-size 93 err = fmt.Errorf("recovered from panic: %v", r) 94 } 95 }() 96 bi, err = buildinfo.Read(r) 97 98 // note: the stdlib does not export the error we need to check for 99 if err != nil { 100 if err.Error() == "not a Go executable" { 101 // since the cataloger can only select executables and not distinguish if they are a go-compiled 102 // binary, we should not show warnings/logs in this case. For this reason we nil-out err here. 103 err = nil 104 return 105 } 106 // in this case we could not read the or parse the file, but not explicitly because it is not a 107 // go-compiled binary (though it still might be). 108 return 109 } 110 return 111 }