Skip to content

bug: GAD range rows with a malformed bound (versionStartExcluding = "0)") match every version #5909

Description

@Eljees

Summary

Some GAD-sourced rows in cve_range carry a malformed bound — versionStartExcluding = 0) — with every other bound empty. By the scanner's own range logic such a row matches every version of the product, so the CVE is reported against releases it cannot affect.

I hit this on a real Jenkins delivery and it produced two false positives at HIGH/CRITICAL.

The rows

From the local cve.db after a normal --update:

cve_number       vendor    product                version  vStartIncl  vStartExcl  vEndIncl  vEndExcl  data_source
CVE-2023-36478   unknown   jetty-http             *        ''          '0)'        ''        ''        GAD
CVE-2022-31692   unknown   spring-security-core   *        ''          '0)'        ''        ''        GAD

Both bounds that would limit the range are empty, and the one that is set is not a version.

Why such a row matches everything

Replaying CVEScanner.get_cves()'s range block (cve_bin_tool/cve_scanner.py, the passes_start / passes_end pair) against that row:

version 12.1.8 : start_excluding taken: 12.1.8 > Version('0)') -> True
                 no-end-range clause fired -> end always passes
                 passes_start=True passes_end=True  -> REPORTED
version  0.0.1 : ... -> REPORTED
version 99.99  : ... -> REPORTED

Two things combine:

  1. Version("0)") parses successfullyVersion: 0) aka ['0'] — so the junk never surfaces as an error, and the comparison parsed_version > Version("0)") is true for anything above zero.
  2. With versionEndIncluding and versionEndExcluding both unset, the "then there is no end range so it passes" branch makes passes_end unconditionally true. That branch is correct in itself (a CVE with no fix yet legitimately has no upper bound) — it just has nothing to push back on once the upper bound has been lost.

Real-world effect

Scanning an exploded jenkins.war 2.568.2 (251 nested archives):

Reported Actual affected range
jetty-http 12.1.8 — CVE-2023-36478, HIGH 7.5 http2-hpack / http3-qpack 9.3.0–9.4.52, 10.0.0–10.0.15, 11.0.0–11.0.15; the advisory states Jetty 12.x is unaffected, and jetty-http is not among the affected artifacts false positive
spring-security-core 7.1.0 — CVE-2022-31692, CRITICAL 9.8 Spring Security 5.6.0–5.6.8 and 5.7.0–5.7.4 (fixed 5.6.9 / 5.7.5) false positive

What I could not establish

I have not reproduced the GAD input that yields (0). Reading parse_range_string() in cve_bin_tool/data_sources/gad_source.py, two-sided tokens survive the round trip correctly — (0,1.2.3) becomes versionStartExcluding=0 plus versionEndExcluding=1.2.3, because the comma is turned into a space and each half hits its own branch. A single token (0) with no comma is what produces 0), since the if/elif chain takes the "(" in version branch and the ")" branch can then never run. Whether the advisory really contained (0), or something upstream collapsed a wider range into it, I can't say from the stored data alone.

So I'm reporting the observable defect rather than guessing at a fix. Two directions look reasonable and I'm happy to prepare either:

  • reject a bound that is not a parseable version at ingest time, so a malformed range is dropped (or logged) instead of silently becoming match-everything;
  • make Version() refuse trailing junk, which would turn this class of problem into a loud failure everywhere rather than only here — though that is the more invasive of the two and would want its own discussion.

Separately, and explicitly unexplained

On the same report, bcprov-jdk18on 1.84 was flagged with CVE-2025-14813 and bcpg-jdk18on 1.84 with CVE-2026-3505 — in both cases 1.84 is the version that fixes the CVE. The stored rows look correct and should exclude it (versionStartIncluding=1.82.0, versionEndExcluding=1.84.0, and Version("1.84") == Version("1.84.0") is True), and replaying the same range logic on those rows gives passes_end=False. So those two do not come from the mechanism above, and I have not found the path that reported them. Mentioning it only in case it is familiar to someone — I'm not claiming a second bug on this evidence.

Environment: cve-bin-tool run through its SBOM path over a CycloneDX SBOM; DB refreshed the same day (NVD 396 849 entries / 2 592 171 range rows; the two rows above are data_source=GAD).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions