diff --git a/src/glvd/cli/combine_deb.py b/src/glvd/cli/combine_deb.py index da91907..ae35d05 100644 --- a/src/glvd/cli/combine_deb.py +++ b/src/glvd/cli/combine_deb.py @@ -34,6 +34,7 @@ class CombineDeb: cve.cve_id , src.deb_source , src.deb_version + , cve.deb_version_fixed , COALESCE(src.deb_version < cve.deb_version_fixed, TRUE) AS debsec_vulnerable FROM debsrc as src @@ -119,29 +120,33 @@ async def combine( 'dist_id': dist.id, 'dists_fallback_id': [i.id for i in dists_fallback], }): - cve_id, deb_source, deb_version, debsec_vulnerable = r + cve_id, deb_source, deb_version, deb_version_fixed, debsec_vulnerable = r cpe = Cpe( part=Cpe.PART.OS, vendor=dist.cpe_vendor, product=dist.cpe_product, version=dist.cpe_version, - other=CpeOtherDebian( - deb_source=deb_source, - deb_version=deb_version, - ), + other=CpeOtherDebian(deb_source=deb_source), ) cpe_match = { 'criteria': str(cpe), + 'deb': { + 'versionLatest': deb_version, + }, 'vulnerable': debsec_vulnerable, } + if deb_version_fixed: + cpe_match['deb']['versionEndExcluding'] = deb_version_fixed + new_entries[(cve_id, deb_source)] = DebCve( dist=dist, cve_id=cve_id, deb_source=deb_source, deb_version=deb_version, + deb_version_fixed=deb_version_fixed, debsec_vulnerable=debsec_vulnerable, data_cpe_match=cpe_match, ) diff --git a/src/glvd/database/__init__.py b/src/glvd/database/__init__.py index 6514cfa..12cd662 100644 --- a/src/glvd/database/__init__.py +++ b/src/glvd/database/__init__.py @@ -97,6 +97,7 @@ class DebCve(Base): last_mod: Mapped[datetime] = mapped_column(init=False, server_default=func.now(), onupdate=func.now()) deb_source: Mapped[str] = mapped_column(primary_key=True) deb_version: Mapped[str] = mapped_column(DebVersion) + deb_version_fixed: Mapped[Optional[str]] = mapped_column(DebVersion) debsec_vulnerable: Mapped[bool] = mapped_column() data_cpe_match: Mapped[Any] @@ -114,5 +115,6 @@ class DebCve(Base): def merge(self, other: Self) -> None: self.deb_version = other.deb_version + self.deb_version_fixed = other.deb_version_fixed self.debsec_vulnerable = other.debsec_vulnerable self.data_cpe_match = other.data_cpe_match diff --git a/src/glvd/web/nvd.py b/src/glvd/web/nvd.py index c9dd935..1d61499 100644 --- a/src/glvd/web/nvd.py +++ b/src/glvd/web/nvd.py @@ -12,7 +12,42 @@ # XXX: Can we replace that with a view, which combines data and data_configurations in the database? -stmt_cve_deb_cpe = ( +stmt_cve_deb_cpe_version = ( + text(''' + SELECT + nvd_cve.data, + array_to_json( + array_remove( + array_agg(deb_cve.data_cpe_match), + NULL + ) + ) AS data_cpe_matches + FROM + nvd_cve + LEFT OUTER JOIN deb_cve USING (cve_id) + INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id) + WHERE + dist_cpe.cpe_vendor = :cpe_vendor AND + dist_cpe.cpe_product = :cpe_product AND + dist_cpe.cpe_version LIKE :cpe_version AND + deb_cve.deb_source = :deb_source AND + ( + deb_cve.deb_version_fixed > :deb_version OR + deb_cve.deb_version_fixed IS NULL + ) + GROUP BY + nvd_cve.cve_id + ''') + .bindparams( + bindparam('cpe_vendor'), + bindparam('cpe_product'), + bindparam('cpe_version'), + bindparam('deb_source'), + bindparam('deb_version'), + ) +) + +stmt_cve_deb_cpe_vulnerable = ( text(''' SELECT nvd_cve.data, @@ -69,16 +104,25 @@ @bp.route('/rest/json/cves/2.0+deb') async def nvd_cve_deb(): - if cpe_name := request.args.get('cpeName', type=str): + if cpe_name := request.args.get('virtualMatchString', type=str): cpe = Cpe.parse(cpe_name) if not cpe.is_debian: return 'Not Debian related CPE', 400 - stmt = stmt_cve_deb_cpe.bindparams( - cpe_vendor=cpe.vendor, - cpe_product=cpe.product, - cpe_version=cpe.version or '%', - deb_source=cpe.other.deb_source or '%', - ) + if cpe.other.deb_source and (deb_version := request.args.get('debVersionEnd', type=str)): + stmt = stmt_cve_deb_cpe_version.bindparams( + cpe_vendor=cpe.vendor, + cpe_product=cpe.product, + cpe_version=cpe.version or '%', + deb_source=cpe.other.deb_source, + deb_version=deb_version, + ) + else: + stmt = stmt_cve_deb_cpe_vulnerable.bindparams( + cpe_vendor=cpe.vendor, + cpe_product=cpe.product, + cpe_version=cpe.version or '%', + deb_source=cpe.other.deb_source or '%', + ) elif cve_id := request.args.get('cveId', type=str): stmt = stmt_cve_deb_cve_id.bindparams(cve_id=cve_id) diff --git a/tests/cli/test_combine_deb.py b/tests/cli/test_combine_deb.py index e0ba687..b130124 100644 --- a/tests/cli/test_combine_deb.py +++ b/tests/cli/test_combine_deb.py @@ -44,7 +44,11 @@ async def test_combine_base(self, db_session): assert t.deb_version == '1' assert t.debsec_vulnerable is False assert t.data_cpe_match == { - 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test\,deb_version\=1', + 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test', + 'deb': { + 'versionEndExcluding': '1', + 'versionLatest': '1', + }, 'vulnerable': False, } @@ -67,7 +71,11 @@ async def test_combine_base_vulnerable(self, db_session): assert t.deb_version == '1' assert t.debsec_vulnerable is True assert t.data_cpe_match == { - 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test\,deb_version\=1', + 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test', + 'deb': { + 'versionEndExcluding': '2', + 'versionLatest': '1', + }, 'vulnerable': True, } @@ -91,7 +99,11 @@ async def test_combine_fallback(self, db_session): assert t.deb_version == '1' assert t.debsec_vulnerable is False assert t.data_cpe_match == { - 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test\,deb_version\=1', + 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test', + 'deb': { + 'versionEndExcluding': '1', + 'versionLatest': '1', + }, 'vulnerable': False, } @@ -115,6 +127,10 @@ async def test_combine_fallback_vulnerable(self, db_session): assert t.deb_version == '1' assert t.debsec_vulnerable is True assert t.data_cpe_match == { - 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test\,deb_version\=1', + 'criteria': r'cpe:2.3:o:debian:debian_linux:13:*:*:*:*:*:*:deb_source\=test', + 'deb': { + 'versionEndExcluding': '2', + 'versionLatest': '1', + }, 'vulnerable': True, }