4. 组件清单与SBOM

在固件供应链治理中,组件清单是治理对象的最小集合,而SBOM(Software Bill of Materials,软件物料清单)则是把这份清单标准化、可交换、可追溯的载体。没有SBOM,固件里的第三方库、开源组件、静态链接代码、二进制驱动就只能靠人工猜测;有了SBOM,才能把“这个固件里到底有什么”变成可查询、可比对、可审计的数据。

本节围绕三个问题展开:固件SBOM用什么标准表达、怎么从固件里生成、生成到什么粒度才够用,以及为什么在固件场景下这件事比在普通应用软件里难得多。

一、两种主流标准:SPDX与CycloneDX

当前固件SBOM最常用的两种标准是SPDX和CycloneDX。它们都描述组件、版本、许可证、依赖关系和唯一标识,但设计取向不同。

SPDX由Linux基金会维护,ISO/IEC 5962是其国际标准版本。它的核心是“文档—包—文件”三层结构:一个SPDX文档包含若干Package,每个Package可展开到File级别,并记录文件校验和、许可证结论、版权信息。SPDX对许可证表达非常严谨,适合需要法律合规审查的场景。它的标识符体系以SPDXID为主,外部引用通过ExternalRef关联CPE、PURL等。

CycloneDX由OWASP社区推动,最初面向应用安全,后来扩展到固件和硬件。它以“组件(component)”为中心,天然支持嵌套依赖、服务、漏洞引用和VEX(漏洞可利用性交换)。CycloneDX的JSON结构更紧凑,工具链集成友好,适合在CI/CD中自动生成和消费。它用bom-ref作为组件引用键,用purl作为组件坐标,能直接挂接漏洞库。

选择建议:如果固件需要对外交付合规证明、许可证审计要求高,优先SPDX;如果固件在持续集成中频繁生成、需要和漏洞扫描联动,优先CycloneDX。两者并非互斥,很多团队同时输出,用SPDX做合规归档,用CycloneDX做安全运营。

固件SBOM生成与落地:从源码/二进制到标准文档 输入源 源码仓库 package.json / Cargo.toml 构建系统 Makefile / CMake / Kconfig 二进制固件 .bin / .elf / .hex 第三方交付物 驱动 / 库 / 预编译模块 容器/构建环境 Dockerfile / 工具链版本 生成方法 源码级扫描 解析依赖清单、锁文件 构建过程插桩 编译器/链接器钩子 二进制成分分析 字符串、符号、指纹匹配 SBOM合并与去重 多来源结果归一化 签名与校验 哈希、签名、时间戳 SPDX 文档 / 包 / 文件 SPDXID 标识 许可证结论 文件校验和 ExternalRef 适合合规归档 CycloneDX 组件为中心 bom-ref 引用 purl 坐标 漏洞/VEX 挂接 嵌套依赖 适合安全运营 固件场景落地难点 静态链接无清单 库代码已融入镜像 交叉编译工具链 宿主与目标不一致 二进制裁剪/压缩 符号与字符串丢失 多镜像/多分区 Bootloader+App+RTOS 供应商不提供组件信息,只能靠逆向推断 粒度控制:组件级(库/包)→ 文件级(可执行/配置)→ 片段级(函数/符号),按治理目标选择,避免过粗漏报、过细不可维护

二、生成方法:从源码、构建到二进制

固件SBOM的生成通常有三条路径,实际项目中往往组合使用。

源码级扫描是最直接的方式。工具读取依赖清单文件,例如C/C++项目的conanfile.txt、vcpkg.json,Rust项目的Cargo.lock,以及Yocto/Buildroot的配方文件,解析出组件名、版本、许可证和依赖关系。这种方式准确率高,但只能覆盖“声明了依赖”的部分,对直接拷贝进源码树的第三方代码无能为力。

构建过程插桩是在编译和链接阶段采集信息。例如在CMake中通过--graphviz导出目标依赖图,在链接器包装脚本中记录每个.o来自哪个库,或者使用编译器插件记录头文件包含路径。这种方式能捕捉到实际参与构建的组件,但需要改造构建系统,且对预编译二进制无效。

