You might be surprised to hear that closed source firmware typically contains open source dependencies. In the case of EDK II (probably the BIOS of your x64 machine you’re using now) it’s about 20 different projects, and in the case of coreboot (hopefully the firmware of the machine you’ll own in the future) it’s about another 10 — some overlapping with EDK II. Examples here would be things like libjpeg (for the OEM splash image) or libssl (for crypto, but only the good kind).
It makes no sense for each person building firmware to write the same SBOM for the OSS code. Moving the SBOM upstream means it can be kept up to date by the same team writing the open source code. It’s very similar to what we encouraged desktop application developers to do with AppStream metadata a decade or so ago. That was wildly successful, so maybe we can do the same trick again here.
My proposal would to submit a sbom.cdx.json
to each upstream project in CycloneDX format, stored in a location amenable to the project — e.g. in ./contrib
, ./data/sbom
or even in the root project folder. The location isn’t important, only the file suffix needs to be predictable.
Notice the CycloneDX word there not SPDX — the latter is great for open source license compliance, but I was only able to encode 43% of our “example firmware SBOM” into SPDX format, even with a lot of ugly hacks. I spent a long time trying to jam a round peg in a square hole and came to the conclusion it’s not going to work very well. SPDX works great as an export format to ensure license compliance (and the uswid
CLI can already do that now…) but SPDX doesn’t work very well as a data source. CycloneDX is just a better designed format for a SBOM, sorry ISO.
Let’s assume we check in a new file to ~30 projects. With my upstream-maintainer hat on, nobody likes to manually edit yet-another-file when tagging releases, so I’m encouraging projects shipping a CycloneDX sbom.cdx.json
to use some of the auto-substituted tokens, e.g.
@VCS_TAG@
→git describe --tags --abbrev=0
e.g.1.2.3
@VCS_VERSION@
→git describe --tags
e.g.1.2.3-250-gfa2371946
@VCS_BRANCH@
→git rev-parse --abbrev-ref HEAD
e.g.staging
@VCS_COMMIT@
→git rev-parse HEAD
e.g.3090e61ee3452c0478860747de057c0269bfb7b6
@VCS_SBOM_AUTHORS@
→git shortlog -n -s -- sbom.cdx.json
e.g.Example User, Another User
@VCS_SBOM_AUTHOR@
→@VCS_SBOM_AUTHORS@[0]
e.g.Example User
@VCS_AUTHORS@
→git shortlog -n -s
e.g.Example User, Another User
@VCS_AUTHOR@
→@VCS_AUTHORS@[0]
e.g.Example User
Using git in this way during the built process allows us to also “fixup” SBOM files with either missing details, or when the downstream ODM patches the project to do something upstream wouldn’t be happy with shipping upstream.
For fwupd (which I’m using as a cute example, it’s not built into firmware…) the sbom.cdx.json
file would be something like this:
{ "bomFormat": "CycloneDX", "specVersion": "1.6", "version": 1, "metadata": { "authors": [ { "name": "@VCS_SBOM_AUTHORS@" } ] }, "components": [ { "type": "library", "bom-ref": "pkg:github/fwupd/fwupd@@VCS_TAG@", "cpe": "cpe:2.3:a:fwupd:fwupd:@VCS_TAG@:*:*:*:*:*:*:*", "name": "fwupd", "version": "@VCS_VERSION@", "description": "Firmware update daemon", "supplier": { "name": "fwupd developers", "url": [ "https://github.com/fwupd/fwupd/blob/main/MAINTAINERS" ] }, "licenses": [ { "license": { "id": "LGPL-2.1-or-later" } } ], "externalReferences": [ { "type": "website", "url": "https://fwupd.org/" }, { "type": "vcs", "url": "https://github.com/fwupd/fwupd" } ] } ] }
Putting it all together means we can do some pretty clever things assuming we have a recursive git checkout using either git modules, sub-modules or sub-projects:
$ uswid --find ~/Code/fwupd --fixup --save sbom.cdx.json --verbose Found: - ~/Code/fwupd/contrib/sbom.cdx.json - ~/Code/fwupd/venv/build/contrib/sbom.cdx.json - ~/Code/fwupd/subprojects/libjcat/contrib/spdx.json Substitution required in ~/Code/fwupd/contrib/sbom.cdx.json: - @VCS_TAG@ → 2.0.1 - @VCS_VERSION@ → 2.0.1-253-gd27804fbb Fixup required in ~/Code/fwupd/subprojects/libjcat/spdx.json: - Add VCS commit → db8822a01af89aa65a8d29c7110cc86d78a5d2b3 Additional dependencies added: - pkg:github/hughsie/libjcat@0.2.1 → pkg:github/hughsie/libxmlb@0.2.1 - pkg:github/fwupd/fwupd@2.0.1 → pkg:github/hughsie/libjcat@0.2.1 ~/Code/fwupd/venv/build/contrib/sbom.cdx.json was merged into existing component pkg:github/fwupd/fwupd@2.0.1
And then we have a sbom.cdx.json
that we can use as an input file used for building the firmware blob. If we can convince EDK2 to merge the additional sbom.cdx.json
for each built module then it all works like magic, and we can build the 100% accurate external SBOM into the firmware binary itself with no additional work. Comments most welcome.