github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/relationships_by_file_ownership.go (about)

     1  package pkg
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/bmatcuk/doublestar/v4"
     7  	"github.com/scylladb/go-set/strset"
     8  
     9  	"github.com/anchore/syft/internal/log"
    10  	"github.com/anchore/syft/syft/artifact"
    11  )
    12  
    13  // AltRpmDBGlob allows db matches against new locations introduced in fedora:{36,37}
    14  // See https://github.com/anchore/syft/issues/1077 for larger context
    15  const AltRpmDBGlob = "**/rpm/{Packages,Packages.db,rpmdb.sqlite}"
    16  
    17  var globsForbiddenFromBeingOwned = []string{
    18  	// any OS DBs should automatically be ignored to prevent cyclic issues (e.g. the "rpm" RPM owns the path to the
    19  	// RPM DB, so if not ignored that package would own all other packages on the system).
    20  	ApkDBGlob,
    21  	DpkgDBGlob,
    22  	RpmDBGlob,
    23  	AltRpmDBGlob,
    24  	// DEB packages share common copyright info between, this does not mean that sharing these paths implies ownership.
    25  	"/usr/share/doc/**/copyright",
    26  }
    27  
    28  type ownershipByFilesMetadata struct {
    29  	Files []string `json:"files"`
    30  }
    31  
    32  // RelationshipsByFileOwnership creates a package-to-package relationship based on discovering which packages have
    33  // evidence locations that overlap with ownership claim from another package's package manager metadata.
    34  func RelationshipsByFileOwnership(catalog *Collection) []artifact.Relationship {
    35  	var relationships = findOwnershipByFilesRelationships(catalog)
    36  
    37  	var edges []artifact.Relationship
    38  	for parentID, children := range relationships {
    39  		for childID, files := range children {
    40  			fs := files.List()
    41  			sort.Strings(fs)
    42  			edges = append(edges, artifact.Relationship{
    43  				From: catalog.byID[parentID],
    44  				To:   catalog.byID[childID],
    45  				Type: artifact.OwnershipByFileOverlapRelationship,
    46  				Data: ownershipByFilesMetadata{
    47  					Files: fs,
    48  				},
    49  			})
    50  		}
    51  	}
    52  
    53  	return edges
    54  }
    55  
    56  // findOwnershipByFilesRelationships find overlaps in file ownership with a file that defines another package. Specifically, a .Location.Path of
    57  // a package is found to be owned by another (from the owner's .Metadata.Files[]).
    58  func findOwnershipByFilesRelationships(catalog *Collection) map[artifact.ID]map[artifact.ID]*strset.Set {
    59  	var relationships = make(map[artifact.ID]map[artifact.ID]*strset.Set)
    60  
    61  	if catalog == nil {
    62  		return relationships
    63  	}
    64  
    65  	for _, candidateOwnerPkg := range catalog.Sorted() {
    66  		id := candidateOwnerPkg.ID()
    67  		if candidateOwnerPkg.Metadata == nil {
    68  			continue
    69  		}
    70  
    71  		// check to see if this is a file owner
    72  		pkgFileOwner, ok := candidateOwnerPkg.Metadata.(FileOwner)
    73  		if !ok {
    74  			continue
    75  		}
    76  		for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
    77  			if matchesAny(ownedFilePath, globsForbiddenFromBeingOwned) {
    78  				// we skip over known exceptions to file ownership, such as the RPM package owning
    79  				// the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended
    80  				continue
    81  			}
    82  
    83  			// look for package(s) in the catalog that may be owned by this package and mark the relationship
    84  			for _, subPackage := range catalog.PackagesByPath(ownedFilePath) {
    85  				subID := subPackage.ID()
    86  				if subID == id {
    87  					continue
    88  				}
    89  				if _, exists := relationships[id]; !exists {
    90  					relationships[id] = make(map[artifact.ID]*strset.Set)
    91  				}
    92  
    93  				if _, exists := relationships[id][subID]; !exists {
    94  					relationships[id][subID] = strset.New()
    95  				}
    96  				relationships[id][subID].Add(ownedFilePath)
    97  			}
    98  		}
    99  	}
   100  
   101  	return relationships
   102  }
   103  
   104  func matchesAny(s string, globs []string) bool {
   105  	for _, g := range globs {
   106  		matches, err := doublestar.Match(g, s)
   107  		if err != nil {
   108  			log.Errorf("failed to match glob=%q : %+v", g, err)
   109  		}
   110  		if matches {
   111  			return true
   112  		}
   113  	}
   114  	return false
   115  }