You Can't Patch What You Don't Know You Shipped.

A firmware image is a bill of materials you assembled and then forgot. When a vulnerability lands in a component three layers down, the question is whether you can even find it in your fleet.

Field Notes · 7 min read

Ask a firmware team what is in the image they shipped and you will often get a list of what the team wrote: the application logic, the drivers they touched, the configuration. That is the smaller part of the binary. The larger part arrived as a directory of source or a precompiled library from somewhere else, and nobody could recite its version without opening the build tree.

That gap is the real subject. A vulnerability disclosed in a component three layers down is not a crisis because the bug is clever. It is a crisis because the first question — which of our products contain it, in which version, on how many devices — has no quick answer. A software bill of materials can supply one, but only if it is kept and queried like an operational record rather than filed like a compliance receipt.

Firmware is assembled, not written

A modern embedded image is closer to integration than to authorship. The silicon vendor supplies a hardware abstraction layer and a board support package; an RTOS provides the kernel; a network stack, a TLS library, a file system, a USB stack, and a bootloader each arrive from a different origin, alongside small utilities that do heavy lifting, such as compression and JSON or protobuf parsing. The product's own code sits on top. Together they form a supply chain nobody drew.

Two things make this harder than in server software. The build rarely runs through a package manager that records what it resolved: components arrive as archives, vendor installers, submodules, or files pasted into the repository. And the output is one statically linked binary, so a component's identity is erased at link time. Nothing on the running device lists its parts the way a package database does. If the inventory was not captured at build time, it does not exist.

The transitive dependency problem

Declared dependencies are the ones you chose; transitive dependencies are the ones your choices chose for you. A TLS library brings a certificate parser, big-number arithmetic, and often its own hash and cipher primitives. A TCP/IP stack carries DNS and DHCP clients. A vendor BSP routinely bundles its own, frequently modified, copies of third-party libraries so the SDK builds out of the box. The same upstream parser can appear twice in one image: once through the library you selected, once vendored inside the BSP you never examined.

Shipped firmware imageRTOS kernelTCP/IP stackTLS libraryBootloaderVendor BSPDNS / DHCPX.509 parserVendored copyblast radius: the whole imagevulnerable componentsame code, vendored
One vulnerable parser, reachable by two paths, puts the entire shipped image in scope.

Vendoring is the sharpest edge. A library copied into the tree and edited stops being a dependency and becomes your source code, with an upstream you have lost track of. Version strings compound the problem: a supplier backports a fix and leaves the version untouched, or ships a fork described as a release plus local changes, so matching on version alone yields false alarms and false confidence. Record the origin, the patches applied, and a hash of what was compiled, and keep the relationship graph: the path tells you whether a fix means updating one library or chasing a copy buried in someone else's SDK.

What an SBOM format captures

SPDX and CycloneDX are the two formats in wide use. Both are machine-readable, and both describe components with names, versions, suppliers, hashes, licenses, and — most important here — the relationships between them. SPDX grew out of license and provenance tracking; CycloneDX was designed with security use cases in mind. Either can now carry what a vulnerability workflow needs, so choose on tooling, not allegiance.

More important than the format is what the document claims to describe. An SBOM generated from the source tree describes what you intended to build; one produced during the build, what the build resolved; one derived from the final binary, what shipped. They can disagree, and in an incident you trust the one bound to the release artifact by a hash. The fields that earn their keep are a stable identifier such as a package URL or CPE name, an exact version, a hash, the relationships, and each component's provenance.

VEX, the vulnerability exploitability exchange, is the companion statement. For a given vulnerability and product it says whether the product is affected, not affected, fixed, or under investigation, and for not-affected it records why. In embedded this matters because many advisories concern code you compiled out, disabled by configuration, or never reach. VEX lets you say so with a reason instead of patching everything or ignoring everything.

An SBOM nobody queries is a checkbox

Regulation has made the document hard to avoid. The EU Cyber Resilience Act places obligations on manufacturers of products with digital elements, among them documenting components in a machine-readable form and handling vulnerabilities across the support period. That pressure is useful, and it carries a trap: an SBOM produced once at release and archived satisfies a form and nothing else. The ways it fails when finally needed are consistent.

  • Generated once, never refreshed — it describes the first release while the fleet runs later builds.
  • Top-level only — the components you chose are listed; the transitive and vendored layers, where the affected code often sits, are absent.
  • Names without identifiers — an entry that says only "TLS library" cannot be matched to an advisory.
  • Not bound to the artifact — no hash ties the document to the binary that shipped, so you cannot prove which devices it describes.
  • Stored where nobody can search it — a file in a release folder is documentation; a queryable store is a capability.
  • Supplier black boxes — precompiled vendor code arrives with no bill of materials, leaving a hole that scanning your own source can never fill.
An SBOM is not a document you hand to an auditor. It is an index you will query at the worst possible moment, and the only measure of its quality is how quickly and how truthfully it answers.

From component to fielded device

The chain that decides your response runs from advisory to component, component to release, release to device, and device to update capability, and each link needs data collected in advance. Advisories must map to identifiers and version ranges your SBOM records. Releases need an SBOM keyed by the hash of the shipped artifact. Devices need an inventory built from what each unit reports it is running, not from what the order system says was sent. And you must know whether a unit can receive an update at all, and over which channel.

That last link is the one teams skip, and it shapes the response. A vulnerable component in a fleet that can update is a release-engineering task: patch, test, sign, roll out, verify. In a segment that cannot be updated — sealed, offline, or built without a working update path — it is a risk-acceptance decision backed by compensating controls, best reached deliberately and early rather than mid-incident. Devices also tend to outlive the support windows of the components inside them; an SBOM that records support status shows a component going unmaintained before an advisory forces the question.

Making it operational

None of this needs new technology, only the discipline you already give the signed image. Generate the SBOM in CI from the build and from analysis of the final binary, not by hand; sign it; store it beside the image it describes for every release you still support. Pin input versions and hashes so it stays true, and refuse unidentified components: a file with no known origin should fail the build or carry a recorded waiver.

Ask suppliers of precompiled code for their own bills of materials, and treat a refusal as a finding. Run an advisory matcher continuously against the store, and record each outcome — affected, not affected with a reason, fixed in a given release — as VEX so the answer is reusable. Then rehearse: take a public advisory against a class of component you use, and time how long it takes to list the affected devices and the subset that can be patched. That duration, not the document, is your real capability.

An SBOM earns its keep at the seams: whether it matches the binary that shipped, whether the transitive and vendored layers appear, whether the fleet inventory is real, and whether a patch can reach the devices that need it. When 6QURE reviews an embedded supply-chain posture, that is where the hours go — following one component from the moment an advisory names it to the moment a fielded device either runs the fix or is consciously accepted as exposed.

Facing something like this in your own environment?