Skip to content

Commit b71268b

Browse files
authored
Merge pull request #86 from TNG/module-packages
kernel module packages
2 parents 2813535 + de0e741 commit b71268b

10 files changed

Lines changed: 414 additions & 312 deletions

File tree

.github/workflows/sbom.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ jobs:
9090
--src-tree /workspace/${{ inputs.src_tree }} \
9191
--output-tree /workspace/${{ inputs.output_tree }} \
9292
--roots-file roots.txt \
93-
--spdx \
94-
--used-files \
93+
--generate-spdx \
94+
--generate-used-files \
9595
--package-license "GPL-2.0 WITH Linux-syscall-note" \
9696
--prettify-json \
9797
--debug

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"../linux/kernel_build",
1818
"--roots",
1919
"arch/x86/boot/bzImage",
20-
"--spdx",
21-
"--used-files",
20+
"--generate-spdx",
21+
"--generate-used-files",
2222
"--package-license",
2323
"GPL-2.0 WITH Linux-syscall-note",
2424
"--debug",

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ The eventual goal is to integrate the `sbom/` directory into the `linux/scripts/
3434
--src-tree ../linux \
3535
--output-tree ../linux/kernel_build \
3636
--roots arch/x86/boot/bzImage \
37-
--spdx \
38-
--used-files \
37+
--generate-spdx \
38+
--generate-used-files \
3939
--prettify-json
4040
```
4141

@@ -46,7 +46,7 @@ Using this cmd graph, the script generates three SPDX documents and writes them
4646
- **`sbom-build.spdx.json`** — Describes all build artifacts and the process by which they were built from the sources in `sbom-source.spdx.json`.
4747
- **`sbom-output.spdx.json`** — Describes the final build outputs, i.e., the provided root artifacts.
4848

49-
If the `--used-files` flag is enabled, the script also produces **`sbom.used-files.txt`**, a flat list of all source files in `sbom-source.spdx.json`.
49+
If the `--generate-used-files` flag is enabled, the script also produces **`sbom.used-files.txt`**, a flat list of all source files in `sbom-source.spdx.json`.
5050

5151
**Note:** If the source tree and output tree are identical, reliably distinguishing source files is not possible. In this case, the source SPDX document is merged into `sbom-build.spdx.json`, and `sbom.used-files.txt` contains all files from `sbom-build.spdx.json`.
5252

