Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(redhat): correct rewriting of recommendations for the same vulnerability #8063

Merged
merged 3 commits into from
Dec 10, 2024

Conversation

DmitriyLewen
Copy link
Contributor

@DmitriyLewen DmitriyLewen commented Dec 6, 2024

Description

Rewriting advisories for the same vulnerability is currently tied to the advisory order.
See #8061 and new tests for more details

This PR sets the order for advisories and fixes overwriting of advisories for the same vulnerability.

Example:
before:

➜  trivy -q image registry.access.redhat.com/ubi8/ubi --cache-backend memory | grep expat
│ expat                       │ CVE-2022-23990   │ MEDIUM   │                     │ 2.2.5-16.el8_10       │                 │ expat: integer overflow in the doProlog function             
│                             │ CVE-2024-45491   │          │                     │                       │                 │ libexpat: Integer Overflow or Wraparound                     │
│                             │ CVE-2024-45492   │          │                     │                       │                 │ libexpat: integer overflow                                   │
│                             │ CVE-2024-50602   │          │ affected            │                       │                 │ libexpat: expat: DoS via XML_ResumeParser                    │

after:

➜  ./trivy -q image registry.access.redhat.com/ubi8/ubi --cache-backend memory | grep expat
│ expat                       │ CVE-2022-23990   │ MEDIUM   │                     │ 2.2.5-16.el8_10       │                 │ expat: integer overflow in the doProlog function             

Related issues

Checklist

  • I've read the guidelines for contributing to this repository.
  • I've followed the conventions in the PR title.
  • I've added tests that prove my fix is effective or that my feature works.
  • I've updated the documentation with the relevant information (if needed).
  • I've added usage information (if the PR introduces new options)
  • I've included a "before" and "after" example to the description (if the PR is a user interface change).

@DmitriyLewen DmitriyLewen self-assigned this Dec 6, 2024
@DmitriyLewen DmitriyLewen marked this pull request as ready for review December 6, 2024 09:48
@@ -116,6 +116,11 @@ 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
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think about de-duplicating advisories first? This change will prevent the display of multiple RHSA-IDs that fix the same CVE-ID, but I think that it would be better to display only the most recent RHSA-ID since multiple RHSA-IDs confuses users in the first place.

diff --git a/pkg/detector/ospkg/redhat/redhat.go b/pkg/detector/ospkg/redhat/redhat.go
index 232c14ba9..a8d061395 100644
--- a/pkg/detector/ospkg/redhat/redhat.go
+++ b/pkg/detector/ospkg/redhat/redhat.go
@@ -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"
@@ -116,16 +114,24 @@ 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))
-	})
+	// Chose the latest fixed version for each CVE-ID
+	// Take the single RHSA-ID with the latest fixed version
+	uniqAdvisories := make(map[string]dbTypes.Advisory)
+	for _, adv := range advisories {
+		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
+		}
+	}
 
 	installed := utils.FormatVersion(pkg)
 	installedVersion := version.NewVersion(installed)
 
-	uniqVulns := make(map[string]types.DetectedVulnerability)
-	for _, adv := range advisories {
+	var vulns []types.DetectedVulnerability
+	for _, adv := range uniqAdvisories {
 		// if Arches for advisory is 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) {
@@ -136,6 +142,8 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln
 		vulnID := adv.VulnerabilityID
 		vuln := types.DetectedVulnerability{
 			VulnerabilityID:  vulnID,
+			VendorIDs:        adv.VendorIDs,    // Will be empty for unpatched vulnerabilities
+			FixedVersion:     adv.FixedVersion, // Will be empty for unpatched vulnerabilities
 			PkgID:            pkg.ID,
 			PkgName:          pkg.Name,
 			InstalledVersion: utils.FormatVersion(pkg),
@@ -151,43 +159,18 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln
 
 		// 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
+			vulns = append(vulns, 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
+		if installedVersion.LessThan(fixedVersion) {
+			vulns = append(vulns, vuln)
 		}
 
-		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
-		}
 	}
 
-	vulns := lo.Values(uniqVulns)
 	sort.Slice(vulns, func(i, j int) bool {
 		return vulns[i].VulnerabilityID < vulns[j].VulnerabilityID
 	})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but I think that it would be better to display only the most recent RHSA-ID since multiple RHSA-IDs confuses users in the first place.

I tried to maintain this logic on the contrary 😄

I refactor and updated (arches are checked before saving unique advisories) your logic - b8b6c16
Take a look

Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think? Should we show all relevant RHSA-IDs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to keep this logic, because I thought users were asking about it.

But my opinion is that it is better to show only the current (with the latest fixed version) RHSA-ID. Multiple RHSA-IDs add noise and confuse users.

Copy link
Collaborator

@knqyf263 knqyf263 Dec 10, 2024

Choose a reason for hiding this comment

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

I agree. If Trivy shows the non-latest RHSA-ID, users may consume the advisory and get confused as the vulnerability will not be fixed.

@knqyf263 knqyf263 added this pull request to the merge queue Dec 10, 2024
Merged via the queue into aquasecurity:main with commit 4202c4b Dec 10, 2024
12 checks passed
@DmitriyLewen DmitriyLewen deleted the fix/redhat-uniq-vulns branch December 10, 2024 09:10
@DmitriyLewen
Copy link
Contributor Author

@aqua-bot backport release/v0.58

@aqua-bot
Copy link
Contributor

Backport PR created: #8135

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants