[PATCH] [rfc] Add SBOM (software bill of materials) to the efi binaries


Martin Fernandez <martin.fernandez@...>
 

This patch modifies the build system in order to generate and use
metadata to add it to the efi binaries. It uses python-uswid [1],
given a set of config files (.ini) with the metadata it converts
them into the coswid format to then push the result to the .sbom
section of the executable file.

SBOM is increasingly important for everyone (both consumers and
vendors) and firmware is now a critical part of systems. The best
way to embed the SBOM metadata is to include generation as part of
the upstream build process, like we did with coreboot [2].

This looks for an sbom.ini file in the Conf directory, as the top
level metadata, and then it autogenerates a per-module .ini file with
information about the module itself, like name, version, git commit
hash (if applicable), etc.

Another interesting tag that is the "edition" tag. This tag contains a
hash of all the sources involved in the building of a module, in a
sort of versioning of it. We did this because the git commit hash
isn't always enough to trace back. During the building, specifically
during the building of the .obj files, I save in a file the sources
that contributed to that .obj file. Later in the process of the .efi
creation I read those files, and generate a new .ini file containing
only the edition tag to later use it with python-uswid.

This patch is heavily experimental and really looking for comments
and more ideas. This is only built for a x86 linux environment as a
POC. Once the idea is more settled I will add support for the other
systems and architectures. It also depends on having bash and
python-swid installed in the system.

For further reference about firmware SBOM [3].

[1] https://github.com/hughsie/python-uswid
[2] https://review.coreboot.org/c/coreboot/+/63639
[3] https://blogs.gnome.org/hughsie/2022/03/10/firmware-software-bill-of-ma=
terials/

Signed-off-by: Martin Fernandez <martin.fernandez@...>
---
BaseTools/Conf/build_rule.template | 10 +++++
.../Source/Python/AutoGen/AutoGenWorker.py | 1 +
BaseTools/Source/Python/AutoGen/GenCoSWID.py | 41 +++++++++++++++++++
.../Source/Python/AutoGen/ModuleAutoGen.py | 6 +++
BaseTools/Source/Python/build/build.py | 1 +
scripts/edition.sh | 20 +++++++++
6 files changed, 79 insertions(+)
create mode 100644 BaseTools/Source/Python/AutoGen/GenCoSWID.py
create mode 100755 scripts/edition.sh

