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