github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/internal/relationship/binary/shared_library_index.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/file"
     9  	"github.com/anchore/syft/syft/pkg"
    10  	"github.com/anchore/syft/syft/sbom"
    11  )
    12  
    13  type sharedLibraryIndex struct {
    14  	libLocationsByBasename map[string]file.CoordinateSet
    15  	allLibLocations        file.CoordinateSet
    16  	packagesByLibraryPath  map[file.Coordinates]*pkg.Collection
    17  }
    18  
    19  func newShareLibIndex(resolver file.Resolver, accessor sbomsync.Accessor) *sharedLibraryIndex {
    20  	s := &sharedLibraryIndex{
    21  		libLocationsByBasename: make(map[string]file.CoordinateSet),
    22  		allLibLocations:        file.NewCoordinateSet(),
    23  		packagesByLibraryPath:  make(map[file.Coordinates]*pkg.Collection),
    24  	}
    25  
    26  	s.build(resolver, accessor)
    27  
    28  	return s
    29  }
    30  
    31  func (i *sharedLibraryIndex) build(resolver file.Resolver, accessor sbomsync.Accessor) {
    32  	// 1. map out all locations that provide libraries (indexed by the basename)
    33  	i.libLocationsByBasename, i.allLibLocations = locationsThatProvideLibraries(accessor)
    34  
    35  	// 2. for each library path, find all packages that claim ownership of the library
    36  	i.packagesByLibraryPath = packagesWithLibraryOwnership(resolver, accessor, i.allLibLocations)
    37  }
    38  
    39  func (i *sharedLibraryIndex) owningLibraryPackage(libraryBasename string) *pkg.Collection {
    40  	// find all packages that own a library by its basename
    41  	collection := pkg.NewCollection()
    42  	if set, ok := i.libLocationsByBasename[libraryBasename]; ok {
    43  		for _, coord := range set.ToSlice() {
    44  			if pkgSet, ok := i.packagesByLibraryPath[coord]; ok {
    45  				toAdd := pkgSet.Sorted()
    46  				collection.Add(toAdd...)
    47  			}
    48  		}
    49  	}
    50  
    51  	return collection
    52  }
    53  
    54  func locationsThatProvideLibraries(accessor sbomsync.Accessor) (map[string]file.CoordinateSet, file.CoordinateSet) {
    55  	// map out all locations that provide libraries (indexed by the basename)
    56  	libLocationsByBasename := make(map[string]file.CoordinateSet)
    57  	allLibLocations := file.NewCoordinateSet()
    58  
    59  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    60  		// PROBLEM: this does not consider all symlinks to real paths that are libraries
    61  		for coord, f := range s.Artifacts.Executables {
    62  			if !f.HasExports {
    63  				continue
    64  			}
    65  
    66  			basename := path.Base(coord.RealPath)
    67  			set := libLocationsByBasename[basename]
    68  			set.Add(coord)
    69  			allLibLocations.Add(coord)
    70  			libLocationsByBasename[basename] = set
    71  		}
    72  	})
    73  
    74  	return libLocationsByBasename, allLibLocations
    75  }
    76  func packagesWithLibraryOwnership(resolver file.Resolver, accessor sbomsync.Accessor, allLibLocations file.CoordinateSet) map[file.Coordinates]*pkg.Collection {
    77  	// map out all packages that claim ownership of a library at a specific path
    78  	packagesByLibraryPath := make(map[file.Coordinates]*pkg.Collection)
    79  
    80  	accessor.ReadFromSBOM(func(s *sbom.SBOM) {
    81  		for _, p := range s.Artifacts.Packages.Sorted() {
    82  			var ownedFilePaths []string
    83  			if p.Type == pkg.BinaryPkg {
    84  				for _, loc := range p.Locations.ToSlice() {
    85  					ownedFilePaths = append(ownedFilePaths, loc.Path())
    86  				}
    87  			} else {
    88  				fileOwner, ok := p.Metadata.(pkg.FileOwner)
    89  				if !ok {
    90  					continue
    91  				}
    92  				ownedFilePaths = fileOwner.OwnedFiles()
    93  			}
    94  
    95  			packagesByLibraryPath = populatePackagesByLibraryPath(resolver, allLibLocations, packagesByLibraryPath, p, ownedFilePaths)
    96  		}
    97  	})
    98  
    99  	return packagesByLibraryPath
   100  }
   101  
   102  func populatePackagesByLibraryPath(
   103  	resolver file.Resolver,
   104  	allLibLocations file.CoordinateSet,
   105  	packagesByLibraryPath map[file.Coordinates]*pkg.Collection,
   106  	p pkg.Package,
   107  	ownedFilePaths []string,
   108  ) map[file.Coordinates]*pkg.Collection {
   109  	for _, pth := range ownedFilePaths {
   110  		ownedLocation, err := resolver.FilesByPath(pth)
   111  		if err != nil {
   112  			log.WithFields("error", err, "path", pth).Trace("unable to find path for owned file")
   113  			continue
   114  		}
   115  
   116  		for _, loc := range ownedLocation {
   117  			// if the location is a library, add the package to the set of packages that own the library
   118  			if !allLibLocations.Contains(loc.Coordinates) {
   119  				continue
   120  			}
   121  
   122  			if _, ok := packagesByLibraryPath[loc.Coordinates]; !ok {
   123  				packagesByLibraryPath[loc.Coordinates] = pkg.NewCollection()
   124  			}
   125  
   126  			// we have a library path, add the package to the set of packages that own the library
   127  			packagesByLibraryPath[loc.Coordinates].Add(p)
   128  		}
   129  	}
   130  	return packagesByLibraryPath
   131  }