8000 Grype false negatives in versions v0.88.0 and later leading to missed critical vulnerabilities · Issue #2628 · anchore/grype · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Grype false negatives in versions v0.88.0 and later leading to missed critical vulnerabilities #2628

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

Closed
sschakraborty opened this issue Apr 30, 2025 · 2 comments · Fixed by #2645
Assignees
Labels
bug Something isn't working

Comments

@sschakraborty
Copy link

What happened

I installed two different versions of Grype - v0.87.0 and v0.91.2. I generated a minimalist test SBOM containing vulnerable artifacts. Grype 0.87.0 reports 3 critical and 2 high vulnerabilities whereas any later version (including the latest) does not report anything. This is true for all Grype versions using v6 DB schema.

What you expected to happen

All versions of Grype should produce consistent results and report all vulnerabilities.

How to reproduce it (as minimally and precisely as possible)

I used the following sbom.json file for testing both versions:

{
  "artifacts": [
    {
      "id": "pkg:maven/log4j/log4j@1.2.17",
      "name": "log4j",
      "version": "1.2.17",
      "language": "java",
      "purl": "pkg:maven/log4j/log4j@1.2.17",
      "locations": [
        {
          "path": "META-INF/maven/commons-logging/commons-logging/pom.xml"
        }
      ]
    }
  ],
  "source": {
    "type": "directory"
  },
  "schema": {
    "version": "16.0.9",
    "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.0.9.json"
  }
}

Using Grype v0.87.0:

$> curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin 'v0.87.0'
[info] checking github for release tag='v0.87.0' 
[info] fetching release script for tag='v0.87.0' 
[info] checking github for release tag='v0.87.0' 
[info] using release tag='v0.87.0' version='0.87.0' os='linux' arch='amd64' 
[info] installed /usr/local/bin/grype 
$> grype db update
 ✔ Vulnerability DB                [updated]  
Vulnerability database updated to latest version!

$> grype sbom:sbom.json --add-cpes-if-none
 ✔ Scanned for vulnerabilities     [0 vulnerability matches]  
   ├── by severity: 3 critical, 2 high, 0 medium, 0 low, 0 negligible
   └── by status:   0 fixed, 5 not-fixed, 0 ignored 
NAME   INSTALLED  FIXED-IN  TYPE  VULNERABILITY        SEVERITY 
log4j  1.2.17                     GHSA-f7vh-qwp3-x37m  Critical  
log4j  1.2.17                     GHSA-65fg-84f6-3jq3  Critical  
log4j  1.2.17                     GHSA-2qrg-x229-3v8q  Critical  
log4j  1.2.17                     GHSA-w9p3-5cr8-m3jj  High      
log4j  1.2.17                     GHSA-fp5r-v3w9-4333  High
A newer version of grype is available for download: 0.91.2 (installed version is 0.87.0)

Using Grype v0.91.2:

$> curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin 'v0.91.2'
[info] checking github for release tag='v0.91.2' 
[info] fetching release script for tag='v0.91.2' 
[info] checking github for release tag='v0.91.2' 
[info] using release tag='v0.91.2' version='0.91.2' os='linux' arch='amd64' 
[info] installed /usr/local/bin/grype
$> grype db update
 ✔ Vulnerability DB                [updated]  
Vulnerability database updated to latest version!

$> grype sbom:sbom.json --add-cpes-if-none
 ✔ Scanned for vulnerabilities     [0 vulnerability matches]  
   ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible
   └── by status:   0 fixed, 0 not-fixed, 0 ignored 
No vulnerabilities found

Anything else we need to know?:
This has been happening since the release of v0.88.0, which made a shift to v6 of the database schema.

Environment:

  • Versions tested: v0.87.0 and v0.91.2
  • OS (e.g: cat /etc/os-release or similar): Alpine Linux v3.21.3
@sschakraborty sschakraborty added the bug Something isn't working label Apr 30, 2025
@westonsteimel
Copy link
Contributor

Thanks for the report. I think this is similar to the issue in #2580 where grype seems to not be preoperly considering namespace from the package url when forming package searches

@wagoodman wagoodman moved this to Ready in OSS Apr 30, 2025
@kzantow kzantow self-assigned this Apr 30, 2025
@kzantow kzantow moved this from Ready to In Progress in OSS Apr 30, 2025
@spiffcs
Copy link
Contributor
spiffcs commented Apr 30, 2025

