fix(purl-backfill): respect arch qualifier (#4987)

* fix(purl-backfill): respect arch qualifier

Previously, when constructing rpm, alpm, and apk metadata struct from a
PURL, Syft would ignore the arch qualifier. Start respecting that
qualifier.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* chore: fix static analysis

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* Clean up control flow in PURL backfill code

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

---------

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
This commit is contained in:
Will Murphy 2026-06-23 15:23:46 -04:00 committed by GitHub
parent fea4a50124
commit fe42bcec38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 361 additions and 30 deletions

View File

@ -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
}
}

View File

@ -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{

View File

@ -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",
},

View File

@ -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"},
},
},
},