Van CMake-build naar SBOM: Trusted Firmware-M op STM32H5
| Ritesh Noronha

We hebben onlangs met lynkctl CycloneDX-SBOM's gegenereerd voor een echte Trusted-Firmware-M-build op een STM32H5-board. De build maakt een veelvoorkomend probleem van embedded firmware concreet: de meeste SBOM's beschrijven wat er in een repository aanwezig is, niet wat er daadwerkelijk in het image wordt geleverd.
Gebouwd is niet geleverd
In een embedded project kan de broncodeboom vendor-SDK's, board support packages, RTOS-ports, HAL-drivers, cryptobibliotheken, netwerkstacks, testhulpmiddelen, gegenereerde bestanden, voorbeeldapplicaties en oude compatibiliteitscode bevatten. Een CMake-target verwijst slechts naar een deel daarvan. De compiler bouwt minder dan de boom bevat. De linker houdt minder over dan de compiler heeft geproduceerd.
Voor een CMake-gebaseerd embedded project denk ik in vier lagen:
Code die in de repository aanwezig is.
Code waarnaar de gekozen CMake-configuratie verwijst.
Code die tot objecten en archieven wordt gecompileerd.
Code die daadwerkelijk bijdraagt aan het uiteindelijke firmware-artefact.
De meeste false positives ontstaan doordat deze lagen tot één antwoord worden samengevoegd.
Wanneer een kwetsbaarheidsscanner een product markeert omdat er een kwetsbare component bestaat in een gedeelde third_party-map, heeft het securityteam nu werk te doen. Iemand moet aantonen of die code voor dit apparaat bereikbaar, gecompileerd, gelinkt of volledig ongebruikt is. Op vlootschaal worden die false positives duur.
Het omgekeerde gebeurt ook. Een build kan een statisch archief, een toolchain-runtime, een gegenereerd bronbestand of een door de leverancier aangeleverd object binnenhalen dat nooit in een pakketmanifest verschijnt. Als de SBOM alleen manifesten of voor de hand liggende mappen scant, kan hij dingen missen die wél degelijk worden geleverd.
Voor embedded werk betekent nauwkeurigheid niet simpelweg meer componenten. Nauwkeurigheid betekent weten welk bewijs elke component onderbouwt.
Waarom broncode-scanning niet volstaat voor een CMake-build
Het scannen van de broncodeboom is het gebruikelijke startpunt. Tools doorlopen de repository, vinden pakketmanifesten en meegeleverde broncode en produceren CycloneDX of SPDX. De zwakte is niet dat scannen slecht is. Scannen ziet aanwezigheid, niet levering. Als third_party/mbedtls in de boom bestaat, is dat nuttig om te weten. Het betekent niet dat mbedTLS voor dit board is gecompileerd, in deze firmware is gelinkt of na dead-code-eliminatie behouden is gebleven.
Voor een CMake-project weet de build al meer dan het bestandssysteem: de gekozen configuratie, de target-graaf, compileervlaggen, include-paden, link-invoer en artefacten. Het open source-project cmake-sbom neemt dat serieus. Het integreert met CMake en genereert SPDX uit install-artefacten en expliciete sbom_add-declaraties. De afweging is dat het afhangt van wat het project expliciet declareert – en TF-M declareert zichzelf niet op die manier. Het erft vendor-CMake, STMicro-board-scripts, gegenereerde bestanden en statische archieven die in de build-boom worden opgehaald, geen daarvan ontworpen voor SBOM-generatie.
Precies voor dat geval is lynkctl gebouwd.
De build: Trusted Firmware-M op STM32H573I-DK
Trusted Firmware-M, meestal geschreven als TF-M, is de referentie-implementatie van de Secure Processing Environment voor Arm Cortex-M-apparaten. Het is gebouwd rond het Armv8-M-beveiligingsmodel, inclusief TrustZone waar de hardware dat ondersteunt.
TF-M biedt:
Secure boot voor het authenticeren van firmware-images.
Een veilige runtime-kern die isolatie en communicatie beheert.
Veilige diensten zoals crypto, Internal Trusted Storage, Protected Storage, firmware-update en Initial Attestation.
PSA Functional API-interfaces waarmee niet-veilige applicaties veilige diensten kunnen aanroepen.
Het is geen enkele platte applicatie. Het is een geconfigureerd firmwareplatform met bootloadercode, veilige partities, vendor-platformondersteuning, gegenereerde bestanden, bibliotheken van derden en linker-uitvoer.