@@ -65,8 +65,8 @@ python3 sbom/sbom.py \
6565
--src-tree ../linux \
6666
--output-tree ../linux/kernel_build \
6767
--roots-file roots.txt \
68-
--spdx \
69-
--used-files \
68+
--generate-spdx \
69+
--generate-used-files \
7070
--prettify-json
7171
```
7272

@@ -136,7 +136,7 @@ flowchart TD
136136
subgraph OUTPUT_GRAPH["sbom-output.spdx.json"]
137137
OUTPUT_DOC["SpdxDocument"]
138138
OUTPUT_SBOM["Sbom"]
139-
PACKAGE["Package (Linux Kernel)"]
139+
PACKAGE["Package (Linux Kernel (bzImage))"]
140140
PACKAGE_LICENSEEXPRESSION["LicenseExpression (GPL-2.0 WITH Linux-syscall-note)"]
141141
BZIMAGE_COPY["File (Copy) (arch/x86/boot/bzImage)"]
142142
BZIMAGE_EXTERNALMAP["ExternalMap (arch/x86/boot/bzImage)"]
@@ -152,7 +152,7 @@ flowchart TD
152152
OUTPUT_SBOM -->|element| BZIMAGE
153153
OUTPUT_SBOM -->|element| PACKAGE_LICENSEEXPRESSION
154154
155-
PACKAGE -->|contains| BZIMAGE
155+
PACKAGE -->|hasDistributionArtifact| BZIMAGE
156156
PACKAGE -->|hasDeclaredLicense| PACKAGE_LICENSEEXPRESSION
157157
158158
end
@@ -202,7 +202,7 @@ flowchart TD
202202
subgraph OUTPUT_GRAPH["sbom-output.spdx.json"]
203203
OUTPUT_DOC["SpdxDocument"]
204204
OUTPUT_SBOM["Sbom"]
205-
PACKAGE["Package (Linux Kernel)"]
205+
PACKAGE["Package (Linux Kernel (bzImage))"]
206206
PACKAGE_LICENSEEXPRESSION["LicenseExpression (GPL-2.0 WITH Linux-syscall-note)"]
207207
BZIMAGE_COPY["File (Copy) (arch/x86/boot/bzImage)"]
208208
BZIMAGE_EXTERNALMAP["ExternalMap (arch/x86/boot/bzImage)"]
@@ -218,7 +218,7 @@ flowchart TD
218218
OUTPUT_SBOM -->|element| BZIMAGE
219219
OUTPUT_SBOM -->|element| PACKAGE_LICENSEEXPRESSION
220220
221-
PACKAGE -->|contains| BZIMAGE
221+
PACKAGE -->|hasDistributionArtifact| BZIMAGE
222222
PACKAGE -->|hasDeclaredLicense| PACKAGE_LICENSEEXPRESSION
223223
224224
end

sbom/lib/sbom/cmd_graph/hardcoded_dependencies.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Callable
66
import sbom.errors as sbom_errors
77
from sbom.path_utils import PathStr, is_relative_to
8+
from sbom.environment import Environment
89

910
HARDCODED_DEPENDENCIES: dict[str, list[str]] = {
1011
# defined in linux/Kbuild
@@ -70,7 +71,7 @@ def _evaluate_template(template: str, variables: dict[str, Callable[[], str | No
7071

7172

7273
def _get_arch(path: PathStr):
73-
srcarch = os.environ.get("SRCARCH")
74+
srcarch = Environment.SRCARCH
7475
if srcarch is None:
7576
sbom_errors.log(
7677
f"Skip architecture specific hardcoded dependency for '{path}' because the SRCARCH environment variable was not set."

sbom/lib/sbom/config.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# SPDX-FileCopyrightText: 2025 TNG Technology Consulting GmbH
3+
4+
import argparse
5+
from dataclasses import dataclass
6+
from enum import Enum
7+
import os
8+
from typing import Any
9+
import uuid
10+
from sbom.path_utils import PathStr
11+
12+
13+
class KernelSpdxDocumentKind(Enum):
14+
SOURCE = "source"
15+
BUILD = "build"
16+
OUTPUT = "output"
17+
18+
19+
@dataclass
20+
class KernelSbomConfig:
21+
src_tree: PathStr
22+
"""Absolute path to the Linux kernel source directory."""
23+
24+
output_tree: PathStr
25+
"""Absolute path to the build output directory."""
26+
27+
root_paths: list[PathStr]
28+
"""List of paths to root outputs (relative to output_tree) to base the SBOM on."""
29+
30+
generate_spdx: bool
31+
"""Whether to generate SPDX SBOM documents. If False, no SPDX files are created."""
32+
33+
spdx_file_names: dict[KernelSpdxDocumentKind, str]
34+
"""If `generate_spdx` is True, defines the file names for each SPDX SBOM kind (source, build, output) to store on disk."""
35+
36+
generate_used_files: bool
37+
"""Whether to generate a flat list of all source files used in the build. If False, no used-files document is created."""
38+
39+
used_files_file_name: str
40+
"""If `generate_used_files` is True, specifies the file name for the used-files document."""
41+
42+
debug: bool
43+
"""Whether to enable debug logging."""
44+
45+
spdxId_prefix: str
46+
"""Prefix to use for all SPDX element IDs."""
47+
48+
spdxId_uuid: uuid.UUID
49+
"""UUID used for reproducible SPDX element IDs."""
50+
51+
build_type: str
52+
"""SPDX buildType value for all Build elements."""
53+
54+
package_license: str
55+
"""License expression applied to all SPDX Packages."""
56+
57+
package_version: str | None
58+
"""Version string applied to all SPDX Packages."""
59+
60+
package_copyright_text: str | None
61+
"""Copyright text applied to all SPDX Packages."""
62+
63+
prettify_json: bool
64+
"""Whether to pretty-print generated SPDX JSON documents."""
65+
66+
67+
def get_config() -> KernelSbomConfig:
68+
# Parse cli arguments
69+
args = _parse_cli_arguments()
70+
71+
# Extract and validate cli arguments
72+
src_tree = os.path.realpath(args["src_tree"])
73+
output_tree = os.path.realpath(args["output_tree"])
74+
root_paths = []
75+
if args["roots_file"]:
76+
with open(args["roots_file"], "rt") as f:
77+
root_paths = [root.strip() for root in f.readlines()]
78+
else:
79+
root_paths = args["roots"]
80+
_validate_path_arguments(src_tree, output_tree, root_paths)
81+
82+
generate_spdx = args["generate_spdx"]
83+
generate_used_files = args["generate_used_files"]
84+
debug = args["debug"]
85+
86+
spdxId_prefix = args["spdxId_prefix"]
87+
spdxId_uuid = uuid.UUID(args["spdxId_uuid"]) if args["spdxId_uuid"] is not None else uuid.uuid4()
88+
build_type = args["build_type"]
89+
package_license = args["package_license"]
90+
package_version = args["package_version"] if args["package_version"] is not None else None
91+
package_copyright_text: str | None = None
92+
if args["package_copyright_text"] is not None:
93+
package_copyright_text = args["package_copyright_text"]
94+
elif os.path.isfile(copying_path := os.path.join(src_tree, "COPYING")):
95+
with open(copying_path, "r") as f:
96+
package_copyright_text = f.read()
97+
prettify_json = args["prettify_json"]
98+
99+
# Hardcoded config
100+
spdx_file_names = {
101+
KernelSpdxDocumentKind.SOURCE: "sbom-source.spdx.json",
102+
KernelSpdxDocumentKind.BUILD: "sbom-build.spdx.json",
103+
KernelSpdxDocumentKind.OUTPUT: "sbom-output.spdx.json",
104+
}
105+
used_files_file_name = "sbom.used-files.txt"
106+
107+
return KernelSbomConfig(
108+
src_tree=src_tree,
109+
output_tree=output_tree,
110+
root_paths=root_paths,
111+
generate_spdx=generate_spdx,
112+
spdx_file_names=spdx_file_names,
113+
generate_used_files=generate_used_files,
114+
used_files_file_name=used_files_file_name,
115+
debug=debug,
116+
spdxId_prefix=spdxId_prefix,
117+
spdxId_uuid=spdxId_uuid,
118+
build_type=build_type,
119+
package_license=package_license,
120+
package_version=package_version,
121+
package_copyright_text=package_copyright_text,
122+
prettify_json=prettify_json,
123+
)
124+
125+
126+
def _parse_cli_arguments() -> dict[str, Any]:
127+
parser = argparse.ArgumentParser(
128+
formatter_class=argparse.RawTextHelpFormatter,
129+
description="Generate SPDX SBOM from kernel sources and build artifacts",
130+
)
131+
parser.add_argument(
132+
"--src-tree",
133+
default="../linux",
134+
help="Path to the Linux kernel source tree (default: ../linux)",
135+
)
136+
parser.add_argument(
137+
"--output-tree",
138+
default="../linux/kernel_build",
139+
help="Path to the build output tree directory (default: ../linux/kernel_build)",
140+
)
141+
group = parser.add_mutually_exclusive_group(required=True)
142+
group.add_argument(
143+
"--roots",
144+
nargs="+",
145+
default="arch/x86/boot/bzImage",
146+
help="Space-separated list of paths (relative to --output-tree) on which the SBOM will be based. "
147+
"Cannot be used together with --roots-file. (default: arch/x86/boot/bzImage)",
148+
)
149+
group.add_argument(
150+
"--roots-file",
151+
help="Path to a file containing the root paths (one per line). Cannot be used together with --roots.",
152+
)
153+
parser.add_argument(
154+
"--generate-spdx",
155+
action="store_true",
156+
default=False,
157+
help="Whether to create sbom-source.spdx.json, sbom-build.spdx.json and sbom-output.spdx.json documents",
158+
)
159+
parser.add_argument(
160+
"--generate-used-files",
161+
action="store_true",
162+
default=False,
163+
help="Whether to create the sbom.used-files.txt file, a flat list of all source files used for the kernel build. "
164+
"Note, if src-tree and output-tree are equal it is not possible to reliably classify source files. "
165+
"In this case sbom.used-files.txt will contain all files used for the kernel build including all build artifacts. (default: False)",
166+
)
167+
parser.add_argument(
168+
"--debug",
169+
action="store_true",
170+
default=False,
171+
help="Debug level (default: False)",
172+
)
173+
174+
# SPDX specific settings
175+
parser.add_argument(
176+
"--spdxId-prefix",
177+
default="urn:spdx.dev:",
178+
help="The prefix to use for all spdxId properties. (default: urn:spdx.dev:)",
179+
)
180+
parser.add_argument(
181+
"--spdxId-uuid",
182+
default=None,
183+
help="The uuid to use for all spdxId properties to make the SPDX documents reproducible. By default a random uuid is generated.",
184+
)
185+
parser.add_argument(
186+
"--build-type",
187+
default="urn:spdx.dev:Kbuild",
188+
help="The SPDX buildType property to use for all Build elements. (default: urn:spdx.dev:Kbuild)",
189+
)
190+
parser.add_argument(
191+
"--package-license",
192+
default="NOASSERTION",
193+
help="The SPDX licenseExpression property to use for the LicenseExpression linked to all SPDX Package elements. (default: NOASSERTION)",
194+
)
195+
parser.add_argument(
196+
"--package-version",
197+
default=None,
198+
help="The SPDX packageVersion property to use for all SPDX Package elements. (default: None)",
199+
)
200+
parser.add_argument(
201+
"--package-copyright-text",
202+
default=None,
203+
help="The SPDX copyrightText property to use for all SPDX Package elements. If not specified, and if a COPYING file exists in the source tree, the package-copyright-text is set to the content of this file. (default: None)",
204+
)
205+
parser.add_argument(
206+
"--prettify-json",
207+
action="store_true",
208+
default=False,
209+
help="Whether to pretty print the gnerated spdx.json documents (default: False)",
210+
)
211+
212+
args = vars(parser.parse_args())
213+
return args
214+
215+
216+
def _validate_path_arguments(src_tree: PathStr, output_tree: PathStr, root_paths: list[PathStr]) -> None:
217+
if not os.path.exists(src_tree):
218+
raise argparse.ArgumentTypeError(f"--src-tree {src_tree} does not exist")
219+
if not os.path.exists(output_tree):
220+
raise argparse.ArgumentTypeError(f"--output-tree {output_tree} does not exist")
221+
for root_path in root_paths:
222+
if not os.path.exists(os.path.join(output_tree, root_path)):
223+
raise argparse.ArgumentTypeError(
224+
f"path to root artifact {os.path.join(output_tree, root_path)} does not exist"
225+
)

sbom/lib/sbom/environment.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# SPDX-FileCopyrightText: 2025 TNG Technology Consulting GmbH
3+
4+
import os
5+
6+
7+
class Environment:
8+
ARCH = os.getenv("ARCH", None)
9+
SRCARCH = os.getenv("SRCARCH", None)

sbom/lib/sbom/spdx/software.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
SbomType = Literal["source", "build"]
1111
FileKindType = Literal["file", "directory"]
1212
SoftwarePurpose = Literal[
13-
"source", "archive", "library", "file", "data", "configuration", "executable", "module", "other"
13+
"source", "archive", "library", "file", "data", "configuration", "executable", "module", "application", "other"
1414
]
1515
ContentIdentifierType = Literal["gitoid", "swhid"]
1616

sbom/lib/sbom/spdx_graph/kernel_file.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,12 @@ def includes_path_segments(path_segments: list[str]) -> bool:
184184
if ends_with([".xz", ".cpio", ".gz", ".tar", ".zip"]):
185185
return "archive"
186186

187+
# Applications
188+
if ends_with(["bzImage"]):
189+
return "application"
190+
187191
# Executables / machine code
188-
if ends_with([".bin", ".elf", "vmlinux", "bzImage", "vmlinux.unstripped", "bpfilter_umh"]):
192+
if ends_with([".bin", ".elf", "vmlinux", "vmlinux.unstripped", "bpfilter_umh"]):
189193
return "executable"
190194

191195
# Kernel modules

0 commit comments

Comments
 (0)