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_vuln_db('...') decorator. The register_vuln_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 VulnDatabase

    class GitVulnDatabase
    VulnDatabase <|-- GitVulnDatabase

    class CveListVulnEntry
    class CveListVulnDatabase
    GitVulnDatabase <|-- CveListVulnDatabase
    CveListVulnDatabase o-- CveListVulnEntry

    class NvdVulnEntry
    class NvdFkieVulnDatabase
    GitVulnDatabase <|-- NvdFkieVulnDatabase
    NvdFkieVulnDatabase o-- NvdVulnEntry

    class AnnotDatabase
    VulnDatabase <|-- 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 VulnDbEntry
    class VulnRecordDbEntry
    VulnDbEntry <|-- VulnRecordDbEntry
    VulnRecordDbEntry <|-- CveListVulnEntry
    VulnRecordDbEntry <|-- NvdVulnEntry

    class AnnotDbEntry
    VulnDbEntry <|-- AnnotDbEntry
    AnnotDbEntry <|-- OpenVexAnnotEntry
    AnnotDbEntry <|-- Spdx3AnnotEntry
    AnnotDbEntry <|-- YoctoAnnotEntry

    class GitDatabase
    GitAnnotDatabase o-- GitDatabase
    GitVulnDatabase 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, GitVulnDatabase 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_vuln_filtered()
    }

    class CsvExport
    class Spdx3Export
    class YoctoCveCheckExport

    BaseExport <|-- CsvExport
    BaseExport <|-- Spdx3Export
    BaseExport <|-- YoctoCveCheckExport
    

From the process_export() function:

  • From the SBOM the list of build “recipe” is retrieved, thanks to iterate_component_builds(). This function returns CompBuild objects, 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 vulnerability object is created, named ComputedVulnInfo. 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_vuln_filtered() method of the BaseExport class 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 CVEs. 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 annotation database entry, check if the annotation is applicable to this component: The version and component identifier must match. If this is not the case, just ignore this annotation: The information contained in the annotation will not be used to find if the vulnerability is applicable to the component currently checked.

    • 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 CVE entry (annotation).

    • For CVE database entries, which are not annotations (typically an entry from NVD or CVE List CVE database), always take all entries.

  • First, regardless of the database priority, if any CVE database entry indicates that the vulnerability is rejected, indicate that the component is not affected, and provide this assessment.

  • For each group of CVE database entries, which were grouped by priority, execute the following checks, starting with the group 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: retrieve all the version ranges applicable to the component. From these version ranges compute the VEX assessment, which is described in more detail in the subsection below.

    • For this current database priority, if no assessment was computed or if the component is considered vulnerable, check if affected sources by the vulnerability are compiled, which is described in more detail in the subsection below.

  • 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 assessment indicating that databases do not contain enough version information. This default assessment considers that the component is affected (vulnerable) by this CVE.

Note

To simplify this explanation, the computation of obsolete assessments was ignored.

Compute VEX assessment from semantic version ranges

From the group of CVE database entries with the same priority, retrieve the list of version ranges applicable to the component being assessed. Each version range may indicate either a range of vulnerable versions or a range of unaffected versions.

Currently, the version ranges are retrieved from the following sources:

  • From NVD database, the ranges are retrieved from configurationsnodescpeMatch, if the CPE is applicable to the component being assessed.

  • From CVE List database, from all the containers (CNA, or ADP), the version ranges are retrieved:

    • From cpeApplicabilitynodescpeMatch, if the CPE is applicable to the component being assessed.

    • From affectedversions, if the version range is considered applicable to the component being assessed:

      • If one of affectedcpes matches the component being assessed,

      • Or, if the affectedpackageName matches the component being assessed,

      • Or, if both fields (cpes and packageName) are missing, and there is only one vulnerable CPE provided by cpeApplicabilitynodescpeMatch matching the component being assessed.

Then, the component version is compared against the list of version ranges that was retrieved. For each version range:

  • If this is a vulnerable version range, check if the component version is within the range, smaller or greater.

  • If this is an unaffected version range, check if the component version is within the range or outside this range.

And finally, the VEX assessment is computed using the following rules:

  • If the component version is within an unaffected version range, and if this range was provided by the kernel.org CNA, then indicate that the vulnerability is fixed with the following status notes: version-not-in-range.

  • If the component version is within a vulnerable version range, then indicate that the vulnerability is affecting this component with the following status notes: version-in-range.

  • If the component version is only outside an unaffected version range (it is not inside an unaffected version range, and it is not outside a vulnerable version range), then indicate that the vulnerability is affecting this component with the following status notes: version-maybe-in-range.

  • If the component version is outside a vulnerable version range, or it is inside an unaffected version range, then indicate that the vulnerability is fixed with the following status notes: version-not-in-range.

  • Otherwise, if there was no version range found, do not provide a VEX assessment (at this point).

Compute VEX assessment from compiled sources

If the vulnerability database entry (typically from CVE List database) provides a list of affected sources (program files), and if the SBOM provides compiled source files for this component, check if we can ignore this vulnerability.

If both conditions are not met, do not provide any assessment.

For each listed affected source file, check if the affected file path is a “suffix” of one of the listed compiled file paths. This is done this way, since most of the time the compiled file path is prefixed with the build directory.

If all affected source files are not found in the list of compiled files, then indicate that the component is not affected with the following justification: “vulnerable code not present”. Otherwise, do not provide any assessment.