Het target voor deze run was het ontwikkelkit STM32H573I-DK:
In de platform-metadata van TF-M is het geconfigureerd als:
Het platform schakelt TrustZone in en gebruikt MCUboot als bootloaderpad. Het schakelt ook de veilige diensten in die van belang zijn voor een representatief image: Crypto, Internal Trusted Storage, Protected Storage, Initial Attestation, de Platform-partitie en Firmware Update.
De build produceert meer dan één firmware-artefact: een bootloader-image en een veilig TF-M-image, elk met een eigen linker-map:
Dat onderscheid is van belang voor SBOM-generatie. Een map-bestand is image-specifiek. Als we dead-code-reconciliatie en nauwkeurigheid op het niveau van het gelinkte image willen, zijn de bootloader en de veilige firmware twee SBOM-onderwerpen, niet één.
Bouw eerst de firmware, genereer daarna
De SBOM-run vindt plaats na het configureren en bouwen van de firmware. Dat geeft lynkctl echte build-metadata in plaats van hem te vragen alleen uit de broncode af te leiden.
We gebruikten de GNU Arm embedded-toolchain:
Twee SBOM's genereren
Elk gelinkt image heeft zijn eigen map-bestand, dus genereerden we twee SBOM's:
Zo blijft het SBOM-onderwerp uitgelijnd met het binaire onderwerp. De bootloader en de veilige runtime zijn niet identiek: ze delen een deel van de build-omgeving en het afhankelijkheidsbewijs, maar elk image heeft zijn eigen target-graaf, gelinkte inhoud en runtime-rol. Ze combineren tot één map-aangestuurde SBOM zou die grens vervagen.
Deze commando's werden uitgevoerd vanuit de TF-M-bron-root, met --project-root ingesteld om de bronpaden verankerd te houden aan de TF-M-root. We gebruikten de CMake-provider expliciet, gaven de geconfigureerde build-map door en gaven expliciete --vendored-root-waarden door. Dat is hier belangrijk omdat verschillende afhankelijkheden in de build-boom worden opgehaald onder build/stm32h5-gnu-brew-newlib/lib/ext/*-src. Die roots vormen de pakketgrenzen voor MCUboot, TF-PSA-Crypto, CMSIS_6, QCBOR en t_cose.
Voor de bootloader:
Voor het veilige firmware-image:

Resultaten
De bootloader-SBOM werd voltooid met 10 componenten:
De SBOM van de veilige firmware werd voltooid met 20 componenten:
Beide zijn CycloneDX 1.6 JSON-documenten. De waarschuwingen zijn OSS-index-grenswaarschuwingen voor content-range- of alias-overeenkomsten zoals tinycrypt, mbedtls, tf-psa-crypto, mbedtls-framework en cddl_gen. De component-identiteiten werden desondanks verbeterd door de expliciete vendored-roots en de verrijking uit de git-checkout. Dit is een gebied voor diagnostische opschoning, geen blokkade voor de SBOM-inhoud.