二进制成分分析是固件场景的兜底手段。工具从固件镜像中提取可打印字符串、符号表、版本号、许可证声明片段,再与已知组件指纹库比对。例如在镜像中发现OpenSSL 1.1.1k字符串,或识别出zlib的特定常量表,就可以推断组件存在。这种方式覆盖最全,但存在误报和漏报,且无法还原完整依赖关系。

一个可操作的组合流程是:源码扫描产出基础SBOM,构建插桩补充实际链接关系,二进制分析做差异比对和兜底,最后合并去重、统一标识、签名输出。合并时要注意同一组件在不同来源中可能名称不同,需要用PURL或CPE做归一化。

三、粒度控制:组件级、文件级与片段级

SBOM粒度决定了治理的精度和成本。固件场景常见三种粒度:

  • 组件级:只记录库或包的名称、版本、许可证。适合快速盘点、许可证合规初筛。缺点是同一个库的不同文件可能许可证不同,组件级会掩盖这种差异。
  • 文件级:记录每个可执行文件、配置文件、资源文件的归属和哈希。适合需要精确定位漏洞影响范围的场景,例如某个CVE只影响库中的特定文件。
  • 片段级:记录到函数或代码片段级别。适合高安全等级固件,但生成和维护成本极高,通常只在关键模块使用。

粒度选择的原则是“够用即可”。如果治理目标是回答“这个固件里有没有Log4j”,组件级足够;如果要回答“这个CVE是否影响我的固件”,文件级更可靠;如果要做二进制级溯源和补丁验证,才需要片段级。过粗会漏报,过细会导致SBOM体积膨胀、更新困难、工具链不堪重负。

四、固件场景的落地难点

固件SBOM之所以比应用软件SBOM难,根源在于固件的构建和交付方式。

静态链接与代码融合是首要难点。应用软件依赖动态库,运行时可以枚举;固件通常把库静态链接进单一镜像,库代码和业务代码在二进制层面已经融合,没有独立的组件边界。SBOM工具只能靠符号和字符串推断,精度受限。

交叉编译与工具链差异带来第二层困难。固件在x86主机上编译,目标却是ARM、RISC-V或MIPS。构建系统、编译器版本、C库实现(glibc/musl/newlib)都会影响组件识别。同一个库在不同工具链下可能表现出不同的符号和字符串特征,指纹库需要覆盖多种组合。

二进制裁剪与压缩进一步削弱可分析性。为了控制固件体积,链接器会丢弃未使用的函数,strip会移除符号表,压缩会打乱字符串。这些操作让二进制分析工具失去关键线索,只能依赖残留的版本字符串或常量特征。

多镜像与多分区使SBOM边界复杂化。一个固件产品可能包含Bootloader、RTOS、应用固件、文件系统镜像、FPGA比特流等多个部分,每部分由不同团队或供应商提供。SBOM需要明确覆盖范围,否则会出现“清单里有但镜像里没有”或“镜像里有但清单里没有”的偏差。

供应商不提供组件信息是最现实的障碍。很多第三方驱动、预编译库、芯片厂商SDK不公开依赖清单,也不提供SBOM。治理团队只能通过逆向分析、合同约束、采购要求逐步推动。在无法获取信息时,应在SBOM中明确标注“未知组件”和推断依据,而不是留空或伪造。

落地建议:先建立“组件级+文件级”的混合SBOM,覆盖所有自研和已知第三方组件;对无法获取信息的二进制模块,单独维护“待确认清单”,记录哈希、来源、推断依据和风险等级;在CI中把SBOM生成作为构建产物的一部分,与固件镜像同版本、同签名、同归档。

总结来说,固件SBOM不是一次性的文档,而是贯穿采购、开发、构建、发布、运维的持续数据流。标准选择决定交换能力,生成方法决定覆盖率,粒度控制决定治理精度,而对固件特殊性的清醒认识决定这套体系能否真正落地。