Skip to content

Commit b03681c

Browse files
authored
feat: Adding locations in CycloneDX reports (#3989)
1 parent 3d1c1b7 commit b03681c

23 files changed

+420
-91
lines changed

cve_bin_tool/input_engine.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def decode_bom_ref(self, ref) -> ProductInfo:
261261
urn_cdx = re.compile(
262262
r"urn:cdx:(?P<bomSerialNumber>.*?)\/(?P<bom_version>.*?)#(?P<bom_ref>.*)"
263263
)
264-
264+
location = "location/to/product"
265265
if urn_cbt_ext_ref.match(ref):
266266
urn_dict = urn_cbt_ext_ref.match(ref).groupdict()
267267
vendor = urn_dict["vendor"]
@@ -290,7 +290,9 @@ def decode_bom_ref(self, ref) -> ProductInfo:
290290

291291
product_info = None
292292
if product is not None and self.validate_product(product):
293-
product_info = ProductInfo(vendor.strip(), product.strip(), version.strip())
293+
product_info = ProductInfo(
294+
vendor.strip(), product.strip(), version.strip(), location
295+
)
294296

295297
return product_info
296298

@@ -314,7 +316,10 @@ def parse_data(self, fields: Set[str], data: Iterable) -> None:
314316

315317
for row in data:
316318
product_info = ProductInfo(
317-
row["vendor"].strip(), row["product"].strip(), row["version"].strip()
319+
row["vendor"].strip(),
320+
row["product"].strip(),
321+
row["version"].strip(),
322+
row.get("location", "location/to/product").strip(),
318323
)
319324
self.parsed_data[product_info][
320325
row.get("cve_number", "").strip() or "default"

cve_bin_tool/merge.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ def parse_data_from_json(
208208

209209
for row in json_data:
210210
product_info = ProductInfo(
211-
row["vendor"].strip(), row["product"].strip(), row["version"].strip()
211+
row["vendor"].strip(),
212+
row["product"].strip(),
213+
row["version"].strip(),
214+
row.get("location", "location/to/product").strip(),
212215
)
213216
parsed_data[product_info][row.get("cve_number", "").strip() or "default"] = {
214217
"remarks": Remarks(str(row.get("remarks", "")).strip()),

cve_bin_tool/output_engine/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def output_csv(
106106
"vendor",
107107
"product",
108108
"version",
109+
"location",
109110
"cve_number",
110111
"severity",
111112
"score",
@@ -917,6 +918,7 @@ def generate_sbom(
917918
sbom_relationships = []
918919
my_package = SBOMPackage()
919920
sbom_relationship = SBOMRelationship()
921+
920922
# Create root package
921923
my_package.initialise()
922924
root_package = f'CVEBINTOOL-{Path(sbom_root).name.replace(".", "-")}'
@@ -929,13 +931,15 @@ def generate_sbom(
929931
my_package.set_licensedeclared(license)
930932
my_package.set_licenseconcluded(license)
931933
my_package.set_supplier("UNKNOWN", "NOASSERTION")
934+
932935
# Store package data
933936
sbom_packages[(my_package.get_name(), my_package.get_value("version"))] = (
934937
my_package.get_package()
935938
)
936939
sbom_relationship.initialise()
937940
sbom_relationship.set_relationship(parent, "DESCRIBES", root_package)
938941
sbom_relationships.append(sbom_relationship.get_relationship())
942+
939943
# Add dependent products
940944
for product_data in all_product_data:
941945
my_package.initialise()
@@ -950,6 +954,8 @@ def generate_sbom(
950954
in sbom_packages
951955
and product_data.vendor == "unknown"
952956
):
957+
location = product_data.location
958+
my_package.set_evidence(location) # Set location directly
953959
sbom_packages[
954960
(my_package.get_name(), my_package.get_value("version"))
955961
] = my_package.get_package()

cve_bin_tool/output_engine/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ def format_output(
160160
"vendor": "haxx"
161161
"product": "curl",
162162
"version": "1.2.1",
163+
"location": "/usr/local/bin/product",
163164
"cve_number": "CVE-1234-1234",
164165
"severity": "LOW",
165166
"score": "1.2",
@@ -191,6 +192,7 @@ def format_output(
191192
"vendor": product_info.vendor,
192193
"product": product_info.product,
193194
"version": product_info.version,
195+
"location": product_info.location,
194196
"cve_number": cve.cve_number,
195197
"severity": cve.severity,
196198
"score": str(cve.score),

cve_bin_tool/parsers/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,20 @@ def find_vendor(self, product, version):
6262
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
6363
vendorlist: list[ScanInfo] = []
6464
file_path = self.filename
65+
location = file_path
6566
if vendor_package_pair != []:
6667
# To handle multiple vendors, return all combinations of product/vendor mappings
6768
for v in vendor_package_pair:
6869
vendor = v["vendor"]
70+
location = v.get("location", "/usr/local/bin/product")
6971
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
7072
vendorlist.append(
71-
ScanInfo(ProductInfo(vendor, product, version), file_path)
73+
ScanInfo(ProductInfo(vendor, product, version, location), file_path)
7274
)
7375
else:
7476
# Add entry
7577
vendorlist.append(
76-
ScanInfo(ProductInfo("UNKNOWN", product, version), file_path)
78+
ScanInfo(ProductInfo("UNKNOWN", product, version, location), file_path)
7779
)
7880
return vendorlist
7981

cve_bin_tool/parsers/java.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@ def find_vendor(self, product, version):
5757
for pair in vendor_package_pair:
5858
vendor = pair["vendor"]
5959
file_path = self.filename
60+
location = pair.get("location", "/usr/local/bin/product")
6061
self.logger.debug(f"{file_path} {product} {version} by {vendor}")
61-
info.append(ScanInfo(ProductInfo(vendor, product, version), file_path))
62+
info.append(
63+
ScanInfo(ProductInfo(vendor, product, version, location), file_path)
64+
)
6265
return info
6366
return None
6467

cve_bin_tool/parsers/python.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,12 @@ def run_checker(self, filename):
152152
if vendor_package_pair != []:
153153
for pair in vendor_package_pair:
154154
vendor = pair["vendor"]
155+
location = pair.get("location", "/usr/local/bin/product")
155156
file_path = self.filename
156157
self.logger.debug(f"{file_path} is {vendor}.{product} {version}")
157-
yield ScanInfo(ProductInfo(vendor, product, version), file_path)
158+
yield ScanInfo(
159+
ProductInfo(vendor, product, version, location), file_path
160+
)
158161

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

cve_bin_tool/sbom_manager/__init__.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
import re
7+
import sys
78
from collections import defaultdict
89
from logging import Logger
910
from pathlib import Path
@@ -15,7 +16,12 @@
1516
from cve_bin_tool.cvedb import CVEDB
1617
from cve_bin_tool.input_engine import TriageData
1718
from cve_bin_tool.log import LOGGER
18-
from cve_bin_tool.util import ProductInfo, Remarks
19+
from cve_bin_tool.util import (
20+
ProductInfo,
21+
Remarks,
22+
find_product_location,
23+
validate_location,
24+
)
1925
from cve_bin_tool.validator import validate_cyclonedx, validate_spdx
2026

2127
from .swid_parser import SWIDParser
@@ -80,10 +86,17 @@ def common_prefix_split(self, product, version) -> list[ProductInfo]:
8086
len(common_prefix_vendor) == 1
8187
and common_prefix_vendor[0] != "UNKNOWN"
8288
):
89+
location = find_product_location(common_prefix_product)
90+
if location is None:
91+
location = "NotFound"
92+
if validate_location(location) is False:
93+
raise ValueError(f"Invalid location {location} for {product}")
8394
found_common_prefix = True
8495
for vendor in common_prefix_vendor:
8596
parsed_data.append(
86-
ProductInfo(vendor, common_prefix_product, version)
97+
ProductInfo(
98+
vendor, common_prefix_product, version, location
99+
)
87100
)
88101
break
89102
if not found_common_prefix:
@@ -97,8 +110,15 @@ def common_prefix_split(self, product, version) -> list[ProductInfo]:
97110
temp = self.get_vendor(sp)
98111
if len(temp) > 1 or (len(temp) == 1 and temp[0] != "UNKNOWN"):
99112
for vendor in temp:
113+
location = find_product_location(sp)
114+
if location is None:
115+
location = "NotFound"
116+
if validate_location(location) is False:
117+
raise ValueError(
118+
f"Invalid location {location} for {product}"
119+
)
100120
# if vendor is not None:
101-
parsed_data.append(ProductInfo(vendor, sp, version))
121+
parsed_data.append(ProductInfo(vendor, sp, version, location))
102122
return parsed_data
103123

104124
def scan_file(self) -> dict[ProductInfo, TriageData]:
@@ -139,9 +159,21 @@ def scan_file(self) -> dict[ProductInfo, TriageData]:
139159
vendor_set = self.get_vendor(product)
140160
for vendor in vendor_set:
141161
# if vendor is not None:
142-
parsed_data.append(ProductInfo(vendor, product, version))
162+
location = find_product_location(product)
163+
if location is None:
164+
location = "NotFound"
165+
if validate_location(location) is False:
166+
raise ValueError(f"Invalid location {location} for {product}")
167+
parsed_data.append(ProductInfo(vendor, product, version, location))
143168
else:
144-
parsed_data.append(ProductInfo(module_vendor, product, version))
169+
location = find_product_location(product)
170+
if location is None:
171+
location = "NotFound"
172+
if validate_location(location) is False:
173+
raise ValueError(f"Invalid location {location} for {product}")
174+
parsed_data.append(
175+
ProductInfo(module_vendor, product, version, location)
176+
)
145177

146178
for row in parsed_data:
147179
self.sbom_data[row]["default"] = {
@@ -357,7 +389,6 @@ def decode_purl(self, purl) -> (str | None, str | None, str | None):
357389

358390

359391
if __name__ == "__main__":
360-
import sys
361392

362393
file = sys.argv[1]
363394
sbom = SBOMManager(file)

cve_bin_tool/sbom_manager/cyclonedx_parser.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import defusedxml.ElementTree as ET
99

10+
from cve_bin_tool.util import find_product_location
1011
from cve_bin_tool.validator import validate_cyclonedx
1112

1213

@@ -38,7 +39,10 @@ def parse_cyclonedx_json(self, sbom_file: str) -> list[list[str]]:
3839
if d["type"] in self.components_supported:
3940
package = d["name"]
4041
version = d["version"]
41-
modules.append([package, version])
42+
location = find_product_location(package)
43+
if location is None:
44+
location = "NotFound"
45+
modules.append([package, version, location])
4246

4347
return modules
4448

@@ -68,8 +72,10 @@ def parse_cyclonedx_xml(self, sbom_file: str) -> list[list[str]]:
6872
if component_version is None:
6973
raise KeyError(f"Could not find version in {component}")
7074
version = component_version.text
71-
if version is not None:
72-
modules.append([package, version])
75+
location = find_product_location(package)
76+
if location is None:
77+
location = "NotFound"
78+
modules.append([package, version, location])
7379
return modules
7480

7581

cve_bin_tool/sbom_manager/spdx_parser.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import yaml
1111

1212
from cve_bin_tool.log import LOGGER
13+
from cve_bin_tool.util import find_product_location
1314
from cve_bin_tool.validator import validate_spdx
1415

1516

@@ -61,7 +62,10 @@ def parse_spdx_json(self, sbom_file: str) -> list[list[str]]:
6162
package = d["name"]
6263
try:
6364
version = d["versionInfo"]
64-
modules.append([package, version])
65+
location = find_product_location(package)
66+
if location is None:
67+
location = "NotFound"
68+
modules.append([package, version, location])
6569
except KeyError as e:
6670
LOGGER.debug(e, exc_info=True)
6771

0 commit comments

Comments
 (0)