De componenteninventaris
Na het genereren van de SBOM's hebben we de componentenlijst uit elk CycloneDX-document geïnspecteerd. De volledige SBOM's bevatten de complete inventaris en de bewijs-annotaties; voor het artikel zijn de belangrijke items de open source- en runtime-componenten die review en triage beïnvloeden.
Component | Gezien in | Waarom het van belang is |
|---|---|---|
TF-PSA-Crypto |
| Crypto-implementatie gebruikt door de bootloader en het veilige image. |
MCUboot |
| Bootloadercode en firmware-updatepad. |
CMSIS_6 |
| Arm Cortex-M-ondersteuningslaag gebruikt door de platform-build. |
STM32H5 HAL driver |
| STMicro-platformdrivercode voor de gekozen board-familie. |
CMSIS Device H5 |
| STM32H5-apparaatondersteuning achter de platform-targets. |
QCBOR |
| CBOR-codering gebruikt door attestation-gerelateerde code van de veilige firmware. |
t_cose |
| COSE-ondersteuning gebruikt door attestation-gerelateerde code van de veilige firmware. |
GCC-runtime-ondersteuning ( |
| Runtime-archief dat in het firmware-image wordt gelinkt, onderscheiden van het uitvoerbare compilerbestand. |
De volledige inventarissen bevatten ook first-party TF-M-targets zoals tfm_spm, tfm_sprt en de veilige partities. Die rijen zijn nuttig in de SBOM omdat ze build-bewijs op targetniveau bewaren, maar ze hoeven hier niet volledig te worden weergegeven. Buildtools zoals cmake, ninja en de compiler-frontends zijn gemarkeerd als excluded in plaats van meegeteld als gelinkte runtime-firmware-inhoud.

Wat deze build ons vertelt
De bootloader en de veilige runtime zijn verschillende onderwerpen. bl2 linkt MCUboots bootutil, een crypto-deel als bl2_crypto en het STM32H5-platform als platform_bl2. tfm_s linkt een bredere set: de veilige partities (tfm_psa_rot_partition_*), de SPM- en SPRT-runtime, QCBOR en t_cose voor attestation-codering, en een ander crypto-service-target. Eén samengevoegde SBOM zou dat verschil hebben verborgen.
Buildtools zijn gelabeld als excluded. cmake, ninja en de twee arm-none-eabi-compiler-frontends werden gedetecteerd, maar ze zijn geen gelinkte firmware-inhoud, dus ze worden als buiten scope gemarkeerd in plaats van meegeteld als geleverd. Het ene toolchain-item dat wél wordt geleverd, gcc als required, staat er omdat libgcc.a in het image wordt gelinkt. Dat is het onderscheid dat telt tijdens triage: een scanner die "compiler gevonden" op dezelfde manier behandelt als "compiler geleverd", produceert ruis.
De vendored-roots gaven echte pakket-identiteit. TF-PSA-Crypto, MCUboot, CMSIS_6, QCBOR en t_cose werden opgelost tot GitHub-purls met commit-niveau-versies, omdat we de verrijkingspijplijn hebben verteld waar hun pakketgrenzen in de opgehaalde build-boom lagen. Zonder dat verschijnt dezelfde code als anonieme meegeleverde broncode.
Het STMicro-platform verschijnt als echte inhoud. Dit board stelt CRYPTO_HW_ACCELERATOR_TYPE=stm in, en die keuze is zichtbaar in tfm_s als het target crypto_service_crypto_hw: het veilige image is bedraad naar de STM32H5-hardwarecrypto, niet naar een puur softwarepad. De rest van de STMicro-laag lost op tot geversioneerde STMicro-repo's, de stm32h5xx_hal_driver en de cmsis_device_h5-apparaatondersteuning achter platform_bl2 en platform_s. Bij een STM32-build is dat precies de code waar een klant of auditor naar zal vragen, dus die moet een echte identiteit dragen in plaats van in een naamloze board-map te zitten.
Gegenereerde code is lastig. Vendor-blobs zijn lastig. Gepatchte meegeleverde bomen zijn lastig. Propriëtaire toolchain-bibliotheken zijn lastig. LTO kan grenzen versluieren. Verouderde build-mappen kunnen liegen. Linker-maps verschillen per toolchain.
Daarom is diagnostiek van belang. Een nuttige SBOM-generator zou moeten aangeven wanneer bewijs ontbreekt of zwak is, en onderscheid maken tussen wat hij heeft bevestigd en wat hij heeft afgeleid. De OSS-index-waarschuwingen in deze run zijn een voorbeeld van die eerlijkheid: de tool markeerde waar een content-overeenkomst vaag was in plaats van een schone identiteit te beweren die hij niet had. Een stille, overmoedige SBOM is slechter dan een luidruchtige maar eerlijke.
Het doel is verdedigbaar bewijs. Voor CMake-gebaseerde embedded firmware begint de beste SBOM met de build, wordt gecorrigeerd door linker-bewijs en pas daarna verrijkt, wanneer dat fundament solide is.
Wilt u de daadwerkelijke SBOM's?
De twee CycloneDX 1.6-documenten uit deze run, stm32h5-bl2.cdx.json en stm32h5-tfm_s.cdx.json, zijn op aanvraag beschikbaar. Om de echte componentenlijsten, bewijs-annotaties en scope-beslissingen te bekijken, vraag de SBOM's hier aan of e-mail ons op hello@interlynk.io.