Mikey Strauss

Making Firmware Verifiable, Not Just Signed

August 10, 2026 supply-chain-securityfirmwarecybersecurityuefiopen-source

Firmware & Supply-Chain Security

My journey into the UEFI firmware supply chain —abandoned dependencies, code you can’t verify — and the open-source tooling I built so firmware can prove itself.

Every server, laptop, phone, switch, and baseboard management controller boots on firmware. It’s the first code that runs and the root of trust everything above it inherits. It is also the supply chain we can vouch for the least.

In cloud-native software we’ve spent a decade building transparency: SBOMs, build provenance, dependency scorecards, keyless signing. Down in firmware, almost none of that is standard. Most firmware ships with no SBOM at all. Many of the components inside a modern image are old, some effectively end-of-life, still riding along in shipping builds with nobody actively maintaining them. And the pieces that do get updated arrive, overwhelmingly, on trust.

Take one real dependency. edk2 (the open-source reference implementation of UEFI, the firmware standard that replaced the legacy PC BIOS, and the base a huge share of real firmware is built from) gets its regular-expression support from a vendored copy of Oniguruma, a full regex engine. Nothing exotic on the surface. But look at the upstream: it’s essentially a single-maintainer project, it carries an OpenSSF Scorecard around 3.9 (no security policy, no code review, no branch protection), and the repository is now archived — read-only, effectively end-of-life. That library is baked into an enormous share of the world’s UEFI firmware. edk2 tracks the latest release, which is the right thing to do. The point is that nothing in the pipeline surfaces any of this. If the next bug lands in Oniguruma, there may be no upstream left to fix it, and nothing to tell you it’s sitting in your boot path.

And Oniguruma isn’t even the worst case. Another edk2 dependency, subhook, lost its upstream entirely: the original repository is gone, and edk2’s own mirror of it is described, verbatim, as a “mirror … that is no longer available.” Firmware still ships it, with nowhere left to get a fix.

edk2 pulls in a dozen third-party projects. Most you can’t vouch for; two are red-flagged, one we fixed.

A small thing that stuck with me

Recently I upstreamed a dependency bump into edk2, the open-source UEFI reference implementation that ends up, directly or downstream, in an enormous share of the world’s firmware. The change was a version bump of libspdm, a security library. Keeping dependencies current is the right instinct: you want the latest fixes, and a stale security library is its own liability. So the change was legitimate, and it went in smoothly, as it should have.

But every dependency bump is also a moment of trust. You’re pulling in code you can’t fully vet, and here’s what stuck with me: at no point in that flow could anyone gauge what was coming in. No SBOM for the dependency, no OpenSSF Scorecard, no provenance trail. In firmware, doing the right thing and having no way to weigh the risk that rides with it are the same moment. If that library had carried a backdoor, the pipeline had no mechanism to notice.

This isn’t an edk2 failing, and it’s not an argument against updating dependencies. It’s the opposite. Maintainers can only weigh what there’s tooling to weigh, and the transparency infrastructure that’s routine one layer up (SBOMs, scorecards, provenance) barely exists down here. So a security library can flow into code that runs on billions of devices, and nothing in the pipeline can say a word about whether to trust it.

That gap, no way to gauge what you pull in and no way to verify what you shipped, is the whole problem. It’s also fixable. The rest of this post is the two steps that matter most, and where each one gets hard.

Step one: an SBOM that actually came from the build

You can’t reason about firmware you can’t enumerate, so step one is boring but foundational: have an SBOM at all. And have one that came from the real build, not one reconstructed afterward by scanning a blob and guessing.

That’s why I’ve been working a CycloneDX SBOM generator into the edk2/OVMF build itself (proposed upstream on the edk2 devel list, with smaller fixes merged into the surrounding ecosystem: edk2 and the uSWID toolchain). An SBOM emitted by the build, from the same inputs that produced the image, is far more trustworthy than one inferred later. It’s the receipt firmware has never had.

But a receipt only tells you what’s supposed to be in the box, not what’s actually in it. That’s where most tooling stops.

Step two: membership is not integrity

Say you now have the SBOM, and you sign the firmware image too. Picture a UEFI image — a stack of PE32 modules, each with a GUID and a role (PEI, DXE, and so on). Two controls guard it:

