github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/rust/parse_audit_binary.go (about)

     1  package rust
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/rust-secure-code/go-rustaudit"
     9  
    10  	"github.com/anchore/syft/internal/log"
    11  	"github.com/anchore/syft/internal/relationship"
    12  	"github.com/anchore/syft/syft/artifact"
    13  	"github.com/anchore/syft/syft/file"
    14  	"github.com/anchore/syft/syft/internal/unionreader"
    15  	"github.com/anchore/syft/syft/pkg"
    16  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    17  )
    18  
    19  // Catalog identifies executables then attempts to read Rust dependency information from them
    20  func parseAuditBinary(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    21  	var pkgs []pkg.Package
    22  	var relationships []artifact.Relationship
    23  
    24  	unionReader, err := unionreader.GetUnionReader(reader.ReadCloser)
    25  	if err != nil {
    26  		return nil, nil, err
    27  	}
    28  
    29  	infos, err := parseAuditBinaryEntry(unionReader, reader.RealPath)
    30  	for _, versionInfo := range infos {
    31  		auditPkgs, auditRelationships := processAuditVersionInfo(reader.Location, versionInfo)
    32  		pkgs = append(pkgs, auditPkgs...)
    33  		relationships = append(relationships, auditRelationships...)
    34  	}
    35  
    36  	return pkgs, relationships, err
    37  }
    38  
    39  // scanFile scans file to try to report the Rust crate dependencies
    40  func parseAuditBinaryEntry(reader unionreader.UnionReader, filename string) ([]rustaudit.VersionInfo, error) {
    41  	// NOTE: multiple readers are returned to cover universal binaries, which are files
    42  	// with more than one binary
    43  	readers, err := unionreader.GetReaders(reader)
    44  	if err != nil {
    45  		log.Debugf("rust cataloger: failed to open a binary: %v", err)
    46  		return nil, fmt.Errorf("rust cataloger: failed to open a binary: %w", err)
    47  	}
    48  
    49  	var versionInfos []rustaudit.VersionInfo
    50  	for _, r := range readers {
    51  		versionInfo, err := rustaudit.GetDependencyInfo(r)
    52  
    53  		if err != nil {
    54  			if errors.Is(err, rustaudit.ErrNoRustDepInfo) {
    55  				// since the cataloger can only select executables and not distinguish if they are a Rust-compiled
    56  				// binary, we should not show warnings/logs in this case.
    57  				return nil, nil
    58  			}
    59  			log.Tracef("rust cataloger: unable to read dependency information (file=%q): %v", filename, err)
    60  			return nil, fmt.Errorf("rust cataloger: unable to read dependency information: %w", err)
    61  		}
    62  
    63  		versionInfos = append(versionInfos, versionInfo)
    64  	}
    65  
    66  	return versionInfos, nil
    67  }
    68  
    69  // auditPkgPair is a helper struct to track the original index of the package in the original audit report + the syft package created for it
    70  type auditPkgPair struct {
    71  	pkg     *pkg.Package
    72  	rustPkg rustaudit.Package
    73  	index   int
    74  }
    75  
    76  func processAuditVersionInfo(location file.Location, versionInfo rustaudit.VersionInfo) ([]pkg.Package, []artifact.Relationship) {
    77  	var pkgs []pkg.Package
    78  
    79  	// first pass: create packages for all runtime dependencies (skip dev and invalid dependencies)
    80  	pairsByOgIndex := make(map[int]auditPkgPair)
    81  	for idx, dep := range versionInfo.Packages {
    82  		p := newPackageFromAudit(&dep, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
    83  		pair := auditPkgPair{
    84  			rustPkg: dep,
    85  			index:   idx,
    86  		}
    87  		if pkg.IsValid(&p) && dep.Kind == rustaudit.Runtime {
    88  			pkgs = append(pkgs, p)
    89  			pair.pkg = &pkgs[len(pkgs)-1]
    90  		}
    91  		pairsByOgIndex[idx] = pair
    92  	}
    93  
    94  	// second pass: create relationships between any packages created
    95  	// we have all the original audit package indices + info, but not all audit packages will have syft packages.
    96  	// we need to be careful to not create relationships for packages that were not created.
    97  	var rels []artifact.Relationship
    98  	for _, parentPair := range pairsByOgIndex {
    99  		// the rust-audit report lists dependencies by index from the original version info object. We need to find
   100  		// the syft packages created for each listed dependency from that original object.
   101  		for _, ogPkgIndex := range parentPair.rustPkg.Dependencies {
   102  			if ogPkgIndex >= uint(len(versionInfo.Packages)) {
   103  				log.WithFields("pkg", parentPair.pkg).Trace("cargo audit dependency index out of range: %d", ogPkgIndex)
   104  				continue
   105  			}
   106  			depPair, ok := pairsByOgIndex[int(ogPkgIndex)]
   107  			if !ok {
   108  				log.WithFields("pkg", parentPair.pkg).Trace("cargo audit dependency not found: %d", ogPkgIndex)
   109  				continue
   110  			}
   111  
   112  			if depPair.pkg == nil || parentPair.pkg == nil {
   113  				// skip relationships for syft packages that were not created from the original report (no matter the reason)
   114  				continue
   115  			}
   116  
   117  			rels = append(rels, artifact.Relationship{
   118  				From: *depPair.pkg,
   119  				To:   *parentPair.pkg,
   120  				Type: artifact.DependencyOfRelationship,
   121  			})
   122  		}
   123  	}
   124  
   125  	relationship.Sort(rels)
   126  
   127  	return pkgs, rels
   128  }