github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/internal/relationship/binary/binary_dependencies.go (about)

     1  package binary
     2  
     3  import (
     4  	"path"
     5  
     6  	"github.com/anchore/syft/internal/log"
     7  	"github.com/anchore/syft/internal/sbomsync"
     8  	"github.com/anchore/syft/syft/artifact"
     9  	"github.com/anchore/syft/syft/file"
    10  	"github.com/anchore/syft/syft/pkg"
    11  	"github.com/anchore/syft/syft/sbom"
    12  )
    13  
    14  func NewDependencyRelationships(resolver file.Resolver, accessor sbomsync.Accessor) []artifact.Relationship {
    15  	// TODO: consider library format (e.g. ELF, Mach-O, PE) for the meantime assume all binaries are homogeneous format
    16  	// start with building new package-to-package relationships for executables-to-executables
    17  	// each relationship must be unique, store in a map[id]map[id]relationship to avoid duplicates
    18  	// 1 & 2... build an index of all shared libraries and their owning packages to search against
    19  	index := newShareLibIndex(resolver, accessor)
    20  
    21  	// 3. craft package-to-package relationships for each binary that represent shared library dependencies
    22  	//note: we only care about package-to-package relationships
    23  	var relIndex *relationshipIndex
    24  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    25  		relIndex = newRelationshipIndex(s.Relationships...)
    26  	})
    27  
    28  	return generateRelationships(resolver, accessor, index, relIndex)
    29  }
    30  
    31  func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, index *sharedLibraryIndex, relIndex *relationshipIndex) []artifact.Relationship {
    32  	// read all existing dependencyOf relationships
    33  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    34  		for _, r := range s.Relationships {
    35  			if r.Type != artifact.DependencyOfRelationship {
    36  				continue
    37  			}
    38  			relIndex.track(r)
    39  		}
    40  	})
    41  
    42  	// find all package-to-package relationships for shared library dependencies
    43  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    44  		for _, parentPkg := range s.Artifacts.Packages.Sorted(pkg.BinaryPkg) {
    45  			for _, evidentLocation := range parentPkg.Locations.ToSlice() {
    46  				if evidentLocation.Annotations[pkg.EvidenceAnnotationKey] != pkg.PrimaryEvidenceAnnotation {
    47  					continue
    48  				}
    49  
    50  				// find all libraries that this package depends on
    51  				exec, ok := s.Artifacts.Executables[evidentLocation.Coordinates]
    52  				if !ok {
    53  					continue
    54  				}
    55  
    56  				populateRelationships(exec, parentPkg, resolver, relIndex, index)
    57  			}
    58  		}
    59  	})
    60  
    61  	return relIndex.newRelationships()
    62  }
    63  
    64  // PackagesToRemove returns a list of binary packages (resolved by the ELF cataloger) that should be removed from the SBOM
    65  // These packages are removed because they are already represented by a higher order packages in the SBOM.
    66  func PackagesToRemove(resolver file.Resolver, accessor sbomsync.Accessor) []artifact.ID {
    67  	pkgsToDelete := make([]artifact.ID, 0)
    68  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    69  		// OTHER package type > ELF package type > Binary package type
    70  		pkgsToDelete = append(pkgsToDelete, getBinaryPackagesToDelete(resolver, s)...)
    71  		pkgsToDelete = append(pkgsToDelete, compareElfBinaryPackages(s)...)
    72  	})
    73  	return pkgsToDelete
    74  }
    75  
    76  func compareElfBinaryPackages(s *sbom.SBOM) []artifact.ID {
    77  	pkgsToDelete := make([]artifact.ID, 0)
    78  	for _, elfPkg := range allElfPackages(s) {
    79  		for _, loc := range onlyPrimaryEvidenceLocations(elfPkg) {
    80  			for _, otherPkg := range s.Artifacts.Packages.PackagesByPath(loc.RealPath) {
    81  				// we only care about comparing binary packages to each other (not other types)
    82  				if otherPkg.Type != pkg.BinaryPkg {
    83  					continue
    84  				}
    85  				if !isElfPackage(otherPkg) {
    86  					pkgsToDelete = append(pkgsToDelete, otherPkg.ID())
    87  				}
    88  			}
    89  		}
    90  	}
    91  	return pkgsToDelete
    92  }
    93  
    94  func onlyPrimaryEvidenceLocations(p pkg.Package) []file.Location {
    95  	var locs []file.Location
    96  	for _, loc := range p.Locations.ToSlice() {
    97  		if loc.Annotations[pkg.EvidenceAnnotationKey] != pkg.PrimaryEvidenceAnnotation {
    98  			continue
    99  		}
   100  		locs = append(locs, loc)
   101  	}
   102  
   103  	return locs
   104  }
   105  
   106  func allElfPackages(s *sbom.SBOM) []pkg.Package {
   107  	var elfPkgs []pkg.Package
   108  	for _, p := range s.Artifacts.Packages.Sorted(pkg.BinaryPkg) {
   109  		if !isElfPackage(p) {
   110  			continue
   111  		}
   112  		elfPkgs = append(elfPkgs, p)
   113  	}
   114  	return elfPkgs
   115  }
   116  
   117  func isElfPackage(p pkg.Package) bool {
   118  	_, ok := p.Metadata.(pkg.ELFBinaryPackageNoteJSONPayload)
   119  	return ok
   120  }
   121  
   122  func getBinaryPackagesToDelete(resolver file.Resolver, s *sbom.SBOM) []artifact.ID {
   123  	pkgsToDelete := make([]artifact.ID, 0)
   124  	for p := range s.Artifacts.Packages.Enumerate() {
   125  		if p.Type == pkg.BinaryPkg {
   126  			continue
   127  		}
   128  		fileOwner, ok := p.Metadata.(pkg.FileOwner)
   129  		if !ok {
   130  			continue
   131  		}
   132  		ownedFiles := fileOwner.OwnedFiles()
   133  		locations, err := resolver.FilesByPath(ownedFiles...)
   134  		if err != nil {
   135  			log.WithFields("error", err).Trace("unable to find path for owned file")
   136  			continue
   137  		}
   138  		for _, loc := range locations {
   139  			for _, pathPkg := range s.Artifacts.Packages.PackagesByPath(loc.RealPath) {
   140  				if pathPkg.Type == pkg.BinaryPkg {
   141  					pkgsToDelete = append(pkgsToDelete, pathPkg.ID())
   142  				}
   143  			}
   144  		}
   145  	}
   146  	return pkgsToDelete
   147  }
   148  
   149  func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, relIndex *relationshipIndex, index *sharedLibraryIndex) {
   150  	for _, libReference := range exec.ImportedLibraries {
   151  		// for each library reference, check s.Artifacts.Packages.Sorted(pkg.BinaryPkg) for a binary package that represents that library
   152  		// if found, create a relationship between the parent package and the library package
   153  		// if not found do nothing.
   154  		// note: we only care about package-to-package relationships
   155  
   156  		// find the basename of the library
   157  		libBasename := path.Base(libReference)
   158  		libLocations, err := resolver.FilesByGlob("**/" + libBasename)
   159  		if err != nil {
   160  			log.WithFields("lib", libReference, "error", err).Trace("unable to resolve library basename")
   161  			continue
   162  		}
   163  
   164  		for _, loc := range libLocations {
   165  			// are you in our index?
   166  			realBaseName := path.Base(loc.RealPath)
   167  			pkgCollection := index.owningLibraryPackage(realBaseName)
   168  			if pkgCollection.PackageCount() < 1 {
   169  				relIndex.add(
   170  					artifact.Relationship{
   171  						From: loc.Coordinates,
   172  						To:   parentPkg,
   173  						Type: artifact.DependencyOfRelationship,
   174  					},
   175  				)
   176  			}
   177  			for _, p := range pkgCollection.Sorted() {
   178  				relIndex.add(
   179  					artifact.Relationship{
   180  						From: p,
   181  						To:   parentPkg,
   182  						Type: artifact.DependencyOfRelationship,
   183  					},
   184  				)
   185  			}
   186  		}
   187  	}
   188  }