lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20260119064731.23879-13-luis.augenstein@tngtech.com>
Date: Mon, 19 Jan 2026 07:47:29 +0100
From: Luis Augenstein <luis.augenstein@...tech.com>
To: nathan@...nel.org,
	nsc@...nel.org
Cc: linux-kbuild@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	akpm@...ux-foundation.org,
	gregkh@...uxfoundation.org,
	maximilian.huber@...tech.com,
	Luis Augenstein <luis.augenstein@...tech.com>
Subject: [PATCH 12/14] tools/sbom: add SPDX build graph

Implement the SPDX build graph to describe the relationships
between source files in the source SBOM and output files in
the output SBOM.

Co-developed-by: Maximilian Huber <maximilian.huber@...tech.com>
Signed-off-by: Maximilian Huber <maximilian.huber@...tech.com>
Signed-off-by: Luis Augenstein <luis.augenstein@...tech.com>
---
 .../sbom/sbom/spdx_graph/build_spdx_graphs.py |  17 +
 .../sbom/sbom/spdx_graph/spdx_build_graph.py  | 317 ++++++++++++++++++
 2 files changed, 334 insertions(+)
 create mode 100644 tools/sbom/sbom/spdx_graph/spdx_build_graph.py

diff --git a/tools/sbom/sbom/spdx_graph/build_spdx_graphs.py b/tools/sbom/sbom/spdx_graph/build_spdx_graphs.py
index a61257a90..eecc52156 100644
--- a/tools/sbom/sbom/spdx_graph/build_spdx_graphs.py
+++ b/tools/sbom/sbom/spdx_graph/build_spdx_graphs.py
@@ -4,6 +4,7 @@
 from datetime import datetime
 from typing import Protocol
 
+import logging
 from sbom.config import KernelSpdxDocumentKind
 from sbom.cmd_graph import CmdGraph
 from sbom.path_utils import PathStr
@@ -11,6 +12,7 @@ from sbom.spdx_graph.kernel_file import KernelFileCollection
 from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection
 from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements
 from sbom.spdx_graph.spdx_source_graph import SpdxSourceGraph
+from sbom.spdx_graph.spdx_build_graph import SpdxBuildGraph
 from sbom.spdx_graph.spdx_output_graph import SpdxOutputGraph
 
 
@@ -61,5 +63,20 @@ def build_spdx_graphs(
             shared_elements=shared_elements,
             spdx_id_generators=spdx_id_generators,
         )
+    else:
+        logging.info(
+            "Skipped creating a dedicated source SBOM because source files cannot be "
+            "reliably classified when the source and object trees are identical. "
+            "Added source files to the build SBOM instead."
+        )
+
+    build_graph = SpdxBuildGraph.create(
+        cmd_graph,
+        kernel_files,
+        shared_elements,
+        output_graph.high_level_build_element,
+        spdx_id_generators,
+    )
+    spdx_graphs[KernelSpdxDocumentKind.BUILD] = build_graph
 
     return spdx_graphs