Now swap one module for a malicious build of the same module. Same GUID. Same name. Same declared version. It slots into the exact place the original occupied.

The membership check passes — the component is “present,” GUID and version match the manifest. The signature passes — the whole tampered image was re-signed by the pipeline that built it. Two green checks, and a trojan on the DXE dispatch path.

An inventory that lists components is not a proof that the shipped code is those components. “Present and correctly named” is not “byte-for-byte what was declared.”

Same-GUID swap: two controls pass; the byte-level reconcile is the one that catches it.

Why you can’t just sha256 the module and be done

The obvious fix — hash every module, compare to the SBOM — runs straight into how firmware is laid out. A module’s bytes at build time are not the bytes sitting in the final image:

So the reconcile has to be format-aware. For the rebased modules you canonicalize: walk the PE structure with something like pefile, reverse the relocation fix-ups, and put the module back into the form it had when its declared hash was generated. Only then is a byte comparison meaningful. On a real OVMF image (the edk2 build that runs UEFI in virtual machines) this got me to 122 of 123 modules reconciled to their declared hashes end-to-end. A same-GUID swap in any of them now fails, loudly, while the signature and the inventory keep saying “fine.”

If that sounds like it only works in a slide deck, the repo ships a make attack-demo that does it for real. It takes a genuine PE32 module, byte-tampers it under its own GUID, runs the actual reconcile, and shows the gate flip to DENY: membership still green, bytes red. You can watch the catch happen rather than take my word for it.

Turning it into a claim you can hand someone

Catching the swap is half of it. The other half is producing evidence a downstream consumer can trust without re-running your whole toolchain, which is the thing the firmware ecosystem has been missing. So every check runs as an independent verifier: byte reconcile, structural checks, policy assertions, 32 in all. Their results AND together into one signed verification summary, an SLSA VSA (verification_summary/v1), anchored to the firmware’s code-region digest so the claim binds to the actual immutable code rather than a filename. It’s emitted as a multi-subject in-toto evidence graph and signed keyless with Sigstore, so there are no long-lived keys to leak. The same checks map to 46 controls across eight frameworks, so one artifact answers a security engineer and an auditor at once.

Here’s what those 32 verifiers actually assert. They group into four clusters that read as one story — is the shipped code what was declared, is the platform configured to defend itself, are the known risks accounted for, and can you trust who built and signed it — and they all converge on a single gate:

An evidence gate that refuses to ship unverified firmware.
The same 32 checks, mapped to 46 controls across eight compliance frameworks

Where it goes next: from the build to the device

Everything above checks the image before it ships — the finished bytes at rest. But the same signed SBOM can check what’s actually on the machine. Using Intel’s CHIPSEC to pull each module out of a shipped image (and, ultimately, straight off the SPI flash), I run the exact same rebase-0 reconcile against the exact same declared hashes. So if a module is swapped after the image was approved — during flashing — it’s caught the same way as a build-time swap: the signature and the inventory still say “fine,” but the bytes no longer match the SBOM’s declared hash, so the deploy-time reconcile denies it. It’s one baseline extended from “what we approved” to “what’s actually running.” This deploy-time leg is newer and I’m still hardening it; reading the bytes off silicon at boot is the step after that.

The takeaway

Firmware is the substrate everything else trusts, and it’s running years behind the rest of the stack on transparency. A dependency can flow into code on billions of devices with no way to gauge it; an image can pass “signed” and “SBOM present” while shipping a trojan the bytes would betray.

The fixes aren’t exotic. One: generate the SBOM inside the build, so firmware finally has a receipt. Two: reconcile the shipped bytes against it, because membership is not integrity. Three: hand it downstream as signed evidence bound to the firmware’s own digest. Until that’s routine, “it booted and it’s signed” will keep reading as “safe” when it isn’t.

The full tool is open source (MIT): the DXE/XIP reconcile, the VSA gate, the make attack-demo, and the fw-supplychain-verify consumer CLI.

→ github.com/houdini91/uefi-supply-chain

Mikey Strauss is a security engineer working on firmware integrity, software supply-chain trust, and low-level systems.

Originally published on Medium.