Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 358 additions & 0 deletions .github/workflows/test-sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
name: Wolfboot SBOM Canary

# Exercises every wolfBoot SBOM route so a change to the build system, the
# vendored wolfGlass tooling under tools/sbom/, or an IDE extractor cannot
# silently break SBOM generation:
#
# * Make path (methods 1-4) -> make sbom TARGET=sim
# * CMake path (method 5) -> cmake --build --target sbom
# * IAR extractor (method 7) -> tools/sbom/frontends/iar_sbom.py
# * compdb extractor (6, 8-11) -> tools/sbom/frontends/compdb_sbom.py
# * per-HAL SBOM -> make sbom-hal TARGET=sim
# * Zephyr module SBOM -> tools/sbom/frontends/zephyr_sbom.py
#
# Each route must emit a schema-valid CycloneDX 1.6 + SPDX 2.3 document whose
# top-level component is wolfboot.
#
# The sbom_canary job also guards three properties of the SBOM itself:
#
# * Toolchain neutrality - gcc and clang must give a byte-identical SBOM.
# The driver captures configuration with the host compiler and the source
# list, so the cross-toolchain that builds the firmware does not change the
# SBOM. No per-compiler front end is needed for clang, LLVM or a vendor
# compiler.
# * Reproducibility - the same configuration built from a different absolute
# path gives a byte-identical SBOM.
# * Path scrub - an absolute host path in a -D macro (the synthetic check and
# a real rp2350 build with PICO_SDK_PATH) must not reach the SBOM.
#
# The cross_targets job proves the same source route works for many embedded
# targets with no IDE and no cross-toolchain installed. It runs make sbom for a
# spread of architectures (Arm-M, Arm-A, PowerPC, Renesas RX and RISC-V). The
# driver never calls the cross compiler, so the SBOM is produced for a target
# whose toolchain is absent.

on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]

jobs:
sbom_canary:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Trust workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Install tooling
run: |
sudo apt-get update
sudo apt-get install -y cmake python3 build-essential clang

- name: Verify vendored gen-sbom
run: test -f tools/sbom/gen-sbom

# Reproducibility guard: a -D macro carrying an absolute host path (e.g.
# arch.mk's -DPICO_SDK_PATH=$(PICO_SDK_PATH)) must never reach the SBOM.
- name: Path scrub check
run: |
printf 'src/image.c\n' > /tmp/scrub-srcs.txt
tools/sbom/sbom-driver \
--srcs-file /tmp/scrub-srcs.txt \
--cflags "-DWOLFBOOT_HASH_SHA256 -DPICO_SDK_PATH=/home/ci-secret/pico-sdk" \
--name wolfboot --version 0.0.0-scrubtest \
--cdx-out /tmp/scrub.cdx.json --spdx-out /tmp/scrub.spdx.json
if grep -q 'ci-secret' /tmp/scrub.cdx.json /tmp/scrub.spdx.json; then
echo "ERROR: absolute host path leaked into the SBOM (scrub failed)." >&2
exit 1
fi
grep -q 'PICO_SDK_PATH' /tmp/scrub.cdx.json || {
echo "ERROR: PICO_SDK_PATH macro was dropped entirely (should be redacted, not removed)." >&2
exit 1; }
echo "scrub OK: path redacted, macro key preserved"

- name: Make path - make sbom (sim)
run: |
cp config/examples/sim.config .config
make sbom TARGET=sim
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
wolfboot-*.cdx.json wolfboot-*.spdx.json

- name: CMake path - cmake --target sbom (sim)
run: |
export SOURCE_DATE_EPOCH=1700000000
rm -rf build-sim
cmake -S . -B build-sim -G "Unix Makefiles" \
-DWOLFBOOT_TARGET=sim -DARCH=HOST -DSIGN=ED25519 -DHASH=SHA256 \
-DWOLFBOOT_SECTOR_SIZE=256 -DWOLFBOOT_PARTITION_SIZE=0x6400 \
-DWOLFBOOT_PARTITION_BOOT_ADDRESS=0x08003000 \
-DWOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08009400 \
-DWOLFBOOT_PARTITION_SWAP_ADDRESS=0x0800F800 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build-sim --target sbom
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
build-sim/wolfboot-*.cdx.json build-sim/wolfboot-*.spdx.json

- name: IAR extractor (method 7)
run: |
python3 tools/sbom/frontends/iar_sbom.py IDE/IAR/wolfboot.ewp \
--name wolfboot \
--driver tools/sbom/sbom-driver \
--version-file include/wolfboot/version.h \
--version-macro LIBWOLFBOOT_VERSION_STRING \
--cdx-out iar.cdx.json --spdx-out iar.spdx.json
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
iar.cdx.json iar.spdx.json

- name: compdb extractor (methods 6, 8-11)
run: |
python3 tools/sbom/frontends/compdb_sbom.py \
build-sim/compile_commands.json \
--name wolfboot \
--driver tools/sbom/sbom-driver \
--version-file include/wolfboot/version.h \
--version-macro LIBWOLFBOOT_VERSION_STRING \
--cdx-out compdb.cdx.json --spdx-out compdb.spdx.json
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
compdb.cdx.json compdb.spdx.json

