Skip to content

feat: added a function to utilize purl integration #4164

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

Merged
merged 5 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions cve_bin_tool/parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Copyright (C) 2022 Intel Corporation
# SPDX-License-Identifier: GPL-3.0-or-later

import sqlite3
from pathlib import Path
from typing import List, Tuple

from packageurl import PackageURL

from cve_bin_tool.error_handler import CVEDBError
from cve_bin_tool.sbom_manager import SBOMManager
from cve_bin_tool.util import ProductInfo, ScanInfo

__all__ = [
Expand Down Expand Up @@ -89,3 +95,46 @@ def generate_purl(self, product, vendor, qualifier={}, subpath=None):
subpath=subpath,
)
return purl

def find_vendor_from_purl(self, purl, ver) -> Tuple[List[ScanInfo], bool]:
"""
Finds the vendor information for a given PackageURL (purl) and version from the database.

This method queries the database to retrieve Common Platform Enumeration (CPE) data associated with the given purl.
It then decodes the CPE data to extract vendor, product, and version information. If the version matches the provided
version, it constructs a ScanInfo object for each matching entry and returns a list of these objects.
"""

query = "SELECT cpe from purl2cpe WHERE purl=?"
cursor = self.db_open_and_get_cursor()
cursor.execute(query, [str(purl)])
cpeList = cursor.fetchall()
vendorlist: list[ScanInfo] = []
sbom = SBOMManager(self.filename)

if cpeList != []:
for item in cpeList:
vendor, product, version = sbom.decode_cpe23(str(item))
location = "/usr/local/bin/product"
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'm not sure what to do about this

Copy link
Contributor

Choose a reason for hiding this comment

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

What is location being used for? How is the path established? As written, this won't work on Windows.

I think the location in the ProductInfo is the file location of the application which is being scanned.

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 too thought that it's the location of the product binary being scanned, but wasn't sure so I put a default string similar to find_vendor() method.

Apart from windows, it should be working on the Linux, however it's not able to find the installed database in the cache.

if version == ver:
vendorlist.append(
ScanInfo(
ProductInfo(vendor, product, version, location),
self.filename,
)
)
else:
return vendorlist, False
return vendorlist, True

def db_open_and_get_cursor(self) -> sqlite3.Cursor:
"""Opens connection to sqlite database, returns cursor object."""

dbpath = Path("~").expanduser() / ".cache" / "cve-bin-tool" / "purl2cpe.db"
connection = sqlite3.connect(dbpath)

if connection is not None:
cursor = connection.cursor()
if cursor is None:
raise CVEDBError
return cursor
33 changes: 17 additions & 16 deletions cve_bin_tool/parsers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from cve_bin_tool.parsers import Parser
from cve_bin_tool.strings import parse_strings
from cve_bin_tool.util import ProductInfo, ScanInfo


class PythonRequirementsParser(Parser):
Expand All @@ -22,13 +21,12 @@ class PythonRequirementsParser(Parser):

def __init__(self, cve_db, logger):
"""Initialize the python requirements file parser."""
self.purl_pkg_type = "pypi"
super().__init__(cve_db, logger)
self.purl_pkg_type = "pypi"

def generate_purl(self, product, vendor, qualifier={}, subpath=None):
"""Generates PURL after normalizing all components."""
product = re.sub(r"[^a-zA-Z0-9._-]", "", product).lower()
vendor = "UNKNOWN"

if not product:
return None
Expand Down Expand Up @@ -97,7 +95,12 @@ def run_checker(self, filename):
for line in lines["install"]:
product = line["metadata"]["name"]
version = line["metadata"]["version"]
vendor = self.find_vendor(product, version)
purl = self.generate_purl(product, "")
vendor, result = self.find_vendor_from_purl(purl, version)

if not result:
vendor = self.find_vendor(product, version)

if vendor is not None:
yield from vendor
self.logger.debug(f"Done scanning file: {self.filename}")
Expand All @@ -112,13 +115,12 @@ class PythonParser(Parser):

def __init__(self, cve_db, logger):
"""Initialize the python package metadata parser."""
self.purl_pkg_type = "pypi"
super().__init__(cve_db, logger)
self.purl_pkg_type = "pypi"

def generate_purl(self, product, vendor, qualifier={}, subpath=None):
"""Generates PURL after normalizing all components."""
product = re.sub(r"[^a-zA-Z0-9._-]", "", product).lower()
vendor = "UNKNOWN"

if not product:
return None
Expand All @@ -144,16 +146,15 @@ def run_checker(self, filename):
try:
product = search(compile(r"^Name: (.+)$", MULTILINE), lines).group(1)
version = search(compile(r"^Version: (.+)$", MULTILINE), lines).group(1)
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
if vendor_package_pair != []:
for pair in vendor_package_pair:
vendor = pair["vendor"]
location = pair.get("location", "/usr/local/bin/product")
file_path = self.filename
self.logger.debug(f"{file_path} is {vendor}.{product} {version}")
yield ScanInfo(
ProductInfo(vendor, product, version, location), file_path
)

purl = self.generate_purl(product, "")
vendor, result = self.find_vendor_from_purl(purl, version)

if not result:
vendor = self.find_vendor(product, version)

if vendor is not None:
yield from vendor

# There are packages with a METADATA file in them containing different data from what the tool expects
except AttributeError:
Expand Down
Loading