Skip to content

Commit

Permalink
refactor: de-duplicate advisories first
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitriyLewen committed Dec 9, 2024
1 parent 29ab009 commit b8b6c16
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 65 deletions.
72 changes: 21 additions & 51 deletions pkg/detector/ospkg/redhat/redhat.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"time"

version "github.com/knqyf263/go-rpm-version"
"github.com/samber/lo"
"golang.org/x/xerrors"

dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings"
redhat "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version"
Expand Down Expand Up @@ -116,29 +114,35 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln
return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err)
}

// Sort advosiries by FixedVersion to avoid incorrectly overwriting vulnerabilities
sort.Slice(advisories, func(i, j int) bool {
return version.NewVersion(advisories[i].FixedVersion).LessThan(version.NewVersion(advisories[j].FixedVersion))
})

installed := utils.FormatVersion(pkg)
installedVersion := version.NewVersion(installed)

uniqVulns := make(map[string]types.DetectedVulnerability)
// Choose the latest fixed version for each CVE-ID (empty for unpatched vulns).
// Take the single RHSA-ID with the latest fixed version (for patched vulns).
uniqAdvisories := make(map[string]dbTypes.Advisory)
for _, adv := range advisories {
// if Arches for advisory is empty or pkg.Arch is "noarch", then any Arches are affected
// If Arches for advisory are empty or pkg.Arch is "noarch", then any Arches are affected
if len(adv.Arches) != 0 && pkg.Arch != "noarch" {
if !slices.Contains(adv.Arches, pkg.Arch) {
continue
}
}

vulnID := adv.VulnerabilityID
if a, ok := uniqAdvisories[adv.VulnerabilityID]; ok {
if version.NewVersion(a.FixedVersion).LessThan(version.NewVersion(adv.FixedVersion)) {
uniqAdvisories[adv.VulnerabilityID] = adv
}
} else {
uniqAdvisories[adv.VulnerabilityID] = adv
}
}

var vulns []types.DetectedVulnerability
for _, adv := range uniqAdvisories {
vuln := types.DetectedVulnerability{
VulnerabilityID: vulnID,
VulnerabilityID: adv.VulnerabilityID,
VendorIDs: adv.VendorIDs, // Will be empty for unpatched vulnerabilities
PkgID: pkg.ID,
PkgName: pkg.Name,
InstalledVersion: utils.FormatVersion(pkg),
FixedVersion: version.NewVersion(adv.FixedVersion).String(), // Will be empty for unpatched vulnerabilities
PkgIdentifier: pkg.Identifier,
Status: adv.Status,
Layer: pkg.Layer,
Expand All @@ -149,49 +153,15 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln
Custom: adv.Custom,
}

// unpatched vulnerabilities
if adv.FixedVersion == "" {
// Advisories are sorted and unpatched advisories always go before patched ones.
// So we just save unpatched advisories and will overwrite
uniqVulns[vulnID] = vuln
continue
}

// patched vulnerabilities
fixedVersion := version.NewVersion(adv.FixedVersion)
if !installedVersion.LessThan(fixedVersion) {
// The advisories are sorted by fixedVersion, so the following cases are possible:
// 1. uniqVulns doesn't contain this vulnID -> just move on to checking the next advisory.
// 2. uniqVulns contains this vuln without fixedVersion.
// 3. uniqVulns contains this vuln with fixedVersion.
// For cases 2 and 3, we should remove this vulnerability from uniqVulns, since in both cases this package is not vulnerable.
delete(uniqVulns, vulnID)
continue
}

vuln.VendorIDs = adv.VendorIDs
vuln.FixedVersion = fixedVersion.String()

if v, ok := uniqVulns[vulnID]; ok {
// In case two advisories resolve the same CVE-ID.
// e.g. The first fix might be incomplete.
v.VendorIDs = ustrings.Unique(append(v.VendorIDs, vuln.VendorIDs...))

// The newer fixed version should be taken.
if version.NewVersion(v.FixedVersion).LessThan(fixedVersion) {
v.FixedVersion = vuln.FixedVersion
}
uniqVulns[vulnID] = v
} else {
uniqVulns[vulnID] = vuln
// Keep unpatched and affected vulnerabilities
if adv.FixedVersion == "" || version.NewVersion(vuln.InstalledVersion).LessThan(version.NewVersion(adv.FixedVersion)) {
vulns = append(vulns, vuln)
}
}

vulns := lo.Values(uniqVulns)
sort.Slice(vulns, func(i, j int) bool {
return vulns[i].VulnerabilityID < vulns[j].VulnerabilityID
})

return vulns, nil
}