- name: Per-HAL SBOM (make sbom-hal)
run: |
cp config/examples/sim.config .config
make sbom-hal TARGET=sim
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot-hal- \
wolfboot-hal-sim-*.cdx.json wolfboot-hal-sim-*.spdx.json

- name: Zephyr module SBOM
run: |
python3 tools/sbom/frontends/zephyr_sbom.py \
--driver tools/sbom/sbom-driver \
--name wolfboot-zephyr \
--cmakelists zephyr/CMakeLists.txt \
--version-file include/wolfboot/version.h \
--version-macro LIBWOLFBOOT_VERSION_STRING \
--cdx-out zephyr.cdx.json --spdx-out zephyr.spdx.json
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot-zephyr \
zephyr.cdx.json zephyr.spdx.json

- name: Make vs CMake equivalence (sim, advisory)
continue-on-error: true
run: |
export SOURCE_DATE_EPOCH=1700000000
cp config/examples/sim.config .config
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
make sbom TARGET=sim
cp wolfboot-*.cdx.json /tmp/make.cdx.json
cp wolfboot-*.spdx.json /tmp/make.spdx.json
rm -rf build-sim-compare
cmake -S . -B build-sim-compare -G "Unix Makefiles" \
-DWOLFBOOT_TARGET=sim -DARCH=HOST -DSIGN=ED25519 -DHASH=SHA256 \
-DWOLFBOOT_SECTOR_SIZE=256 -DWOLFBOOT_PARTITION_SIZE=0x6400 \
-DWOLFBOOT_PARTITION_BOOT_ADDRESS=0x08003000 \
-DWOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08009400 \
-DWOLFBOOT_PARTITION_SWAP_ADDRESS=0x0800F800 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build-sim-compare --target sbom
cp build-sim-compare/wolfboot-*.cdx.json /tmp/cmake.cdx.json
cp build-sim-compare/wolfboot-*.spdx.json /tmp/cmake.spdx.json
diff -u /tmp/make.cdx.json /tmp/cmake.cdx.json
diff -u /tmp/make.spdx.json /tmp/cmake.spdx.json

# Toolchain neutrality: the cross-toolchain that builds the firmware must
# not change the SBOM. Capture the same config with gcc and with clang and
# require a byte-identical result. SOURCE_DATE_EPOCH is fixed so the two
# runs are comparable.
- name: Toolchain neutrality (gcc vs clang)
run: |
export SOURCE_DATE_EPOCH=1700000000
cp config/examples/sim.config .config
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
make sbom TARGET=sim HOSTCC=gcc
mv wolfboot-*.cdx.json /tmp/gcc.cdx.json
mv wolfboot-*.spdx.json /tmp/gcc.spdx.json
make sbom TARGET=sim HOSTCC=clang
mv wolfboot-*.cdx.json /tmp/clang.cdx.json
mv wolfboot-*.spdx.json /tmp/clang.spdx.json
if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
|| ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
echo "ERROR: gcc and clang produced different SBOMs." >&2
echo "The SBOM must not depend on the toolchain." >&2
exit 1
fi
echo "neutrality OK: gcc and clang SBOMs are byte-identical"

# Reproducibility: the same configuration built from a different absolute
# path must give a byte-identical SBOM. This catches any absolute build
# path that leaks into the source list or the config record.
- name: Reproducibility (path independence)
run: |
export SOURCE_DATE_EPOCH=1700000000
cp config/examples/sim.config .config
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
make sbom TARGET=sim
cp wolfboot-*.cdx.json /tmp/repro-a.cdx.json
rm -rf /tmp/wb-copy && mkdir -p /tmp/wb-copy
cp -a . /tmp/wb-copy/
git config --global --add safe.directory /tmp/wb-copy
( cd /tmp/wb-copy \
&& export SOURCE_DATE_EPOCH=1700000000 \
&& rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json \
&& make sbom TARGET=sim )
cp /tmp/wb-copy/wolfboot-*.cdx.json /tmp/repro-b.cdx.json
if ! diff -u /tmp/repro-a.cdx.json /tmp/repro-b.cdx.json; then
echo "ERROR: SBOM is not reproducible across build paths." >&2
exit 1
fi
echo "reproducibility OK: identical SBOM from two paths"

