Tool design
Concepts
sbom-cve-check tool is composed of 3 types of concept:
For each concept, plugins can be registered to add custom functionalities.
CVE database
A CVE database contains CVE entries. An annotation entry is a special kind of CVE entry, which is contained in an Annotation database.
Each database class is automatically registered into a registry, thanks to the
@register_cve_db('...') decorator. The register_cve_db function parameter
is the type name to register. The list of builtins CVE database type names is
provided in the CVE database section.
In the diagram below, the class diagram associated with CVE databases.
Note
Not all class are listed, only the most important ones.
classDiagram
class CveDatabase
class GitCveDatabase
CveDatabase <|-- GitCveDatabase
class CveListCveEntry
class CveListCveDatabase
GitCveDatabase <|-- CveListCveDatabase
CveListCveDatabase o-- CveListCveEntry
class NvdCveEntry
class NvdFkieCveDatabase
GitCveDatabase <|-- NvdFkieCveDatabase
NvdFkieCveDatabase o-- NvdCveEntry
class AnnotDatabase
CveDatabase <|-- AnnotDatabase
class GitAnnotDatabase
AnnotDatabase <|-- GitAnnotDatabase
class OpenVexAnnotEntry
class OpenVexAnnotDatabase
GitAnnotDatabase <|-- OpenVexAnnotDatabase
OpenVexAnnotDatabase o-- OpenVexAnnotEntry
class Spdx3AnnotEntry
class Spdx3AnnotDatabase
AnnotDatabase <|-- Spdx3AnnotDatabase
Spdx3AnnotDatabase o-- Spdx3AnnotEntry
class YoctoAnnotEntry
class YoctoAnnotDatabase
AnnotDatabase <|-- YoctoAnnotDatabase
YoctoAnnotDatabase o-- YoctoAnnotEntry
class CveDbEntry
CveDbEntry <|-- CveListCveEntry
CveDbEntry <|-- NvdCveEntry
class AnnotDbEntry
CveDbEntry <|-- AnnotDbEntry
AnnotDbEntry <|-- OpenVexAnnotEntry
AnnotDbEntry <|-- Spdx3AnnotEntry
AnnotDbEntry <|-- YoctoAnnotEntry
class GitDatabase
GitCveDatabase o-- GitDatabase
GitAnnotDatabase o-- GitDatabase
To speed up the lookup of CVEs that are associated with a component, the various databases are indexed: For each database, an index is created, which is a map between CPE product name (and only that part) and the list of potentially associated CVE identifiers.
For git databases, GitCveDatabase or GitAnnotDatabase, the index of the
database is stored to disk at the root of the git tree with the following name:
.sbom-cve-check-cache-index.json. The index is stored to disk because indexing
the database takes a bit of time. To invalidate the cache file, it contains the
associated commit hash identifier and the hash of relevant configuration
values.
The various database indexes are merged into a unique index stored in RAM.
The logic to obtain the applicable CVEs for a component identifier is described in the section below.
SBOM
A SBOM provides, among other things, the components contained in the image deployed to the device.
Each SBOM class is automatically registered into a registry, thanks to the
@register_sbom('...') decorator. The register_sbom function parameter
is the type name to register. The list of SBOM builtins type names is provided
in the SBOM supported formats section.
In the diagram below, the class diagram associated with SBOM:
classDiagram
class Sbom {
+Component components
+components_grouped_by_id()
+write_to_file()
}
class Component {
+name
+version
+identifiers
+description
+compiled_sources
}
class Spdx3Sbom
class Spdx3Component
Sbom <|-- Spdx3Sbom
Sbom o-- Component
Spdx3Sbom o-- Spdx3Component
Export
The tool provides multiple kinds of export types. The list of builtins export type names is provided in the export formats section.
Each export class is automatically registered into a registry, thanks to the
@register_export('...') decorator. The register_export function parameter
is the type name to register.
In the diagram below, the class diagram associated with export types:
classDiagram
class BaseExport {
+process_export()
-_is_cve_filtered()
}
class CsvExport
class Spdx3Export
class YoctoCveCheckExport
BaseExport <|-- CsvExport
BaseExport <|-- Spdx3Export
BaseExport <|-- cYoctoCveCheckExport
From the process_export() function:
From the SBOM the list of build “recipe” is retrieved, thanks to
iterate_component_builds(). This function returnsCompBuildobjects, each containing one or multiple components with the same vendor and product name, and with the same version. Typically, there are multiple components with the same identifier when a package is split into sub-packages.Then, for each group of components with the same identifier and the same version, a search for applicable CVE is realized as described in the subsection below. Here, the term “applicable” means that the CVE is associated with this component identifier regardless of the component version.
For each found CVE identifier, a special annotation object is created, named
AggregateAnnotEntry. This object aggregates the various sources of information from the CVE databases that were registered. This object is also responsible for computing the VEX assessment, as described in the subsection below, associated with the previously mentioned group of components.The
_is_cve_filtered()method of theBaseExportclass allows to filter (exclude) annotations that will be exported. The various filters are described in the export options.
Find applicable CVE
In this section, the term “applicable” means that the CVE is associated with this component identifier regardless of the component version. It does not mean that the component is vulnerable.
To find applicable CVE, from one or more component identifiers, obtained, for example, from a CPE, the following strategy is realized:
First get a unique list of product names and group component identifiers by product name.
For each product name, the database index, which is described in the CVE database subsection, is queried to obtain a unique set of potential associated CVE. Some CVEs in this list may not be applicable, since we only looked for the product name, without checking for the vendor part of the CPE.
In some databases, for the same CVE identifier, the information used to identify the component is sometimes incomplete; for example, the vendor part may be missing, but another database may have all the information needed to confirm, without a doubt, whether the component is applicable. To be able to confirm that the CVE is applicable (or not), the following algorithm is used (for each CVE):
For each component product name, get the component identifiers (from CPE) specified in CVEs that loosely match, which means that they have the same product name without checking for other fields (vendor, …).
Still for each component product name, keep only the best associated CVE component identifiers: If there are CVE component identifiers with the vendor part, only keep those, otherwise take all the identifiers found.
Then check if one of the component identifiers fully matches with the CVE component identifiers that were kept. If there is a match, consider that this CVE identifier is applicable to the component to check.
Compute VEX assessment
To compute the CVE VEX assessment associated with one or more component identifiers and one version, the following algorithm is used.
Retrieve the CVE database entries associated with the CVE identifier to check, and group these entries by priority. For CVE database entries, which are annotations, check that the component version is exactly the same as one of the versions specified in the annotation. If there is no match, do not use this annotation.
Start with the CVE database entries with the highest priority:
If the CVE database entry is an annotation, take the VEX assessment provided by this annotation if there is any.
Otherwise, “merge” the CVE database entries information: retrieve all the version ranges information applicable to the component. From these version ranges compute the VEX assessment.
If no assessment could be computed, repeat this process with CVE database entries with lower priority. In the end if no assessment could be computed, generate a default one indicating that databases do not contain enough version information. This default assessment considers that the component is affected (vulnerable) by this CVE.