Just wanted to add a detailed comment here around the WHY this is happening while we work on a fix.

v0.87.0 would eventually get to this part of when we still had the vulnerability_provider file and run SearchForVulnerabilities using the github:language:java namespace with the package name log4j:log4j

for _, n := range namespaces {
for _, packageName := range n.Resolver().Resolve(p) {
nsStr := n.String()
allPkgVulns, err := pr.reader.SearchForVulnerabilities(nsStr, packageName)
if err != nil {
return nil, fmt.Errorf("provider failed to fetch namespace=%q pkg=%q: %w", nsStr, packageName, err)
}

This would return the expected results that were first encountered. Theses results are correct.

Currently on main in the latest version of grype the flow is a little different since we moved to v6 of the database:
We still use the defaultMatcher but this takes us to MatchPackageByEcosystemAndCPE

func MatchPackageByEcosystemAndCPEs(store vulnerability.Provider, p pkg.Package, matcher match.MatcherType, includeCPEs bool) ([]match.Match, []match.IgnoredMatch, error) {
var matches []match.Match
var ignored []match.IgnoredMatch
for _, name := range store.PackageSearchNames(p) {
nameMatches, nameIgnores, err := MatchPackageByEcosystemPackageNameAndCPEs(store, p, name, matcher, includeCPEs)
if err != nil {
return nil, nil, err
}
matches = append(matches, nameMatches...)
ignored = append(ignored, nameIgnores...)
}
return matches, ignored, nil
}

By that point in the code we still have all the package information and could potentially make the correct query to the DB.

We eventually get to the v6 vulnerability provider where we call FindVulnerabilities with this information as part of the criteria

func (vp vulnerabilityProvider) FindVulnerabilities(criteria ...vulnerability.Criteria) ([]vulnerability.Vulnerability, error) {
if err := search.ValidateCriteria(criteria); err != nil {
return nil, err
}
var err error
var out []vulnerability.Vulnerability
for _, criteriaSet := range search.CriteriaIterator(criteria) {
var vulnSpecs VulnerabilitySpecifiers
var osSpecs OSSpecifiers
var pkgSpec *PackageSpecifier
var cpeSpec *cpe.Attributes
var pkgType syftPkg.Type
for i := 0; i < len(criteriaSet); i++ {
applied := false
switch c := criteriaSet[i].(type) {
case *search.PackageNameCriteria:
if pkgSpec == nil {
pkgSpec = < 8000 span class="pl-c1">&PackageSpecifier{}
}
pkgSpec.Name = c.PackageName
applied = true
case *search.EcosystemCriteria:
if pkgSpec == nil {
pkgSpec = &PackageSpecifier{}
}
// the v6 store normalizes ecosystems around the syft package type, so that field is preferred
switch {
case c.PackageType != "" && c.PackageType != syftPkg.UnknownPkg:
// prefer to match by a non-blank, known package type
pkgType = c.PackageType
pkgSpec.Ecosystem = string(c.PackageType)
case c.Language != "":
// if there's no known package type, but there is a non-blank language
// try that.
pkgSpec.Ecosystem = string(c.Language)
case c.PackageType == syftPkg.UnknownPkg:
// if language is blank, and package type is explicitly "UnknownPkg" and not
// just blank, use that.
pkgType = c.PackageType
pkgSpec.Ecosystem = string(c.PackageType)
}
applied = true
case *search.IDCriteria:
vulnSpecs = append(vulnSpecs, VulnerabilitySpecifier{
Name: c.ID,
})
applied = true
case *search.CPECriteria:
if cpeSpec == nil {
cpeSpec = &cpe.Attributes{}
}
*cpeSpec = c.CPE.Attributes
if cpeSpec.Product == cpe.Any {
return nil, fmt.Errorf("must specify product to search by CPE; got: %s", c.CPE.Attributes.BindToFmtString())
}
if pkgSpec == nil {
pkgSpec = &PackageSpecifier{}
}
pkgSpec.CPE = &c.CPE.Attributes
applied = true
case *search.DistroCriteria:
for _, d := range c.Distros {
osSpecs = append(osSpecs, &OSSpecifier{
Name: d.Name(),
MajorVersion: d.MajorVersion(),
MinorVersion: d.MinorVersion(),
RemainingVersion: d.RemainingVersion(),
LabelVersion: d.Codename,
})
}
applied = true
}
// remove fully applied criteria from later checks
if applied {
criteriaSet = append(criteriaSet[0:i], criteriaSet[i+1:]...)
i--
}
}
if len(osSpecs) == 0 {
// we don't want to search across all distros, instead if the user did not specify a distro we should assume that
// they want to search across affected packages not associated with any distro.
osSpecs = append(osSpecs, NoOSSpecified)
}
// if there is an ecosystem provided and a name, we need to make certain that we're using the name normalization
// rules specific to the ecosystem before searching.
if pkgType != "" && pkgSpec.Name != "" {
pkgSpec.Name = name.Normalize(pkgSpec.Name, pkgType)
}
versionMatcher, remainingCriteria := splitConstraintMatcher(criteriaSet...)
var affectedPackages []AffectedPackageHandle
var affectedCPEs []AffectedCPEHandle
if pkgSpec != nil || len(vulnSpecs) > 0 {
affectedPackages, err = vp.reader.GetAffectedPackages(pkgSpec, &GetAffectedPackageOptions{
OSs: osSpecs,
Vulnerabilities: vulnSpecs,
PreloadBlob: true,
})
if err != nil {
if errors.Is(err, ErrOSNotPresent) {
log.WithFields("os", osSpecs).Debug("no OS found in the DB for the given criteria")
return nil, nil
}
return nil, err
}
affectedPackages = filterAffectedPackageVersions(versionMatcher, affectedPackages)
// after filtering, read vulnerability data
if err = fillAffectedPackageHandles(vp.reader, ptrs(affectedPackages)); err != nil {
return nil, err
}
}
if cpeSpec != nil {
affectedCPEs, err = vp.reader.GetAffectedCPEs(cpeSpec, &GetAffectedCPEOptions{
Vulnerabilities: vulnSpecs,
PreloadBlob: true,
})
if err != nil {
return nil, err
}
affectedCPEs = filterAffectedCPEVersions(versionMatcher, affectedCPEs, cpeSpec)
// after filtering, read vulnerability data
if err = fillAffectedCPEHandles(vp.reader, ptrs(affectedCPEs)); err != nil {
return nil, err
}
}
// fill complete vulnerabilities for this set -- these should have already had all properties lazy loaded
vulns, err := vp.toVulnerabilities(affectedPackages, affectedCPEs)
if err != nil {
return nil, err
}
// filter vulnerabilities by any remaining criteria such as ByQualifiedPackages
vulns, err = vp.filterVulnerabilities(vulns, remainingCriteria...)
if err != nil {
return nil, err
}
out = append(out, vulns...)
}
return out, nil
}

Ecosystem: Java
PackageName: log4j

@westonsteimel noticed here how the package name is log4j and not log4j:log4j like the name/groupID combination in the v5 query.

@wagoodman summed it up nicely here:
This is really an issue in the syft_sbom_provider when we are crafting packages to search for, not really a matcher issue.

  1. passing a purl works since we derive the pkg type from the purl type
  2. passing an sbom with a missing pkg type does not work since we do not attempt to derive pkg type from purl if the pkg type is missing
  3. passing metadata vs no metadata will affect what values will be used for matching since we won't necessarily look for artifactid and groupid from the same places in all cases (which is a bug) <-- This is the bug.

@wagoodman also suggested a few places where we could try and fix this:

  • In syft when decoding SBOM formats and converting to syft packages we should try to figure the package type from the purl if possible (when the type is missing)
  • In grype when crafting a grype package from a syft package, attempt to hydrate missing metadata from whats provided in the purl... e.g. artifact id and group id (maybe also do this in syft?)

@kzantow created an issue where we can revist some of the above behavior to make sure we address this inconsistency in result searches with v6:
#2631

Looks like this has been moved to in progress so expect some kind of fix surrounding the above points to be in the works.

@kzantow kzantow moved this from In Progress to In Review in OSS May 12, 2025
@github-project-automation github-project-automation bot moved this from In Review to Done in OSS May 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants
0