diff --git a/tools/sbom/sbom/spdx_graph/spdx_build_graph.py b/tools/sbom/sbom/spdx_graph/spdx_build_graph.py
new file mode 100644
index 000000000..2956800fa
--- /dev/null
+++ b/tools/sbom/sbom/spdx_graph/spdx_build_graph.py
@@ -0,0 +1,317 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+# Copyright (C) 2025 TNG Technology Consulting GmbH
+
+from dataclasses import dataclass
+from typing import Mapping
+from sbom.cmd_graph import CmdGraph
+from sbom.path_utils import PathStr
+from sbom.spdx import SpdxIdGenerator
+from sbom.spdx.build import Build
+from sbom.spdx.core import ExternalMap, NamespaceMap, Relationship, SpdxDocument
+from sbom.spdx.software import File, Sbom
+from sbom.spdx_graph.kernel_file import KernelFileCollection
+from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements
+from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection
+from sbom.spdx_graph.spdx_source_graph import source_file_license_elements
+
+
+@...aclass
+class SpdxBuildGraph(SpdxGraph):
+    """SPDX graph representing build dependencies connecting source files and
+    distributable output files"""
+
+    @classmethod
+    def create(
+        cls,
+        cmd_graph: CmdGraph,
+        kernel_files: KernelFileCollection,
+        shared_elements: SharedSpdxElements,
+        high_level_build_element: Build,
+        spdx_id_generators: SpdxIdGeneratorCollection,
+    ) -> "SpdxBuildGraph":
+        if len(kernel_files.source) > 0:
+            return _create_spdx_build_graph(
+                cmd_graph,
+                kernel_files,
+                shared_elements,
+                high_level_build_element,
+                spdx_id_generators,
+            )
+        else:
+            return _create_spdx_build_graph_with_mixed_sources(
+                cmd_graph,
+                kernel_files,
+                shared_elements,
+                high_level_build_element,
+                spdx_id_generators,
+            )
+
+
+def _create_spdx_build_graph(
+    cmd_graph: CmdGraph,
+    kernel_files: KernelFileCollection,
+    shared_elements: SharedSpdxElements,
+    high_level_build_element: Build,
+    spdx_id_generators: SpdxIdGeneratorCollection,
+) -> SpdxBuildGraph:
+    """
+    Creates an SPDX build graph where source and output files are referenced
+    from external documents.
+
+    Args:
+        cmd_graph: The dependency graph of a kernel build.
+        kernel_files: Collection of categorized kernel files involved in the build.
+        shared_elements: SPDX elements shared across multiple documents.
+        high_level_build_element: The high-level Build element referenced by the build graph.
+        spdx_id_generators: Collection of generators for SPDX element IDs.
+
+    Returns:
+        SpdxBuildGraph: The SPDX build graph connecting source files and distributable output files.
+    """
+    # SpdxDocument
+    build_spdx_document = SpdxDocument(
+        spdxId=spdx_id_generators.build.generate(),
+        profileConformance=["core", "software", "build"],
+        namespaceMap=[
+            NamespaceMap(prefix=generator.prefix, namespace=generator.namespace)
+            for generator in [
+                spdx_id_generators.build,
+                spdx_id_generators.source,
+                spdx_id_generators.output,
+                spdx_id_generators.base,
+            ]
+            if generator.prefix is not None
+        ],
+    )
+
+    # Sbom
+    build_sbom = Sbom(
+        spdxId=spdx_id_generators.build.generate(),
+        software_sbomType=["build"],
+    )
+
+    # Src and object tree elements
+    obj_tree_element = File(
+        spdxId=spdx_id_generators.build.generate(),
+        name="$(obj_tree)",
+        software_fileKind="directory",
+    )
+    obj_tree_contains_relationship = Relationship(
+        spdxId=spdx_id_generators.build.generate(),
+        relationshipType="contains",
+        from_=obj_tree_element,
+        to=[],
+    )
+
+    # File elements
+    build_file_elements = [file.spdx_file_element for file in kernel_files.build.values()]
+    file_relationships = _file_relationships(
+        cmd_graph=cmd_graph,
+        file_elements={key: file.spdx_file_element for key, file in kernel_files.to_dict().items()},
+        high_level_build_element=high_level_build_element,
+        spdx_id_generator=spdx_id_generators.build,
+    )
+
+    # Update relationships
+    build_spdx_document.rootElement = [build_sbom]
+
+    build_spdx_document.import_ = [
+        *(
+            ExternalMap(externalSpdxId=file_element.spdx_file_element.spdxId)
+            for file_element in kernel_files.source.values()
+        ),
+        ExternalMap(externalSpdxId=high_level_build_element.spdxId),
+        *(ExternalMap(externalSpdxId=file.spdx_file_element.spdxId) for file in kernel_files.output.values()),
+    ]
+
+    build_sbom.rootElement = [obj_tree_element]
+    build_sbom.element = [
+        obj_tree_element,
+        obj_tree_contains_relationship,
+        *build_file_elements,
+        *file_relationships,
+    ]
+
+    obj_tree_contains_relationship.to = [
+        *build_file_elements,
+        *(file.spdx_file_element for file in kernel_files.output.values()),
+    ]
+
+    # create Spdx graphs
+    build_graph = SpdxBuildGraph(
+        build_spdx_document,
+        shared_elements.agent,
+        shared_elements.creation_info,
+        build_sbom,
+    )
+    return build_graph
+
+
+def _create_spdx_build_graph_with_mixed_sources(
+    cmd_graph: CmdGraph,
+    kernel_files: KernelFileCollection,
+    shared_elements: SharedSpdxElements,
+    high_level_build_element: Build,
+    spdx_id_generators: SpdxIdGeneratorCollection,
+) -> SpdxBuildGraph:
+    """
+    Creates an SPDX build graph where only output files are referenced from
+    an external document. Source files are included directly in the build graph.
+
+    Args:
+        cmd_graph: The dependency graph of a kernel build.
+        kernel_files: Collection of categorized kernel files involved in the build.
+        shared_elements: SPDX elements shared across multiple documents.
+        high_level_build_element: The high-level Build element referenced by the build graph.
+        spdx_id_generators: Collection of generators for SPDX element IDs.
+
+    Returns:
+        SpdxBuildGraph: The SPDX build graph connecting source files and distributable output files.
+    """
+    # SpdxDocument
+    build_spdx_document = SpdxDocument(
+        spdxId=spdx_id_generators.build.generate(),
+        profileConformance=["core", "software", "build"],
+        namespaceMap=[
+            NamespaceMap(prefix=generator.prefix, namespace=generator.namespace)
+            for generator in [
+                spdx_id_generators.build,
+                spdx_id_generators.output,
+                spdx_id_generators.base,
+            ]
+            if generator.prefix is not None
+        ],
+    )
+
+    # Sbom
+    build_sbom = Sbom(
+        spdxId=spdx_id_generators.build.generate(),
+        software_sbomType=["build"],
+    )
+
+    # File elements
+    build_file_elements = [file.spdx_file_element for file in kernel_files.build.values()]
+    file_relationships = _file_relationships(
+        cmd_graph=cmd_graph,
+        file_elements={key: file.spdx_file_element for key, file in kernel_files.to_dict().items()},
+        high_level_build_element=high_level_build_element,
+        spdx_id_generator=spdx_id_generators.build,
+    )
+
+    # Source file license elements
+    source_file_license_identifiers, source_file_license_relationships = source_file_license_elements(
+        list(kernel_files.build.values()), spdx_id_generators.build
+    )
+
+    # Update relationships
+    build_spdx_document.rootElement = [build_sbom]
+    root_file_elements = [file.spdx_file_element for file in kernel_files.output.values()]
+    build_spdx_document.import_ = [
+        ExternalMap(externalSpdxId=high_level_build_element.spdxId),
+        *(ExternalMap(externalSpdxId=file.spdxId) for file in root_file_elements),
+    ]
+
+    build_sbom.rootElement = [*root_file_elements]
+    build_sbom.element = [
+        *build_file_elements,
+        *source_file_license_identifiers,
+        *source_file_license_relationships,
+        *file_relationships,
+    ]
+
+    build_graph = SpdxBuildGraph(
+        build_spdx_document,
+        shared_elements.agent,
+        shared_elements.creation_info,
+        build_sbom,
+    )
+    return build_graph
+
+
+def _file_relationships(
+    cmd_graph: CmdGraph,
+    file_elements: Mapping[PathStr, File],
+    high_level_build_element: Build,
+    spdx_id_generator: SpdxIdGenerator,
+) -> list[Build | Relationship]:
+    """
+    Construct SPDX Build and Relationship elements representing dependency
+    relationships in the cmd graph.
+
+    Args:
+        cmd_graph: The dependency graph of a kernel build.
+        file_elements: Mapping of filesystem paths (PathStr) to their
+            corresponding SPDX File elements.
+        high_level_build_element: The SPDX Build element representing the overall build process/root.
+        spdx_id_generator: Generator for unique SPDX IDs.
+
+    Returns:
+        list[Build | Relationship]: List of SPDX Build and Relationship elements
+    """
+    high_level_build_ancestorOf_relationship = Relationship(
+        spdxId=spdx_id_generator.generate(),
+        relationshipType="ancestorOf",
+        from_=high_level_build_element,
+        completeness="complete",
+        to=[],
+    )
+
+    # Create a relationship between each node (output file)
+    # and its children (input files)
+    build_and_relationship_elements: list[Build | Relationship] = [high_level_build_ancestorOf_relationship]
+    for node in cmd_graph:
+        if next(node.children, None) is None:
+            continue
+
+        # .cmd file dependencies
+        if node.cmd_file is not None:
+            build_element = Build(
+                spdxId=spdx_id_generator.generate(),
+                build_buildType=high_level_build_element.build_buildType,
+                build_buildId=high_level_build_element.build_buildId,
+                comment=node.cmd_file.savedcmd,
+            )
+            hasInput_relationship = Relationship(
+                spdxId=spdx_id_generator.generate(),
+                relationshipType="hasInput",
+                from_=build_element,
+                to=[file_elements[child_node.absolute_path] for child_node in node.children],
+            )
+            hasOutput_relationship = Relationship(
+                spdxId=spdx_id_generator.generate(),
+                relationshipType="hasOutput",
+                from_=build_element,
+                to=[file_elements[node.absolute_path]],
+            )
+            build_and_relationship_elements += [
+                build_element,
+                hasInput_relationship,
+                hasOutput_relationship,
+            ]
+            high_level_build_ancestorOf_relationship.to.append(build_element)
+
+        # incbin dependencies
+        if len(node.incbin_dependencies) > 0:
+            incbin_dependsOn_relationship = Relationship(
+                spdxId=spdx_id_generator.generate(),
+                relationshipType="dependsOn",
+                comment="\n".join([incbin_dependency.full_statement for incbin_dependency in node.incbin_dependencies]),
+                from_=file_elements[node.absolute_path],
+                to=[
+                    file_elements[incbin_dependency.node.absolute_path]
+                    for incbin_dependency in node.incbin_dependencies
+                ],
+            )
+            build_and_relationship_elements.append(incbin_dependsOn_relationship)
+
+        # hardcoded dependencies
+        if len(node.hardcoded_dependencies) > 0:
+            hardcoded_dependency_relationship = Relationship(
+                spdxId=spdx_id_generator.generate(),
+                relationshipType="dependsOn",
+                from_=file_elements[node.absolute_path],
+                to=[file_elements[n.absolute_path] for n in node.hardcoded_dependencies],
+            )
+            build_and_relationship_elements.append(hardcoded_dependency_relationship)
+
+    return build_and_relationship_elements
-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