# Path scrub in a real build: rp2350 injects -DPICO_SDK_PATH=<abs path>
# through arch.mk. The absolute path must be redacted while the macro key
# is kept. This exercises the scrub on a genuine build, not a synthetic
# command line.
- name: Path scrub in a real build (rp2350 / PICO_SDK_PATH)
run: |
cp config/examples/rp2350.config .config
rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
PICO_SDK_PATH=/home/ci-secret/pico-sdk \
make sbom TARGET=rp2350
if grep -q 'ci-secret' wolfboot-*.cdx.json wolfboot-*.spdx.json; then
echo "ERROR: absolute PICO_SDK_PATH leaked in a real rp2350 build." >&2
exit 1
fi
grep -q 'PICO_SDK_PATH' wolfboot-*.cdx.json || {
echo "ERROR: PICO_SDK_PATH macro was dropped (should be redacted)." >&2
exit 1; }
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
wolfboot-*.cdx.json wolfboot-*.spdx.json
echo "rp2350 scrub OK: path redacted, macro key preserved"

- name: Upload SBOM artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: wolfboot-sboms
path: |
wolfboot-*.cdx.json
wolfboot-*.spdx.json
build-sim/wolfboot-*.cdx.json
build-sim/wolfboot-*.spdx.json
iar.cdx.json
iar.spdx.json
compdb.cdx.json
compdb.spdx.json
zephyr.cdx.json
zephyr.spdx.json
if-no-files-found: warn

# Proves the source route (make sbom) works for a spread of embedded targets
# with no IDE and no cross-toolchain installed. The driver never calls the
# cross compiler, so every target below produces a valid SBOM on a plain
# runner. This is the "any target, any toolchain, no hardware" guarantee.
cross_targets:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# A spread of architectures whose cross-toolchains are NOT installed:
# stm32h7 Arm Cortex-M7 nrf52840 Arm Cortex-M4
# imx-rt1060 Arm Cortex-M7 sama5d3 Arm Cortex-A5
# nxp-t1040 PowerPC e5500 renesas-rx65n Renesas RX
# hifive1 RISC-V
target:
- stm32h7
- nrf52840
- imx-rt1060
- sama5d3
- nxp-t1040
- renesas-rx65n
- hifive1

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Trust workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Install tooling
run: |
sudo apt-get update
sudo apt-get install -y python3

- name: Verify vendored gen-sbom
run: test -f tools/sbom/gen-sbom

- name: make sbom (no cross-toolchain present)
run: |
cp config/examples/${{ matrix.target }}.config .config
make sbom TARGET=${{ matrix.target }}
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfboot \
wolfboot-*.cdx.json wolfboot-*.spdx.json

- name: Upload SBOM artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: wolfboot-sbom-${{ matrix.target }}
path: |
wolfboot-*.cdx.json
wolfboot-*.spdx.json
if-no-files-found: warn

windows_sbom:
runs-on: windows-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Native driver path scrub
shell: pwsh
run: |
Set-Content -Path scrub-srcs.txt -Value "src/image.c"
python tools/sbom/sbom-driver.py `
--srcs-file scrub-srcs.txt `
--cflags "-DWOLFBOOT_HASH_SHA256 -DSDK_PATH=C:\Users\ci-secret\sdk -DSDK_SHARE=\\server\share\sdk" `
--name wolfboot `
--version-file include/wolfboot/version.h `
--version-macro LIBWOLFBOOT_VERSION_STRING `
--cdx-out wolfboot-win.cdx.json `
--spdx-out wolfboot-win.spdx.json
if (Select-String -Path wolfboot-win.cdx.json,wolfboot-win.spdx.json -Pattern 'ci-secret|server\\share' -Quiet) {
throw "Windows absolute path leaked into the SBOM."
}
python tools/sbom/validate_sbom.py --name-prefix wolfboot wolfboot-win.cdx.json wolfboot-win.spdx.json

- name: Upload Windows SBOM artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: wolfboot-sbom-windows
path: |
wolfboot-win.cdx.json
wolfboot-win.spdx.json
if-no-files-found: warn
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ language.settings.xml
/**/build
/**/build-**

# Exception: vendored wolfGlass SBOM integration sources.
# This directory contains tracked Make/CMake fragments, not generated output.
!/tools/sbom/build/
!/tools/sbom/build/**

# User config
# See cmake/config_defaults_user.cmake.sample
/**/cmake/config_defaults_user.cmake
Expand Down Expand Up @@ -421,3 +426,13 @@ sdcard.img

# wolfHSM STM32H5 TZ demo build output
port/stmicro/stm32h5-tz-wolfhsm/out/

# Generated SBOM artifacts (CycloneDX 1.6 + SPDX 2.3)
wolfboot-*.cdx.json
wolfboot-*.spdx.json
wolfboot-*.spdx
wolfboot-sbom-srcs.txt

# Python cache files
__pycache__/
*.py[cod]
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1546,4 +1546,9 @@ if(HOST_IS_MSVC) # Some VS2022 helpers
"${CMAKE_CURRENT_BINARY_DIR}")
endif() # HOST_IS_MSVC VS2022 helpers

#---------------------------------------------------------------------------------------------
# SBOM generation (CycloneDX 1.6 + SPDX 2.3), shares the engine used by `make sbom`
#---------------------------------------------------------------------------------------------
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/sbom.cmake)

message(STATUS "End [WOLFBOOT_ROOT]/CmakeLists.txt")
Loading
Loading