diff --git a/BaseTools/Conf/build_rule.template b/BaseTools/Conf/build_rule=
.template
index 5895b48fd8..080111fcf2 100755
--- a/BaseTools/Conf/build_rule.template
+++ b/BaseTools/Conf/build_rule.template
@@ -130,6 +130,7 @@
=0D
<Command.GCC>=0D
"$(CC)" $(DEPS_FLAGS) $(CC_FLAGS) -c -o ${dst} $(INC) ${src}=0D
+ echo ${src} >> $(OUTPUT_DIR)(+)${s_dir}(+)source_files.lst=0D
=0D
<Command.XCODE>=0D
"$(CC)" $(DEPS_FLAGS) $(CC_FLAGS) -o ${dst} $(INC) ${src}=0D
@@ -152,8 +153,10 @@
*.h, *.H=0D
=0D
<OutputFile>=0D
+ $(OUTPUT_DIR)(+)header_files.lst=0D
=0D
<Command>=0D
+ echo ${src} >> ${dst}=0D
=0D
[Assembly-Code-File.COMMON.COMMON]=0D
<InputFile.MSFT, InputFile.INTEL>=0D
@@ -173,6 +176,7 @@
"$(PP)" $(DEPS_FLAGS) $(PP_FLAGS) $(INC) ${src} > ${d_path}(+)${s_=
base}.ii=0D
Trim --source-code --convert-hex --trim-long -o ${d_path}(+)${s_ba=
se}.iiii ${d_path}(+)${s_base}.ii=0D
"$(ASM)" /Fo${dst} $(ASM_FLAGS) /I${s_path} $(INC) ${d_path}(+)${s=
_base}.iiii=0D
+ echo ${src} >> $(OUTPUT_DIR)(+)${s_dir}(+)source_files.lst=0D
=0D
<Command.GCC>=0D
Trim --asm-file -o ${d_path}(+)${s_base}.i -i $(INC_LIST) ${src}=0D
@@ -227,6 +231,7 @@
"$(PP)" $(DEPS_FLAGS) $(PP_FLAGS) $(INC) ${src} > ${d_path}(+)${s_=
base}.ii=0D
Trim --trim-long --source-code -o ${d_path}(+)${s_base}.iii ${d_pa=
th}(+)${s_base}.ii=0D
"$(NASM)" -I${s_path}(+) $(NASM_INC) $(NASM_FLAGS) -o $dst ${d_pat=
h}(+)${s_base}.iii=0D
+ echo ${src} >> $(OUTPUT_DIR)(+)${s_dir}(+)source_files.lst=0D
=0D
[Device-Tree-Source-File]=0D
<InputFile>=0D
@@ -340,6 +345,9 @@
<InputFile>=0D
?.dll=0D
=0D
+ <ExtraDependency>=0D
+ $(OUTPUT_DIR)(+)header_files.lst=0D
+=0D
<OutputFile>=0D
$(OUTPUT_DIR)(+)$(MODULE_NAME).efi=0D
$(DEBUG_DIR)(+)$(MODULE_NAME).efi=0D
@@ -362,6 +370,8 @@
-$(CP) $(DEBUG_DIR)(+)$(MODULE_NAME).debug $(BIN_DIR)(+)$(MODULE_N=
AME_GUID).debug=0D
=0D
"$(GENFW)" -e $(MODULE_TYPE) -o ${dst} ${src} $(GENFW_FLAGS)=0D
+ bash $(WORKSPACE)(+)scripts(+)edition.sh $(OUTPUT_DIR) $(MODULE_NAME)=0D
+ uswid --verbose --load $(WORKSPACE)(+)Conf(+)sbom.ini --load $(MODULE_NAM=
E).ini --load $(OUTPUT_DIR)(+)$(MODULE_NAME).ini --save ${dst}=0D
$(CP) ${dst} $(DEBUG_DIR)=0D
$(CP) ${dst} $(BIN_DIR)(+)$(MODULE_NAME_GUID).efi=0D
-$(CP) $(DEBUG_DIR)(+)*.map $(OUTPUT_DIR)=0D
diff --git a/BaseTools/Source/Python/AutoGen/AutoGenWorker.py b/BaseTools/S=
ource/Python/AutoGen/AutoGenWorker.py
index 0ba2339bed..4697dcfb0c 100755
--- a/BaseTools/Source/Python/AutoGen/AutoGenWorker.py
+++ b/BaseTools/Source/Python/AutoGen/AutoGenWorker.py
@@ -282,6 +282,7 @@ class AutoGenWorkerInProcess(mp.Process):
=0D
Ma.CreateCodeFile(False)=0D
Ma.CreateMakeFile(False,GenFfsList=3DFfsCmd.get((Ma.MetaFi=
le.Path, Ma.Arch),[]))=0D
+ Ma.CreateCoSWIDFile()=0D
Ma.CreateAsBuiltInf()=0D
if GlobalData.gBinCacheSource and CommandTarget in [None, =
"", "all"]:=0D
try:=0D
diff --git a/BaseTools/Source/Python/AutoGen/GenCoSWID.py b/BaseTools/Sourc=
e/Python/AutoGen/GenCoSWID.py
new file mode 100644
index 0000000000..65dec0c773
--- /dev/null
+++ b/BaseTools/Source/Python/AutoGen/GenCoSWID.py
@@ -0,0 +1,41 @@
+from os import path
+import subprocess
+
+
+class ModuleCoSWID(object):
+ '''Create a per-module ini file with SBOM related data
+
+ Also try to get the git HEAD commit hash if the project is
+ versioned by git
+ '''
+ def __init__(self, ModuleAutoGen):
+ self.Name =3D ModuleAutoGen.Name
+ self.Guid =3D ModuleAutoGen.Guid
+ self.Version =3D ModuleAutoGen.Version
+
+ self.colloquial_version =3D None
+ try:
+ process =3D subprocess.run(["git", "rev-parse", "HEAD"],
+ capture_output=3DTrue,
+ check=3DTrue)
+ self.colloquial_version =3D process.stdout.decode("utf-8").str=
ip()
+ # If either git is not in the system, the tree is not in a
+ # git repo or anything else just don't fail
+ except:
+ pass
+
+ self.FilePath =3D path.join(ModuleAutoGen.BuildDir, self.Name + ".=
ini")
+
+ self.content =3D None
+
+ def Generate(self):
+ self.content =3D "\n".join([
+ "[uSWID]",
+ f"tag-id =3D {self.Guid}",
+ f"software-name =3D {self.Name}",
+ f"software-version =3D {self.Version}",
+ ""
+ ])
+
+ if self.colloquial_version is not None:
+ self.content +=3D f"colloquial-version =3D {self.colloquial_ve=
rsion}\n"
diff --git a/BaseTools/Source/Python/AutoGen/ModuleAutoGen.py b/BaseTools/S=
ource/Python/AutoGen/ModuleAutoGen.py
index d05410b329..6588322e78 100755
--- a/BaseTools/Source/Python/AutoGen/ModuleAutoGen.py
+++ b/BaseTools/Source/Python/AutoGen/ModuleAutoGen.py
@@ -20,6 +20,7 @@ from . import InfSectionParser
from . import GenC=0D
from . import GenMake=0D
from . import GenDepex=0D
+from . import GenCoSWID=0D
from io import BytesIO=0D
from GenPatchPcdTable.GenPatchPcdTable import parsePcdInfoFromMapFile=0D
from Workspace.MetaFileCommentParser import UsageList=0D
@@ -1784,6 +1785,11 @@ class ModuleAutoGen(AutoGen):
FilePath =3D path.join(self.BuildDir, self.Name + ".makefile")=0D
SaveFileOnChange(FilePath, MakefilePath, False)=0D
=0D
+ def CreateCoSWIDFile(self):=0D
+ coSWID =3D GenCoSWID.ModuleCoSWID(self)=0D
+ coSWID.Generate()=0D
+ SaveFileOnChange(coSWID.FilePath, coSWID.content, False)=0D
+=0D
def CopyBinaryFiles(self):=0D
for File in self.Module.Binaries:=0D
SrcPath =3D File.Path=0D
diff --git a/BaseTools/Source/Python/build/build.py b/BaseTools/Source/Pyth=
on/build/build.py
index 07187c0361..a80363acba 100755
--- a/BaseTools/Source/Python/build/build.py
+++ b/BaseTools/Source/Python/build/build.py
@@ -870,6 +870,7 @@ class Build():
=0D
PcdMa.CreateCodeFile(False)=0D
PcdMa.CreateMakeFile(False,GenFfsList =3D DataPipe.Get=
("FfsCommand").get((PcdMa.MetaFile.Path, PcdMa.Arch),[]))=0D
+ PcdMa.CreateCoSWIDFile()=0D
PcdMa.CreateAsBuiltInf()=0D
# Force cache miss for PCD driver=0D
if GlobalData.gBinCacheSource and self.Target in [None=
, "", "all"]:=0D
diff --git a/scripts/edition.sh b/scripts/edition.sh
new file mode 100755
index 0000000000..00de646a6f
--- /dev/null
+++ b/scripts/edition.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -xe -o pipefail
+
+output_dir=3D"${1:?}"
+module_name=3D"${2:?}"
+
+edition=3D"$(cat "${output_dir}/source_files.lst" "${output_dir}/header_fi=
les.lst" | \
+ tr ' ' '\n' | \
+ sort -u | \
+ tee "${module_name}.files" | \
+ xargs cat | \
+ sha256sum | \
+ cut -d' ' -f 1)"
+
+cat <<EOF > "${output_dir}/${module_name}.ini"
+[uSWID]
+edition =3D ${edition}
+EOF
+
--=20
2.30.2

Join {rfc@edk2.groups.io to automatically receive all group messages.