Expand Down
38 changes: 24 additions & 14 deletions pkg/detector/ospkg/redhat/redhat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ func TestScanner_Detect(t *testing.T) {
},
},
{
VulnerabilityID: "CVE-2019-12735",
VendorIDs: []string{"RHSA-2019:1619"},
VulnerabilityID: "CVE-2019-12735",
VendorIDs: []string{
"RHSA-2019:1619",
},
PkgName: "vim-minimal",
InstalledVersion: "2:7.4.160-5.el7",
FixedVersion: "2:7.4.160-6.el7_6",
Expand Down Expand Up @@ -124,8 +126,10 @@ func TestScanner_Detect(t *testing.T) {
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2019-17007",
VendorIDs: []string{"RHSA-2021:0876"},
VulnerabilityID: "CVE-2019-17007",
VendorIDs: []string{
"RHSA-2021:0876",
},
PkgName: "nss",
InstalledVersion: "3.36.0-7.1.el7_6",
FixedVersion: "3.36.0-9.el7_6",
Expand All @@ -141,7 +145,6 @@ func TestScanner_Detect(t *testing.T) {
VulnerabilityID: "CVE-2020-12403",
VendorIDs: []string{
"RHSA-2021:0538",
"RHSA-2021:0876",
},
PkgName: "nss",
InstalledVersion: "3.36.0-7.1.el7_6",
Expand Down Expand Up @@ -188,7 +191,6 @@ func TestScanner_Detect(t *testing.T) {
{
VulnerabilityID: "CVE-2024-45490",
VendorIDs: []string{
"RHSA-2024:6989-2",
"RHSA-2024:6989-3",
},
PkgName: "expat",
Expand Down Expand Up @@ -234,8 +236,10 @@ func TestScanner_Detect(t *testing.T) {
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2016-5195",
VendorIDs: []string{"RHSA-2017:0372"},
VulnerabilityID: "CVE-2016-5195",
VendorIDs: []string{
"RHSA-2017:0372",
},
PkgName: "kernel-headers",
InstalledVersion: "3.10.0-1127.19-1.el7",
FixedVersion: "4.5.0-15.2.1.el7",
Expand Down Expand Up @@ -279,8 +283,10 @@ func TestScanner_Detect(t *testing.T) {
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2016-5195",
VendorIDs: []string{"RHSA-2016:2098"},
VulnerabilityID: "CVE-2016-5195",
VendorIDs: []string{
"RHSA-2016:2098",
},
PkgName: "kernel-headers",
InstalledVersion: "3.10.0-326.36-3.el7",
FixedVersion: "3.10.0-327.36.3.el7",
Expand Down Expand Up @@ -314,8 +320,10 @@ func TestScanner_Detect(t *testing.T) {
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2019-12735",
VendorIDs: []string{"RHSA-2019:1619"},
VulnerabilityID: "CVE-2019-12735",
VendorIDs: []string{
"RHSA-2019:1619",
},
PkgName: "vim-minimal",
InstalledVersion: "2:7.4.160-5.el8",
FixedVersion: "2:7.4.160-7.el8_7",
Expand Down Expand Up @@ -356,8 +364,10 @@ func TestScanner_Detect(t *testing.T) {
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2019-11043",
VendorIDs: []string{"RHSA-2020:0322"},
VulnerabilityID: "CVE-2019-11043",
VendorIDs: []string{
"RHSA-2020:0322",
},
PkgName: "php",
InstalledVersion: "7.2.10-1.module_el8.2.0+313+b04d0a66",
FixedVersion: "7.2.11-1.1.module+el8.0.0+4664+17bd8d65",
Expand Down

0 comments on commit b8b6c16

Please sign in to comment.