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 var cpes []cpe.CPE
epoch := "" epoch := ""
rpmmod := "" rpmmod := ""
arch := ""
for _, qualifier := range purl.Qualifiers { for _, qualifier := range purl.Qualifiers {
switch qualifier.Key { switch qualifier.Key {
@ -65,6 +66,8 @@ func backfillFromPurl(p *pkg.Package) {
epoch = qualifier.Value epoch = qualifier.Value
case pkg.PURLQualifierRpmModularity: case pkg.PURLQualifierRpmModularity:
rpmmod = qualifier.Value rpmmod = qualifier.Value
case pkg.PURLQualifierArch:
arch = qualifier.Value
} }
} }
@ -84,9 +87,7 @@ func backfillFromPurl(p *pkg.Package) {
setJavaMetadataFromPurl(p, purl) setJavaMetadataFromPurl(p, purl)
} }
if p.Type == pkg.RpmPkg { setDistroMetadata(p, rpmmod, arch)
setRpmMetadataFromPurl(p, rpmmod)
}
for _, c := range cpes { for _, c := range cpes {
if slices.Contains(p.CPEs, c) { 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) { func setJavaMetadataFromPurl(p *pkg.Package, _ packageurl.PackageURL) {
if p.Type != pkg.JavaPkg { if p.Type != pkg.JavaPkg {
return return
@ -107,33 +121,104 @@ 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 { if p.Type != pkg.RpmPkg {
return return
} }
if rpmmod == "" { if rpmmod == "" && arch == "" {
return return
} }
if p.Metadata == nil { if p.Metadata == nil {
p.Metadata = pkg.RpmDBEntry{ p.Metadata = pkg.RpmDBEntry{}
ModularityLabel: &rpmmod,
}
return
} }
switch m := p.Metadata.(type) { switch m := p.Metadata.(type) {
case pkg.RpmDBEntry: case pkg.RpmDBEntry:
if m.ModularityLabel == nil { if m.ModularityLabel == nil && rpmmod != "" {
m.ModularityLabel = &rpmmod m.ModularityLabel = &rpmmod
p.Metadata = m
} }
if m.Arch == "" {
m.Arch = arch
}
p.Metadata = m
case pkg.RpmArchive: case pkg.RpmArchive:
if m.ModularityLabel == nil { if m.ModularityLabel == nil && rpmmod != "" {
m.ModularityLabel = &rpmmod m.ModularityLabel = &rpmmod
}
if m.Arch == "" {
m.Arch = arch
}
p.Metadata = m 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
}
} }
func setVersionFromPurl(p *pkg.Package, purl packageurl.PackageURL, epoch string) { func setVersionFromPurl(p *pkg.Package, purl packageurl.PackageURL, epoch string) {

View File

@ -39,6 +39,9 @@ func Test_Backfill(t *testing.T) {
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
Name: "dbus-common", Name: "dbus-common",
Version: "1.12.8-26.el8", Version: "1.12.8-26.el8",
Metadata: pkg.RpmDBEntry{
Arch: "noarch",
},
}, },
}, },
{ {
@ -51,6 +54,9 @@ func Test_Backfill(t *testing.T) {
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
Name: "dbus-common", Name: "dbus-common",
Version: "1:1.12.8-26.el8", 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", Version: "2.4.37-51",
Metadata: pkg.RpmDBEntry{ Metadata: pkg.RpmDBEntry{
ModularityLabel: strRef("httpd:2.4"), 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{}, 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", name: "target-sw from CPE",
in: pkg.Package{ 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", 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{ wantMetadata: pkg.RpmDBEntry{
Release: "some-release", 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", 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

@ -62,6 +62,7 @@ func TestDecoder_Decode(t *testing.T) {
Version: "2.88dsf-59", Version: "2.88dsf-59",
Type: pkg.DebPkg, Type: pkg.DebPkg,
PURL: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit", PURL: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit",
Metadata: pkg.DpkgDBEntry{Architecture: "all"},
}, },
}, },
}, },
@ -95,6 +96,7 @@ func TestDecoder_Decode(t *testing.T) {
Version: "239-82.el8_10.2", Version: "239-82.el8_10.2",
Type: pkg.RpmPkg, 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", 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"},
}, },
}, },
}, },
@ -106,6 +108,7 @@ func TestDecoder_Decode(t *testing.T) {
Version: "1:1.12.8-26.el8", Version: "1:1.12.8-26.el8",
Type: pkg.RpmPkg, 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", 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"},
}, },
}, },
}, },
@ -117,6 +120,7 @@ func TestDecoder_Decode(t *testing.T) {
Version: "7.61.1", Version: "7.61.1",
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
PURL: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3", PURL: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3",
Metadata: pkg.ApkDBEntry{Architecture: "aarch64"},
}, },
}, },
}, },