From 573440b7cf82cc1f21fdeec0da5d744d6b110db5 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 2 Jul 2024 16:07:08 -0400 Subject: [PATCH] Infer the package type from ELF package notes (#3008) * fix ELF package types to be honored Signed-off-by: Alex Goodman * prefer OS packages over binary packages when there are duplicates Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- .../integration/mariner_distroless_test.go | 3 ++- ...lude_binaries_by_file_ownership_overlap.go | 17 ++++++++++++- ...binaries_by_file_ownership_overlap_test.go | 24 ++++++++++++++++++- syft/pkg/cataloger/binary/elf_package.go | 22 ++++++++++++++--- .../binary/elf_package_cataloger_test.go | 4 ++-- 5 files changed, 62 insertions(+), 8 deletions(-) diff --git a/cmd/syft/internal/test/integration/mariner_distroless_test.go b/cmd/syft/internal/test/integration/mariner_distroless_test.go index 5231f325427..f45098aff67 100644 --- a/cmd/syft/internal/test/integration/mariner_distroless_test.go +++ b/cmd/syft/internal/test/integration/mariner_distroless_test.go @@ -10,7 +10,8 @@ import ( func TestMarinerDistroless(t *testing.T) { sbom, _ := catalogFixtureImage(t, "image-mariner-distroless", source.SquashedScope) - expectedPkgs := 12 + // 12 RPMs + 2 binaries with ELF package notes claiming to be RPMs + expectedPkgs := 14 actualPkgs := 0 for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) { actualPkgs += 1 diff --git a/internal/relationship/exclude_binaries_by_file_ownership_overlap.go b/internal/relationship/exclude_binaries_by_file_ownership_overlap.go index 03e2d7fbcdf..9733c00e717 100644 --- a/internal/relationship/exclude_binaries_by_file_ownership_overlap.go +++ b/internal/relationship/exclude_binaries_by_file_ownership_overlap.go @@ -1,6 +1,7 @@ package relationship import ( + "reflect" "slices" "github.com/anchore/syft/internal/sbomsync" @@ -21,6 +22,10 @@ var ( binaryCatalogerTypes = []pkg.Type{ pkg.BinaryPkg, } + binaryMetadataTypes = []string{ + reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(), + reflect.TypeOf(pkg.BinarySignature{}).Name(), + } ) func ExcludeBinariesByFileOwnershipOverlap(accessor sbomsync.Accessor) { @@ -60,5 +65,15 @@ func excludeBinaryByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collect return false } - return slices.Contains(binaryCatalogerTypes, child.Type) + if slices.Contains(binaryCatalogerTypes, child.Type) { + return true + } + + if child.Metadata == nil { + return false + } + + childMetadataType := reflect.TypeOf(child.Metadata) + + return slices.Contains(binaryMetadataTypes, childMetadataType.Name()) } diff --git a/internal/relationship/exclude_binaries_by_file_ownership_overlap_test.go b/internal/relationship/exclude_binaries_by_file_ownership_overlap_test.go index 6074e0c162a..e8347937bc2 100644 --- a/internal/relationship/exclude_binaries_by_file_ownership_overlap_test.go +++ b/internal/relationship/exclude_binaries_by_file_ownership_overlap_test.go @@ -12,7 +12,9 @@ func TestExclude(t *testing.T) { packageB := pkg.Package{Name: "package-a", Type: pkg.PythonPkg} packageC := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg} packageD := pkg.Package{Name: "package-d", Type: pkg.BinaryPkg} - for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} { + packageE := pkg.Package{Name: "package-e", Type: pkg.RpmPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}} + packageF := pkg.Package{Name: "package-f", Type: pkg.RpmPkg, Metadata: pkg.BinarySignature{}} + for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE, &packageF} { p := p p.SetID() } @@ -43,6 +45,26 @@ func TestExclude(t *testing.T) { packages: pkg.NewCollection(packageA, packageC), shouldExclude: true, }, + { + name: "exclusions from os -> elf binary (as RPM)", + relationship: artifact.Relationship{ + Type: artifact.OwnershipByFileOverlapRelationship, + From: packageA, + To: packageE, + }, + packages: pkg.NewCollection(packageA, packageE), + shouldExclude: true, + }, + { + name: "exclusions from os -> binary (masquerading as RPM)", + relationship: artifact.Relationship{ + Type: artifact.OwnershipByFileOverlapRelationship, + From: packageA, + To: packageF, + }, + packages: pkg.NewCollection(packageA, packageF), + shouldExclude: true, + }, { name: "no exclusions from python -> binary", relationship: artifact.Relationship{ diff --git a/syft/pkg/cataloger/binary/elf_package.go b/syft/pkg/cataloger/binary/elf_package.go index e2485394297..9b13647a3cc 100644 --- a/syft/pkg/cataloger/binary/elf_package.go +++ b/syft/pkg/cataloger/binary/elf_package.go @@ -13,7 +13,7 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) p Version: metadata.Version, Licenses: pkg.NewLicenseSet(pkg.NewLicense(metadata.License)), PURL: packageURL(metadata), - Type: pkg.BinaryPkg, + Type: pkgType(metadata.Type), Locations: locations, Metadata: metadata.ELFBinaryPackageNoteJSONPayload, } @@ -67,6 +67,8 @@ func packageURL(metadata elfBinaryPackageNotes) string { ).ToString() } +const alpmType = "alpm" + func purlDistroType(ty string) string { switch ty { case "rpm": @@ -75,8 +77,22 @@ func purlDistroType(ty string) string { return packageurl.TypeDebian case "apk": return packageurl.TypeAlpine - case "alpm": - return "alpm" + case alpmType: + return alpmType } return packageurl.TypeGeneric } + +func pkgType(ty string) pkg.Type { + switch ty { + case "rpm": + return pkg.RpmPkg + case "deb": + return pkg.DebPkg + case "apk": + return pkg.ApkPkg + case alpmType: + return pkg.AlpmPkg + } + return pkg.BinaryPkg +} diff --git a/syft/pkg/cataloger/binary/elf_package_cataloger_test.go b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go index f3569e5f492..4ec6bd61f05 100644 --- a/syft/pkg/cataloger/binary/elf_package_cataloger_test.go +++ b/syft/pkg/cataloger/binary/elf_package_cataloger_test.go @@ -77,7 +77,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) { file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Licenses: pkg.NewLicenseSet(), - Type: pkg.BinaryPkg, + Type: pkg.RpmPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{ Type: "rpm", Architecture: "x86_64", @@ -99,7 +99,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) { file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Licenses: pkg.NewLicenseSet(), - Type: pkg.BinaryPkg, + Type: pkg.RpmPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{ Type: "rpm", Architecture: "arm",