Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Vulnerability": {
"CVSS": [],
"Description": "Root.io vulnerability record with no fix available",
"FixedIn": [
{
"Name": "unpatched-package",
"NamespaceName": "rootio:distro:alpine:3.17",
"VendorAdvisory": {
"AdvisorySummary": [],
"NoAdvisory": true
},
"Version": "",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. This looks like a vulnerability with no fix information in a test case, but at anchore/vunnel#863 (comment) I thought you said that the rootio provider never discloses unfixed vulnerabilities.

"VersionFormat": "apk"
}
],
"Link": "",
"Metadata": {
"CVE": [
{
"Name": "CVE-2023-9999",
"Link": ""
}
]
},
"Name": "CVE-2023-9999",
"NamespaceName": "rootio:distro:alpine:3.17",
"Severity": "Medium"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Vulnerability": {
"CVSS": [],
"Description": "Root.io vulnerability record with Alpine -rXX007X pattern",
"FixedIn": [
{
"Name": "libssl3",
"NamespaceName": "rootio:distro:alpine:3.21",
"VendorAdvisory": {
"AdvisorySummary": [],
"NoAdvisory": false
},
"Version": "3.0.8-r00071",
"VersionFormat": "apk"
}
],
"Link": "",
"Metadata": {
"CVE": [
{
"Name": "CVE-2024-1234",
"Link": ""
}
]
},
"Name": "CVE-2024-1234",
"NamespaceName": "rootio:distro:alpine:3.21",
"Severity": "High"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Vulnerability": {
"CVSS": [],
"Description": "Root.io vulnerability record indicating test-package is fixed",
"FixedIn": [
{
"Name": "test-package",
"NamespaceName": "rootio:distro:alpine:3.17",
"VendorAdvisory": {
"AdvisorySummary": [],
"NoAdvisory": false
},
"Version": "3.0.8-r4",
"VersionFormat": "apk"
}
],
"Link": "",
"Metadata": {
"CVE": [
{
"Name": "CVE-2023-1234",
"Link": ""
}
]
},
"Name": "CVE-2023-1234",
"NamespaceName": "rootio:distro:alpine:3.17",
"Severity": "Medium"
}
}
111 changes: 111 additions & 0 deletions pkg/process/v6/transformers/os/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
"github.com/anchore/syft/syft/pkg"
)

const (
rootioNamespacePrefix = "rootio"
)

// advisoryKey is an internal struct used for sorting and deduplicating advisories
// that have both a link and ID from the vunnel results data
type advisoryKey struct {
Expand All @@ -30,6 +34,9 @@ type advisoryKey struct {
}

func Transform(vulnerability unmarshal.OSVulnerability, state provider.State) ([]data.Entry, error) {
if isRootIoNamespace(vulnerability.Vulnerability.NamespaceName) {
return processRootIoVulnerability(vulnerability, state)
}
in := []any{
grypeDB.VulnerabilityHandle{
Name: vulnerability.Vulnerability.Name,
Expand All @@ -56,6 +63,96 @@ func Transform(vulnerability unmarshal.OSVulnerability, state provider.State) ([
return transformers.NewEntries(in...), nil
}

func isRootIoNamespace(namespace string) bool {
return strings.HasPrefix(namespace, rootioNamespacePrefix+":")
}

func processRootIoVulnerability(vuln unmarshal.OSVulnerability, state provider.State) ([]data.Entry, error) {
var entries []any

entries = append(entries, grypeDB.VulnerabilityHandle{
Name: vuln.Vulnerability.Name,
ProviderID: state.Provider,
Provider: internal.ProviderModel(state),
Status: grypeDB.VulnerabilityActive,
ModifiedDate: internal.ParseTime(vuln.Vulnerability.Metadata.Updated),
PublishedDate: internal.ParseTime(vuln.Vulnerability.Metadata.Issued),
BlobValue: &grypeDB.VulnerabilityBlob{
ID: vuln.Vulnerability.Name,
Assigners: nil,
Description: strings.TrimSpace(vuln.Vulnerability.Description),
References: getReferences(vuln),
Aliases: getAliases(vuln),
Severities: getSeverities(vuln),
},
})

for _, u := range getRootIoUnaffectedPackages(vuln) {
entries = append(entries, u)
}

return transformers.NewEntries(entries...), nil
}

func getRootIoUnaffectedPackages(vuln unmarshal.OSVulnerability) []grypeDB.UnaffectedPackageHandle {
var uphs []grypeDB.UnaffectedPackageHandle
groups := groupFixedIns(vuln)

for group, fixedIns := range groups {
for _, fixedIn := range fixedIns {
if fixedIn.Version != "" {
uph := grypeDB.UnaffectedPackageHandle{
Package: getPackage(group),
OperatingSystem: getOperatingSystem(group.osName, group.id, group.osVersion, group.osChannel),
BlobValue: getRootIoUnaffectedBlob(vuln, fixedIn, group),
}
uphs = append(uphs, uph)
break
}
}
}

sort.Sort(internal.ByUnaffectedPackage(uphs))
return uphs
}

func getRootIoUnaffectedBlob(vuln unmarshal.OSVulnerability, fixedIn unmarshal.OSFixedIn, group groupIndex) *grypeDB.PackageBlob {
cves := getAliases(vuln)

constraint := determineRootIoConstraint(group.osName, fixedIn.Version)
ranges := []grypeDB.Range{
{
Version: grypeDB.Version{
Type: fixedIn.VersionFormat,
Constraint: constraint,
},
},
}

return &grypeDB.PackageBlob{
CVEs: cves,
Ranges: ranges,
}
}

func determineRootIoConstraint(osName string, version string) string {
switch osName {
case "debian", "ubuntu":
// Debian/Ubuntu packages use .root.io suffix
// Example: 1.5.2-6+deb12u1.root.io.4
return "version_contains .root.io"
case "alpine", "chainguard", "wolfi":
// Alpine packages may use either:
// 1. .root.io suffix (e.g., 3.0.8-r3.root.io.1)
// 2. -rXX007X pattern (e.g., 3.0.8-r00071, 3.0.8-r10074)
// We check for .root.io first as it's more common across distros
// The -rXX007X pattern check is handled in Grype's IsRootIoPackage()
return "version_contains .root.io"
default:
return "version_contains .root.io"
}
}

func getAffectedPackages(vuln unmarshal.OSVulnerability) []grypeDB.AffectedPackageHandle {
var afs []grypeDB.AffectedPackageHandle
groups := groupFixedIns(vuln)
Expand Down Expand Up @@ -270,8 +367,22 @@ type osInfo struct {

func getOSInfo(group string) osInfo {
// derived from enterprise feed groups, expected to be of the form {distro release ID}:{version}
// or for Root.io: rootio:distro:{distro}:{version}
feedGroupComponents := strings.Split(group, ":")

// Handle Root.io namespace format
if len(feedGroupComponents) >= 4 && feedGroupComponents[0] == rootioNamespacePrefix && feedGroupComponents[1] == "distro" {
// Root.io format: rootio:distro:alpine:3.17
id := feedGroupComponents[2]
version := feedGroupComponents[3]
return osInfo{
name: normalizeOsName(id),
id: rootioNamespacePrefix + "-" + id, // Prefix with rootio to distinguish
version: version,
channel: "",
}
}

id := feedGroupComponents[0]
version := feedGroupComponents[1]
channel := ""
Expand Down
Loading