Code to Image SBOM

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:

  1. Code die in de repository aanwezig is.

  2. Code waarnaar de gekozen CMake-configuratie verwijst.

  3. Code die tot objecten en archieven wordt gecompileerd.

  4. 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:

TFM_PLATFORM=stm/stm32h573i_dk
TFM_PLATFORM=stm/stm32h573i_dk
TFM_PLATFORM=stm/stm32h573i_dk

In de platform-metadata van TF-M is het geconfigureerd als:

TFM_SYSTEM_PROCESSOR=cortex-m33
TFM_SYSTEM_ARCHITECTURE=armv8-m.main
STM32H573xx
CRYPTO_HW_ACCELERATOR_TYPE=stm
TFM_SYSTEM_PROCESSOR=cortex-m33
TFM_SYSTEM_ARCHITECTURE=armv8-m.main
STM32H573xx
CRYPTO_HW_ACCELERATOR_TYPE=stm
TFM_SYSTEM_PROCESSOR=cortex-m33
TFM_SYSTEM_ARCHITECTURE=armv8-m.main
STM32H573xx
CRYPTO_HW_ACCELERATOR_TYPE=stm

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:

build/stm32h5-gnu-brew-newlib/bin/bl2.axf
build/stm32h5-gnu-brew-newlib/bin/tfm_s.axf
build/stm32h5-gnu-brew-newlib/bin/bl2.map
build/stm32h5-gnu-brew-newlib/bin/tfm_s.map
build/stm32h5-gnu-brew-newlib/bin/bl2.axf
build/stm32h5-gnu-brew-newlib/bin/tfm_s.axf
build/stm32h5-gnu-brew-newlib/bin/bl2.map
build/stm32h5-gnu-brew-newlib/bin/tfm_s.map
build/stm32h5-gnu-brew-newlib/bin/bl2.axf
build/stm32h5-gnu-brew-newlib/bin/tfm_s.axf
build/stm32h5-gnu-brew-newlib/bin/bl2.map
build/stm32h5-gnu-brew-newlib/bin/tfm_s.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:

TFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake
CROSS_COMPILE=arm-none-eabi
TFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake
CROSS_COMPILE=arm-none-eabi
TFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake
CROSS_COMPILE=arm-none-eabi
cmake -S . -B build/stm32h5-gnu-brew-newlib -GNinja \
  -DTFM_PLATFORM=stm/stm32h573i_dk \
  -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \
  -DCROSS_COMPILE=arm-none-eabi

ninja -C
cmake -S . -B build/stm32h5-gnu-brew-newlib -GNinja \
  -DTFM_PLATFORM=stm/stm32h573i_dk \
  -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \
  -DCROSS_COMPILE=arm-none-eabi

ninja -C
cmake -S . -B build/stm32h5-gnu-brew-newlib -GNinja \
  -DTFM_PLATFORM=stm/stm32h573i_dk \
  -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \
  -DCROSS_COMPILE=arm-none-eabi

ninja -C


Twee SBOM's genereren

Elk gelinkt image heeft zijn eigen map-bestand, dus genereerden we twee SBOM's:

build/sboms/stm32h5-bl2.cdx.json
build/sboms/stm32h5-tfm_s.cdx.json
build/sboms/stm32h5-bl2.cdx.json
build/sboms/stm32h5-tfm_s.cdx.json
build/sboms/stm32h5-bl2.cdx.json
build/sboms/stm32h5-tfm_s.cdx.json

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:

lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target bl2 \
  --map-file build/stm32h5-gnu-brew-newlib/bin/bl2.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-bl2.cdx.json \
  -v
lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target bl2 \
  --map-file build/stm32h5-gnu-brew-newlib/bin/bl2.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-bl2.cdx.json \
  -v
lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target bl2 \
  --map-file build/stm32h5-gnu-brew-newlib/bin/bl2.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-bl2.cdx.json \
  -v

Voor het veilige firmware-image:

lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target tfm_s \
  --map-file build/stm32h5-gnu-brew-newlib/bin/tfm_s.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/qcbor-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/t_cose-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-tfm_s.cdx.json \
  -v
lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target tfm_s \
  --map-file build/stm32h5-gnu-brew-newlib/bin/tfm_s.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/qcbor-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/t_cose-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-tfm_s.cdx.json \
  -v
lynkctl generate . \
  --provider cmake \
  --cmake-build-dir build/stm32h5-gnu-brew-newlib \
  --cmake-target tfm_s \
  --map-file build/stm32h5-gnu-brew-newlib/bin/tfm_s.map \
  --project-root /src/trusted-firmware-m \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/mcuboot-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/tf-psa-crypto-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/qcbor-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/t_cose-src \
  --vendored-root build/stm32h5-gnu-brew-newlib/lib/ext/cmsis-src \
  --evidence \
  -o build/sboms/stm32h5-tfm_s.cdx.json \
  -v


Resultaten

De bootloader-SBOM werd voltooid met 10 componenten:

SBOM generated: 10 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "bl2"
SBOM generated: 10 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "bl2"
SBOM generated: 10 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "bl2"

De SBOM van de veilige firmware werd voltooid met 20 componenten:

SBOM generated: 20 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "tfm_s"
SBOM generated: 20 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "tfm_s"
SBOM generated: 20 components, 5 warnings, 0 errors
selected CMake configuration "MinSizeRel"
selected CMake target "tfm_s"

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

bl2, tfm_s

Crypto-implementatie gebruikt door de bootloader en het veilige image.

MCUboot

bl2, tfm_s

Bootloadercode en firmware-updatepad.

CMSIS_6

bl2, tfm_s

Arm Cortex-M-ondersteuningslaag gebruikt door de platform-build.

STM32H5 HAL driver

bl2, tfm_s

STMicro-platformdrivercode voor de gekozen board-familie.

CMSIS Device H5

bl2, tfm_s

STM32H5-apparaatondersteuning achter de platform-targets.

QCBOR

tfm_s

CBOR-codering gebruikt door attestation-gerelateerde code van de veilige firmware.

t_cose

tfm_s

COSE-ondersteuning gebruikt door attestation-gerelateerde code van de veilige firmware.

GCC-runtime-ondersteuning (libgcc.a)

bl2, tfm_s

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.

Vertrouwd door beveiligings- en complianceteams bij meer dan 100 gereguleerde bedrijven.

Zie uw SBOM zoals het hoort

Interlynk automatiseert SBOM's, beheert open source-risico's, monitort leveranciers en bereidt u voor op het post-quantumtijdperk – alles in één vertrouwd platform.

Vertrouwd door beveiligings- en complianceteams bij meer dan 100 gereguleerde bedrijven.

Interlynk automatiseert SBOM's, beheert open-sourcerisico's, monitort leveranciers en bereidt u voor op het post-quantumtijdperk, allemaal op één vertrouwd platform.

Audit-klare SBOM. Bij elke build.

Vertrouwd door beveiligings- en complianceteams bij meer dan 100 gereguleerde bedrijven.

Interlynk automatiseert SBOM's, beheert open-sourcerisico's, monitort leveranciers en bereidt u voor op het post-quantumtijdperk, allemaal op één vertrouwd platform.

Audit-klare SBOM. Bij elke build.