[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 |
|