De stand van zaken van SBOM-generatie voor C/C++: editie 2026
| Ritesh Noronha

Als je denkt dat het genereren van een SBOM een opgelost probleem is, werk je waarschijnlijk niet in C/C++.
Een goede SBOM-generator zou drie dingen goed moeten doen: nauwkeurig zijn, reproduceerbaar zijn, en weergeven wat er daadwerkelijk in de binaire file of firmware zit die je uitbrengt. Voor ecosystemen met volwassen package managers en machineleesbare afhankelijkheidsmetadata is dit aanzienlijk eenvoudiger. JavaScript heeft package-lock.json, Python heeft vaak poetry.lock, Rust heeft Cargo.lock en Go heeft go.sum. Die bestanden maken het genereren van een SBOM niet triviaal, maar ze bieden meestal een veel duidelijker startpunt dan C/C++.
Voor C/C++ hebben we dat voorrecht niet. En in de embedded wereld, waar C/C++ domineert, is de kloof tussen wat SBOM-tools produceren en wat er daadwerkelijk in je firmware zit groot genoeg om er met een vrachtwagen doorheen te rijden.
Ik heb veel tijd besteed aan het bouwen van SBOM-tooling voor embedded C/C++-projecten, en ik wil een eerlijke beoordeling delen van waar het landschap vandaag de dag staat — wat werkt, wat niet werkt, en waarom een gecombineerde aanpak de enige realistische weg is naar een redelijk nauwkeurige SBOM.
Het verhaal van beheerde afhankelijkheden: beter, maar niet het hele plaatje
Tools zoals CMake, Conan, vcpkg en Meson hebben enige structuur gebracht in afhankelijkheidsbeheer voor C/C++. Als je project Conan gebruikt, kun je een conan.lock-bestand krijgen. Als je vcpkg in manifestmodus gebruikt, heb je ten minste een vcpkg.json-manifest en versiebeperkingen. CMake's FetchContent-declaraties documenteren ook wat er tijdens de configuratiefase wordt binnengehaald.
Sommige SBOM-tools kunnen deze metadata gebruiken en een redelijk startpunt opleveren voor ten minste een deel van de afhankelijkheidsgrafiek. Voor het deel van de C/C++-wereld dat moderne afhankelijkheidsbeheer consequent heeft omarmd, is het SBOM-verhaal beter dan vroeger, ook al is het nog steeds onvolledig.
Die groep is klein, vooral in embedded systemen. De meeste embedded C/C++-projecten die ik tegenkom, worden niet met Conan of vcpkg gebouwd. Ze worden gebouwd met eenvoudige Makefiles — soms handgeschreven, soms gegenereerd door een IDE zoals IAR Embedded Workbench of STM32CubeIDE. De adoptie van CMake groeit in de embedded wereld, maar is nog lang niet universeel. En zelfs projecten die CMake gebruiken, hebben vaak een mix van FetchContent, systeembibliotheken, Git-submodules en meegeleverde broncode die geen enkele tool volledig in kaart brengt.
De benadering met de `-l`-vlag: nuttig, maar onvoldoende
Een veelgebruikte techniek om C/C++-afhankelijkheden te identificeren is het inspecteren van de invoer voor de linker — vooral -l-vlaggen en zoekpaden van de linker. Als uw build koppelt tegen -lssl -lcrypto -lz, kan een tool OpenSSL en zlib afleiden en dat verrijken met metadata van de pakketbeheerder wanneer die bibliotheken afkomstig waren van het besturingssysteem of een bekende pakketbron. Dit kan zowel dynamische bibliotheken (.so / .dylib) als statische archieven (.a) omvatten wanneer de build ze expliciet benoemt.
Voor desktop- en server-builds kan deze aanpak u redelijk ver brengen. De -l-vlaggen, gecombineerd met zoekpaden van de linker (-L), vertellen vaak welke bibliotheken worden meegetrokken. Statische .a-archieven die op deze manier worden gelinkt, zijn ook zichtbaarder dan meegeleverde broncode die rechtstreeks in het doel wordt gecompileerd.
Maar zelfs wanneer deze aanpak erin slaagt een component te identificeren, is er een dieper probleem: vaak krijg je alleen een bibliotheeknaam, en soms een versie, maar niet veel meer. De minimumelementen van NTIA omvatten de naam van de leverancier, de componentnaam, de versie, unieke identificaties, afhankelijkheidsrelaties, de auteur van de SBOM-gegevens en een tijdstempel. Een -l-vlag kan u een bibliotheeknaam geven. Een .a-bestand kan u misschien alleen een bestandsnaam geven zoals libfoo.a, met weinig of geen machineleesbare herkomstinformatie over wie het heeft gebouwd, waar het vandaan kwam of welke upstream-release het vertegenwoordigt. Als de bibliotheek niet afkomstig was van een pakketbron met metadata, moeten de resterende NTIA-velden meestal op een andere manier worden achterhaald — en voor de meeste C/C++-projecten bestaat er geen geautomatiseerde "andere manier."
In de embedded wereld is de situatie aanzienlijk erger. Embedded firmware-builds gebruiken vaak helemaal geen -l-vlaggen die naar systeembreed geïnstalleerde pakketten verwijzen. In plaats daarvan compileren ze de RTOS, HAL, netwerkstack, cryptobibliotheek en middleware vanuit broncode — of ze linken tegen project-lokale .a-bestanden die eerder zijn gebouwd uit meegeleverde code met weinig aangehechte metadata. De resulterende binary is vaak monolithisch. Er is mogelijk geen runtime-afhankelijkheidsinformatie in ldd-stijl om te inspecteren, geen DT_NEEDED-vermeldingen in ELF-headers, en gestript symbols verminderen wat binaire inspectie kan achterhalen. Een statisch .a-bestand dat in de lib/-directory van een project staat en maanden eerder is gebouwd uit een onbekende upstream-versie, is voor SBOM-doeleinden bijna een black box — en een nalevingskloof voor elke regelgeving die meer eist dan een componentnaam.
Ingekochte code: De echte uitdaging
Dit is waar het echt moeilijk wordt, en waar ik denk dat het C/C++ SBOM-probleem het scherpst verschilt van ecosystemen met sterke conventies voor package managers.
Code van derden zit overal in C/C++. Ontwikkelaars kopiëren bronbestanden — soms een enkel .c- en .h-paar, soms complete bibliotheekbomen — rechtstreeks in hun project. Bibliotheken zoals nlohmann/json, mongoose, stb_image, miniz en lwIP worden op deze manier regelmatig geïntegreerd. Hun eigen documentatie zegt vaak letterlijk dat je precies dit moet doen: "kopieer deze twee bestanden naar je project." Zodra ze zijn gekopieerd, is de code niet meer te onderscheiden van broncode van de eerste partij.
Fingerprinting is een van de belangrijkste technieken om gevendoriseerde code te detecteren, en het werkt goed in de eenvoudige gevallen. Als een ontwikkelaar een ongewijzigde kopie van een zelfstandige bibliotheek heeft gevendord, kun je de bestanden hashen, vergelijken met een corpus van bekende releases, en vaak een betrouwbare match krijgen. Voor kleine, op zichzelf staande bibliotheken kan dit behoorlijk betrouwbaar zijn.
Maar vendoring brengt complicaties met zich mee die van fingerprinting een heuristisch spel maken in plaats van een deterministisch spel.
Wijzigingen komen vaak voor. Ontwikkelaars vendoren code en passen die vervolgens aan — bugs repareren, API's aanpassen, ongebruikte functionaliteit verwijderen. Zodra een bestand is gewijzigd, werkt exacte hash-matching niet meer. Dan kom je terecht in fuzzy matching: vergelijken met bekende versies, technieken zoals MinHash of Winnowing gebruiken voor code-overeenkomst, of een structurele vergelijking op AST-niveau uitvoeren. Deze benaderingen kunnen werken, maar ze introduceren onzekerheid. Is dit een aangepaste kopie van FreeRTOS 10.4.3, of is het FreeRTOS 10.5.1 met andere wijzigingen? Het antwoord is van belang voor CVE-correlatie, en de tooling kan je dat vaak niet met voldoende zekerheid vertellen.
Vendor-SDK's voegen ruis toe. Halfgeleiderleveranciers zoals ST, NXP, TI en Renesas leveren SDK's mee die open-sourcecomponenten bundelen — FreeRTOS, lwIP, mbedTLS, FatFS — naast propriëtaire HAL-code. Deze bundels voegen vaak vendorspecifieke copyrightkopteksten toe, wijzigen directorystructuren, hernoemen bestanden, en forken de upstreamcode soms aanzienlijk. STM32CubeF4 bevat een versie van FreeRTOS die door ST's aanpassingsproces is gegaan. Renesas FSP bundelt een afgeleide van ThreadX. Deze tegen upstreamreleases fingerprinten is onbetrouwbaar zonder vendorspecifieke detectielogica.
Inbeddingsmodellen en op ML gebaseerde code-overeenkomst kunnen helpen, en er is interessant academisch werk over het gebruik van geleerde coderepresentaties voor identificatie van softwarecomponenten. Maar vandaag zijn deze benaderingen meestal te operationeel zwaar voor routinematige SBOM-generatie in CI op schaal. Ze kunnen een rol spelen bij eenmalige audits of onderzoeken met hoge zekerheidseisen, maar zijn nog niet een eenvoudige vervanging voor eenvoudigere technieken.
Het probleem met de gedeelde leveranciersmap
Er is een praktisch aspect dat naar mijn mening te weinig wordt besproken in SBOM-discussies. De meeste C/C++-ontwikkelaars bewaren hun geleverde code (vendored code) in afzonderlijke mappen — vendor/, 3rdparty/, external/, lib/ — wat het beheer vereenvoudigt. Dit getuigt van goede hygiu00ebne en helpt SBOM-tools om de grens tussen eigen code en code van derden te identificeren.
In de praktijk echter, met name binnen de embedded software-industrie, wordt zo'n map met geleverde code vaak gedeeld over meerdere projecten of build-doelen. Een vendor/-map kan bijvoorbeeld FreeRTOS, lwIP, mbedTLS, FatFS en nog een dozijn andere bibliotheken bevatten. Project A maakt gebruik van FreeRTOS en lwIP. Project B gebruikt FreeRTOS en mbedTLS. Project C gebruikt ze allemaal.
Een SBOM-generator die simpelweg de gehele vendor-map inventariseert, genereert een SBOM die alle geleverde componenten vermeldt — maar die SBOM is onjuist voor elk afzonderlijk build-artefact. Het geeft een overschatting van wat er daadwerkelijk in het binaire bestand aanwezig is. Als Project A geen gebruikmaakt van mbedTLS, leidt de vermelding ervan in de SBOM van Project A tot valse positieven bij kwetsbaarheidsscans en een vertekend beeld van het werkelijke aanvalsoppervlak.
Zelfs als alle code in een vendor-map compileert en linkt, zal de --gc-sections-vlag van de linker alle secties die niet bereikbaar zijn vanaf het startpunt via garbagecollection verwijderen. U kunt weliswaar 30 gedeelde bibliotheken in uw build compileren, maar als de functies hiervan nooit worden aangeroepen, dragen ze nul bytes bij aan het uiteindelijke binaire bestand. Wanneer u deze allemaal als componenten in uw SBOM opneemt, leidt dit tot onnauwkeurige resultaten en valse positieven.
Bij veel embedded projecten is de meest betrouwbare methode om dit correct aan te pakken het analyseren van wat het build-systeem daadwerkelijk compileert en linkt voor een specifiek doel. Dit brengt ons terug bij het build-systeem als de primaire bron van waarheid, en verklaart waarom analyse tijdens de build-fase doorgaans essentieel is voor embedded C/C++.
De echte wereld is rommelig: een gecombineerde aanpak
Geen enkele techniek alleen lost de problematiek rondom de C/C++ SBOM op. Na het bestuderen van de verschillende benaderingen — zoals het parsen van manifestbestanden, de inspectie van linkerflags, bestandsfingerprinting, binaire analyse en build-systeeminstrumentatie — ben ik ervan overtuigd geraakt dat een gecombineerde aanpak de enige weg is naar een redelijk nauwkeurige SBOM. Concreet dient u vier signalen met elkaar te combineren:
Wat er wordt gebouwd. Het build-systeem — of dit nu Make, CMake, Meson, IAR of een andere tool is — weet welke bronbestanden worden gecompileerd en welke bibliotheken voor een bepaald doelbestand worden gelinkt. Door het build-proces te instrumenteren, verkrijgt u de absolute waarheid over wat er in het binaire bestand terechtkomt. Dit vormt het fundament.
Wat er wordt meegeleverd (vendored). Door middel van directory fingerprinting, bestandshashing en de extractie van versiestrings kunnen bekende open-source componenten in leveranciersgidsen (vendor directories) worden geïdentificeerd. De sleutel hierbij is om dit te correleren met wat het build-systeem daadwerkelijk gebruikt — en niet louter met wat er op de schijf aanwezig is. Een component in vendor/ die nooit in uw doelbestand wordt gecompileerd, hoort niet in uw SBOM thuis.
Voor welk platform of welke microcontroller het wordt gebouwd. Het doelplatform bepaalt welke SDK's en HAL-bibliotheken van leveranciers van toepassing zijn. Als u bouwt voor een STM32F4, is de kans groot dat er STM32CubeF4 HAL-componenten aanwezig zijn. Indien u zich richt op een Renesas RA6M4, krijgt u te maken met Renesas FSP. Door rekening te houden met het platform kunt u de zoekruimte verkleinen en leveranciersspecifieke detectieheuristieken toepassen. Dit vereenvoudigt tevens de CPE/PURL-mapping, aangezien de naamgeving van embedded componenten zeer leveranciersspecifiek is.
Statisch dependency-management. Inzicht in hoe statische bibliotheken (.a, .lib) worden gelinkt, welke objectbestanden ze bevatten en wat de herkomst ervan is, maakt het plaatje compleet. Map-bestanden (.map), linker-scripts en de inhoud van archieven bieden hierbij allen aanwijzingen. Voor IAR-projecten tonen .map-bestanden elk objectbestand dat in de definitieve image is gelinkt. Voor op GCC gebaseerde builds onthult ar -t op statische archieven de exacte inhoud ervan.
Elk van deze signalen afzonderlijk is onvolledig. Gecombineerd leiden ze echter tot een aanzienlijk nauwkeurigere SBOM, wat essentieel is voor uw vulnerability management, inkoopprocessen en naleving van wet- en regelgeving.
Dit is het ontwerp waarop lynkctl, de commerciële SBOM-generator voor embedded C/C++ van Interlynk, is gebaseerd — toolchain-bewuste extractie uit GNU Make, CMake en IAR, gecombineerd met een gecureerde embedded open-sourcesoftware-index voor de meegeleverde softwarelaag.
Ontwikkelaarscontext blijft nog steeds belangrijk
Geautomatiseerde detectie is belangrijk, maar voor C/C++-projecten, vooral embedded projecten, blijft door ontwikkelaars aangeleverde context erg belangrijk. Het ontwikkelingsteam weet vaak welke meegeleverde bibliotheek is overgenomen, waar die vandaan komt, welke patches zijn toegepast en welke componenten bij welke buildvariant horen. Die context is precies het soort informatie dat geautomatiseerde scanners moeilijk betrouwbaar kunnen reconstrueren uit broncodebomen, archieven en gestript binaire bestanden.
Dat betekent niet dat handmatige of door ontwikkelaars onderhouden manifesten een wondermiddel zijn. Ze kunnen verouderen, afhankelijkheden missen of afwijken van de werkelijkheid als ze niet nauw aan het bouwproces zijn gekoppeld. In de praktijk kunnen ze echter een nuttige aanvulling zijn op geautomatiseerde analyse, vooral voor herkomst-, leverancier-, licentie- en buildvariantmetadata die achteraf moeilijk af te leiden zijn.
Gestructureerde componentmetadata is de ontbrekende laag in SBOM's — en Interlynk helpt deze te definiëren. Met de nieuwe sbom-generatiefunctie van sbomasm stellen we teams in staat ontwikkelaarscontext vast te leggen waar die ontstaat, waardoor geautomatiseerde scans en analyse tijdens de build aanzienlijk betrouwbaarder worden.
Waar gaan we vanaf hier naartoe?
Het C/C++ SBOM-probleem kan niet worden opgelost met één enkele tool of een enkele techniek. Het vereist een gelaagde aanpak die build-systeemintelligentie, detectie van meegeleverde code (vendored code), platformbewuste heuristieken en door de gemeenschap onderhouden corpora van bekende embedded bibliotheken en hun vingerafdrukken combineert.
De embedded C/C++ community dient daarnaast te investeren in een betere hygiëne van afhankelijkheden — door waar mogelijk pakketbeheerders te introduceren, expliciete manifests voor meegeleverde code bij te houden en het genereren van SBOM's te behandelen als een primair build-artefact in plaats van een bijzaak.
Voor degenen onder ons die tooling in deze sector ontwikkelen, is de kans helder: degene die op betrouwbare wijze de kloof kan overbruggen tussen de complexiteit van C/C++-projecten in de praktijk en de gestructureerde, nauwkeurige SBOM's die door regelgeving worden vereist, lost een van de moeilijkste resterende problemen in de veiligheid van de softwareleveringsketen op.
En dat zal een SBOM zijn die u kunt vertrouwen en verifiëren.
Ontdek de aanpak van Interlynk voor Embedded
Citaties
CMake, "FetchContent," officiële documentatie: https://cmake.org/cmake/help/latest/module/FetchContent.html
NTIA, "De minimale elementen voor een software bill of materials (SBOM)," juli 2021: https://www.ntia.gov/sites/default/files/publications/sbom_minimum_elements_report_0.pdf
Europese Commissie, "Cyber Resilience Act - implementatie," geraadpleegd op 9 april 2026: https://digital-strategy.ec.europa.eu/en/factpages/cyber-resilience-act-implementation
FDA, "Medical Device Software Guidance Navigator," inclusief "Cybersecurity in Medical Devices: Overwegingen voor het kwaliteitssysteem en inhoud van premarket-indieningen": https://www.fda.gov/medical-devices/regulatory-accelerator/medical-device-software-guidance-navigator
Het Witte Huis, "Executive Order on Improving the Nation's Cybersecurity" (Executive Order 14028), 12 mei 2021: https://www.whitehouse.gov/briefing-room/presidential-actions/2021/05/12/executive-order-on-improving-the-nations-cybersecurity/
sbomasm, "Uw SBOM-samensteller" : https://github.com/interlynk-io/sbomasm
Veelgestelde vragen
Hoe genereer ik een SBOM voor een C/C++-codebase?
Genereer tijdens de build een CycloneDX- of SPDX-SBOM en combineer meerdere signalen, want voor C/C++ volstaat geen enkele techniek. Gebruik beschikbare package-manager-metadata (een conan.lock van Conan, een vcpkg.json-manifest of CMake FetchContent-declaraties), inspecteer linker-invoer (-l-flags en -L-zoekpaden) om systeem- en statische bibliotheken te identificeren, en fingerprint vendored broncode (rechtstreeks in het project gekopieerde code) tegen bekende releases. Een gecombineerde aanpak is de enige realistische weg naar een redelijk nauwkeurige SBOM.
Waarom is het genereren van een SBOM moeilijker voor C/C++ dan voor andere talen?
Ecosystemen zoals JavaScript (package-lock.json), Python (poetry.lock), Rust (Cargo.lock) en Go (go.sum) geven tools een machineleesbare afhankelijkheidsgraaf als startpunt. Voor C/C++ bestaat geen equivalent. Code wordt vaak vendored, vanuit de broncode gecompileerd of als projectlokale .a-archieven zonder herkomstgegevens gelinkt, waardoor de meeste tools grote delen missen van wat er werkelijk in de binary of firmware wordt uitgeleverd.
Kunnen CMake, Conan of vcpkg een SBOM genereren?
Ze helpen, maar lossen het niet volledig op. Conan kan een conan.lock produceren, de manifest-modus van vcpkg levert een vcpkg.json met versiebeperkingen, en CMake FetchContent documenteert wat er tijdens configuratie wordt opgehaald, dus een SBOM-tool kan hiermee een deel van de afhankelijkheidsgraaf opbouwen. Maar de adoptie is klein, vooral in embedded, waar eenvoudige Makefiles, IAR- of STM32CubeIDE-projecten, Git-submodules en vendored broncode gangbaar zijn en geen enkele tool alles vastlegt.
Hoe leg je vendored of gekopieerde C/C++-code vast in een SBOM?
Vendored code, zoals nlohmann/json, mongoose, stb_image of lwIP die in het project is gekopieerd, is niet meer te onderscheiden van eigen broncode. Fingerprinting (bestanden hashen en vergelijken met een corpus van bekende releases) werkt voor ongewijzigde kopieën, maar zodra code is aangepast heb je fuzzy matching nodig zoals MinHash, Winnowing of vergelijking op AST-niveau, wat onzekerheid introduceert. SDK's van chipfabrikanten als ST, NXP, TI en Renesas die upstream-componenten forken en hernoemen, voegen extra ruis toe.
Werkt het inspecteren van linker-flags (-l) bij embedded C/C++-builds?
Het inspecteren van -l-flags en -L-zoekpaden kan bij desktop- en serverbuilds bibliotheken als OpenSSL of zlib identificeren, vooral als ze uit een package-bron komen. Maar het levert meestal alleen een naam op, en soms een versie, niet de volledige NTIA Minimum Elements (leverancier, unieke identifiers, afhankelijkheidsrelaties). Embedded firmware compileert RTOS, HAL en crypto vaak vanuit de broncode of linkt projectlokale .a-bestanden zonder metadata, zodat alleen linkerinspectie aanzienlijke gaten laat.
Welk SBOM-formaat moet een C/C++-project gebruiken?
Gebruik CycloneDX of SPDX. Beide zijn open, machineleesbare standaarden en de verwachte formaten voor compliance-regimes zoals de FDA premarket-richtlijn en de EU Cyber Resilience Act. Een goede C/C++ SBOM-generator moet nauwkeurige, reproduceerbare uitvoer produceren die weergeeft wat er werkelijk in de uitgeleverde binary of firmware zit.