diff --git a/syft/format/internal/backfill.go b/syft/format/internal/backfill.go index 8bb274a8b..8c7831d41 100644 --- a/syft/format/internal/backfill.go +++ b/syft/format/internal/backfill.go @@ -48,6 +48,7 @@ func backfillFromPurl(p *pkg.Package) { var cpes []cpe.CPE epoch := "" rpmmod := "" + arch := "" for _, qualifier := range purl.Qualifiers { switch qualifier.Key { @@ -65,6 +66,8 @@ func backfillFromPurl(p *pkg.Package) { epoch = qualifier.Value case pkg.PURLQualifierRpmModularity: rpmmod = qualifier.Value + case pkg.PURLQualifierArch: + arch = qualifier.Value } } @@ -84,9 +87,7 @@ func backfillFromPurl(p *pkg.Package) { setJavaMetadataFromPurl(p, purl) } - if p.Type == pkg.RpmPkg { - setRpmMetadataFromPurl(p, rpmmod) - } + setDistroMetadata(p, rpmmod, arch) for _, c := range cpes { if slices.Contains(p.CPEs, c) { @@ -96,6 +97,19 @@ func backfillFromPurl(p *pkg.Package) { } } +func setDistroMetadata(p *pkg.Package, rpmmod, arch string) { + switch p.Type { + case pkg.RpmPkg: + setRpmMetadataFromPurl(p, rpmmod, arch) + case pkg.DebPkg: + setDpkgMetadataFromPurl(p, arch) + case pkg.AlpmPkg: + setAlpmMetadataFromPurl(p, arch) + case pkg.ApkPkg: + setApkMetadataFromPurl(p, arch) + } +} + func setJavaMetadataFromPurl(p *pkg.Package, _ packageurl.PackageURL) { if p.Type != pkg.JavaPkg { return @@ -107,32 +121,103 @@ func setJavaMetadataFromPurl(p *pkg.Package, _ packageurl.PackageURL) { } } -func setRpmMetadataFromPurl(p *pkg.Package, rpmmod string) { +func setRpmMetadataFromPurl(p *pkg.Package, rpmmod, arch string) { if p.Type != pkg.RpmPkg { return } - if rpmmod == "" { + if rpmmod == "" && arch == "" { return } if p.Metadata == nil { - p.Metadata = pkg.RpmDBEntry{ - ModularityLabel: &rpmmod, - } - return + p.Metadata = pkg.RpmDBEntry{} } switch m := p.Metadata.(type) { case pkg.RpmDBEntry: - if m.ModularityLabel == nil { + if m.ModularityLabel == nil && rpmmod != "" { m.ModularityLabel = &rpmmod - p.Metadata = m } + if m.Arch == "" { + m.Arch = arch + } + p.Metadata = m case pkg.RpmArchive: - if m.ModularityLabel == nil { + if m.ModularityLabel == nil && rpmmod != "" { m.ModularityLabel = &rpmmod - p.Metadata = m } + if m.Arch == "" { + m.Arch = arch + } + p.Metadata = m + } +} + +func setDpkgMetadataFromPurl(p *pkg.Package, arch string) { + if p.Type != pkg.DebPkg { + return + } + if arch == "" { + return + } + + if p.Metadata == nil { + p.Metadata = pkg.DpkgDBEntry{} + } + + switch m := p.Metadata.(type) { + case pkg.DpkgDBEntry: + if m.Architecture == "" { + m.Architecture = arch + } + p.Metadata = m + case pkg.DpkgArchiveEntry: + if m.Architecture == "" { + m.Architecture = arch + } + p.Metadata = m + } +} + +func setAlpmMetadataFromPurl(p *pkg.Package, arch string) { + if p.Type != pkg.AlpmPkg { + return + } + if arch == "" { + return + } + + if p.Metadata == nil { + p.Metadata = pkg.AlpmDBEntry{Architecture: arch} + return + } + + if m, ok := p.Metadata.(pkg.AlpmDBEntry); ok { + if m.Architecture == "" { + m.Architecture = arch + } + p.Metadata = m + } +} + +func setApkMetadataFromPurl(p *pkg.Package, arch string) { + if p.Type != pkg.ApkPkg { + return + } + if arch == "" { + return + } + + if p.Metadata == nil { + p.Metadata = pkg.ApkDBEntry{Architecture: arch} + return + } + + if m, ok := p.Metadata.(pkg.ApkDBEntry); ok { + if m.Architecture == "" { + m.Architecture = arch + } + p.Metadata = m } } diff --git a/syft/format/internal/backfill_test.go b/syft/format/internal/backfill_test.go index 79918d6e9..beff255ae 100644 --- a/syft/format/internal/backfill_test.go +++ b/syft/format/internal/backfill_test.go @@ -39,6 +39,9 @@ func Test_Backfill(t *testing.T) { Type: pkg.RpmPkg, Name: "dbus-common", Version: "1.12.8-26.el8", + Metadata: pkg.RpmDBEntry{ + Arch: "noarch", + }, }, }, { @@ -51,6 +54,9 @@ func Test_Backfill(t *testing.T) { Type: pkg.RpmPkg, Name: "dbus-common", Version: "1:1.12.8-26.el8", + Metadata: pkg.RpmDBEntry{ + Arch: "noarch", + }, }, }, { @@ -65,6 +71,22 @@ func Test_Backfill(t *testing.T) { Version: "2.4.37-51", Metadata: pkg.RpmDBEntry{ ModularityLabel: strRef("httpd:2.4"), + Arch: "x86_64", + }, + }, + }, + { + name: "rpm with arch and no rpmmod", + in: pkg.Package{ + PURL: "pkg:rpm/redhat/httpd@2.4.37-51?arch=x86_64&distro=rhel-8.7", + }, + expected: pkg.Package{ + PURL: "pkg:rpm/redhat/httpd@2.4.37-51?arch=x86_64&distro=rhel-8.7", + Type: pkg.RpmPkg, + Name: "httpd", + Version: "2.4.37-51", + Metadata: pkg.RpmDBEntry{ + Arch: "x86_64", }, }, }, @@ -121,6 +143,225 @@ func Test_Backfill(t *testing.T) { Metadata: pkg.JavaArchive{}, }, }, + // deb cases + { + name: "deb nil metadata gets arch set", + in: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Type: pkg.DebPkg, + Name: "curl", + Version: "7.74.0-1.3+deb11u7", + Metadata: pkg.DpkgDBEntry{ + Architecture: "amd64", + }, + }, + }, + { + name: "deb arch:all preserved (nil metadata)", + in: pkg.Package{ + PURL: "pkg:deb/debian/tzdata@2021a-1+deb11u10?arch=all&distro=debian-11", + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/tzdata@2021a-1+deb11u10?arch=all&distro=debian-11", + Type: pkg.DebPkg, + Name: "tzdata", + Version: "2021a-1+deb11u10", + Metadata: pkg.DpkgDBEntry{ + Architecture: "all", + }, + }, + }, + { + name: "deb populated metadata empty arch gets filled", + in: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Metadata: pkg.DpkgDBEntry{ + Package: "curl", + }, + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Type: pkg.DebPkg, + Name: "curl", + Version: "7.74.0-1.3+deb11u7", + Metadata: pkg.DpkgDBEntry{ + Package: "curl", + Architecture: "amd64", + }, + }, + }, + { + name: "deb populated metadata with arch preserved", + in: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Metadata: pkg.DpkgDBEntry{ + Package: "curl", + Architecture: "arm64", + }, + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Type: pkg.DebPkg, + Name: "curl", + Version: "7.74.0-1.3+deb11u7", + Metadata: pkg.DpkgDBEntry{ + Package: "curl", + Architecture: "arm64", + }, + }, + }, + { + name: "deb archive entry empty arch gets filled", + in: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Metadata: pkg.DpkgArchiveEntry{ + Package: "curl", + }, + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Type: pkg.DebPkg, + Name: "curl", + Version: "7.74.0-1.3+deb11u7", + Metadata: pkg.DpkgArchiveEntry{ + Package: "curl", + Architecture: "amd64", + }, + }, + }, + { + name: "deb archive entry with arch preserved", + in: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Metadata: pkg.DpkgArchiveEntry{ + Package: "curl", + Architecture: "arm64", + }, + }, + expected: pkg.Package{ + PURL: "pkg:deb/debian/curl@7.74.0-1.3+deb11u7?arch=amd64&distro=debian-11", + Type: pkg.DebPkg, + Name: "curl", + Version: "7.74.0-1.3+deb11u7", + Metadata: pkg.DpkgArchiveEntry{ + Package: "curl", + Architecture: "arm64", + }, + }, + }, + // alpm cases + { + name: "alpm nil metadata gets arch set", + in: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + }, + expected: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + Type: pkg.AlpmPkg, + Name: "curl", + Version: "7.88.1-1", + Metadata: pkg.AlpmDBEntry{ + Architecture: "x86_64", + }, + }, + }, + { + name: "alpm populated metadata empty arch gets filled", + in: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + Metadata: pkg.AlpmDBEntry{ + Package: "curl", + }, + }, + expected: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + Type: pkg.AlpmPkg, + Name: "curl", + Version: "7.88.1-1", + Metadata: pkg.AlpmDBEntry{ + Package: "curl", + Architecture: "x86_64", + }, + }, + }, + { + name: "alpm populated metadata with arch preserved", + in: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + Metadata: pkg.AlpmDBEntry{ + Package: "curl", + Architecture: "aarch64", + }, + }, + expected: pkg.Package{ + PURL: "pkg:alpm/arch/curl@7.88.1-1?arch=x86_64&distro=arch-rolling", + Type: pkg.AlpmPkg, + Name: "curl", + Version: "7.88.1-1", + Metadata: pkg.AlpmDBEntry{ + Package: "curl", + Architecture: "aarch64", + }, + }, + }, + // apk cases + { + name: "apk nil metadata gets arch set", + in: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + }, + expected: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + Type: pkg.ApkPkg, + Name: "curl", + Version: "7.83.1-r2", + Metadata: pkg.ApkDBEntry{ + Architecture: "aarch64", + }, + }, + }, + { + name: "apk populated metadata empty arch gets filled", + in: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + Metadata: pkg.ApkDBEntry{ + Package: "curl", + }, + }, + expected: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + Type: pkg.ApkPkg, + Name: "curl", + Version: "7.83.1-r2", + Metadata: pkg.ApkDBEntry{ + Package: "curl", + Architecture: "aarch64", + }, + }, + }, + { + name: "apk populated metadata with arch preserved", + in: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + Metadata: pkg.ApkDBEntry{ + Package: "curl", + Architecture: "x86_64", + }, + }, + expected: pkg.Package{ + PURL: "pkg:apk/alpine/curl@7.83.1-r2?arch=aarch64&distro=alpine-3.16.2", + Type: pkg.ApkPkg, + Name: "curl", + Version: "7.83.1-r2", + Metadata: pkg.ApkDBEntry{ + Package: "curl", + Architecture: "x86_64", + }, + }, + }, { name: "target-sw from CPE", in: pkg.Package{ diff --git a/syft/format/internal/cyclonedxutil/helpers/component_test.go b/syft/format/internal/cyclonedxutil/helpers/component_test.go index 341af7778..2b1f44421 100644 --- a/syft/format/internal/cyclonedxutil/helpers/component_test.go +++ b/syft/format/internal/cyclonedxutil/helpers/component_test.go @@ -317,7 +317,7 @@ func Test_decodeComponent(t *testing.T) { }, }, }, - wantMetadata: pkg.RpmDBEntry{}, + wantMetadata: pkg.RpmDBEntry{Arch: "x86_64"}, wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", }, { @@ -341,6 +341,7 @@ func Test_decodeComponent(t *testing.T) { }, wantMetadata: pkg.RpmDBEntry{ Release: "some-release", + Arch: "x86_64", }, wantPURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", }, diff --git a/syft/format/purls/decoder_test.go b/syft/format/purls/decoder_test.go index 8609bcb7a..cec0c3106 100644 --- a/syft/format/purls/decoder_test.go +++ b/syft/format/purls/decoder_test.go @@ -58,10 +58,11 @@ func TestDecoder_Decode(t *testing.T) { purl: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit", expected: []pkg.Package{ { - Name: "sysv-rc", - Version: "2.88dsf-59", - Type: pkg.DebPkg, - PURL: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit", + Name: "sysv-rc", + Version: "2.88dsf-59", + Type: pkg.DebPkg, + PURL: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit", + Metadata: pkg.DpkgDBEntry{Architecture: "all"}, }, }, }, @@ -91,10 +92,11 @@ func TestDecoder_Decode(t *testing.T) { purl: "pkg:rpm/redhat/systemd-x@239-82.el8_10.2?arch=aarch64&distro=rhel-8.10&upstream=systemd-239-82.el8_10.2.src.rpm", expected: []pkg.Package{ { - Name: "systemd-x", - Version: "239-82.el8_10.2", - Type: pkg.RpmPkg, - PURL: "pkg:rpm/redhat/systemd-x@239-82.el8_10.2?arch=aarch64&distro=rhel-8.10&upstream=systemd-239-82.el8_10.2.src.rpm", + Name: "systemd-x", + Version: "239-82.el8_10.2", + Type: pkg.RpmPkg, + PURL: "pkg:rpm/redhat/systemd-x@239-82.el8_10.2?arch=aarch64&distro=rhel-8.10&upstream=systemd-239-82.el8_10.2.src.rpm", + Metadata: pkg.RpmDBEntry{Arch: "aarch64"}, }, }, }, @@ -102,10 +104,11 @@ func TestDecoder_Decode(t *testing.T) { purl: "pkg:rpm/redhat/dbus-common@1.12.8-26.el8?arch=noarch&distro=rhel-8.10&epoch=1&upstream=dbus-1.12.8-26.el8.src.rpm", expected: []pkg.Package{ { - Name: "dbus-common", - Version: "1:1.12.8-26.el8", - Type: pkg.RpmPkg, - PURL: "pkg:rpm/redhat/dbus-common@1.12.8-26.el8?arch=noarch&distro=rhel-8.10&epoch=1&upstream=dbus-1.12.8-26.el8.src.rpm", + Name: "dbus-common", + Version: "1:1.12.8-26.el8", + Type: pkg.RpmPkg, + PURL: "pkg:rpm/redhat/dbus-common@1.12.8-26.el8?arch=noarch&distro=rhel-8.10&epoch=1&upstream=dbus-1.12.8-26.el8.src.rpm", + Metadata: pkg.RpmDBEntry{Arch: "noarch"}, }, }, }, @@ -113,10 +116,11 @@ func TestDecoder_Decode(t *testing.T) { purl: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3", expected: []pkg.Package{ { - Name: "curl", - Version: "7.61.1", - Type: pkg.ApkPkg, - PURL: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3", + Name: "curl", + Version: "7.61.1", + Type: pkg.ApkPkg, + PURL: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3", + Metadata: pkg.ApkDBEntry{Architecture: "aarch64"}, }, }, },