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  }