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