@@ -13,6 +13,24 @@ name: Wolfboot SBOM Canary
1313#
1414# Each route must emit a schema-valid CycloneDX 1.6 + SPDX 2.3 document whose
1515# top-level component is wolfboot.
16+ #
17+ # The sbom_canary job also guards three properties of the SBOM itself:
18+ #
19+ # * Toolchain neutrality - gcc and clang must give a byte-identical SBOM.
20+ # The driver captures configuration with the host compiler and the source
21+ # list, so the cross-toolchain that builds the firmware does not change the
22+ # SBOM. No per-compiler front end is needed for clang, LLVM or a vendor
23+ # compiler.
24+ # * Reproducibility - the same configuration built from a different absolute
25+ # path gives a byte-identical SBOM.
26+ # * Path scrub - an absolute host path in a -D macro (the synthetic check and
27+ # a real rp2350 build with PICO_SDK_PATH) must not reach the SBOM.
28+ #
29+ # The cross_targets job proves the same source route works for many embedded
30+ # targets with no IDE and no cross-toolchain installed. It runs make sbom for a
31+ # spread of architectures (Arm-M, Arm-A, PowerPC, Renesas RX and RISC-V). The
32+ # driver never calls the cross compiler, so the SBOM is produced for a target
33+ # whose toolchain is absent.
1634
1735on :
1836 push :
3654 - name : Install tooling
3755 run : |
3856 sudo apt-get update
39- sudo apt-get install -y cmake python3 build-essential
57+ sudo apt-get install -y cmake python3 build-essential clang
4058
4159 # gen-sbom ships with wolfSSL. Use the vendored submodule copy if the
4260 # pinned revision already carries it, otherwise fetch it from wolfSSL
@@ -128,6 +146,78 @@ jobs:
128146 python3 tools/scripts/ide-sbom/validate_sbom.py \
129147 zephyr.cdx.json zephyr.spdx.json
130148
149+ # Toolchain neutrality: the cross-toolchain that builds the firmware must
150+ # not change the SBOM. Capture the same config with gcc and with clang and
151+ # require a byte-identical result. SOURCE_DATE_EPOCH is fixed so the two
152+ # runs are comparable.
153+ - name : Toolchain neutrality (gcc vs clang)
154+ run : |
155+ export SOURCE_DATE_EPOCH=1700000000
156+ cp config/examples/sim.config .config
157+ rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
158+ make sbom TARGET=sim HOSTCC=gcc \
159+ GEN_SBOM="${{ steps.gensbom.outputs.path }}"
160+ mv wolfboot-*.cdx.json /tmp/gcc.cdx.json
161+ mv wolfboot-*.spdx.json /tmp/gcc.spdx.json
162+ make sbom TARGET=sim HOSTCC=clang \
163+ GEN_SBOM="${{ steps.gensbom.outputs.path }}"
164+ mv wolfboot-*.cdx.json /tmp/clang.cdx.json
165+ mv wolfboot-*.spdx.json /tmp/clang.spdx.json
166+ if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
167+ || ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
168+ echo "ERROR: gcc and clang produced different SBOMs." >&2
169+ echo "The SBOM must not depend on the toolchain." >&2
170+ exit 1
171+ fi
172+ echo "neutrality OK: gcc and clang SBOMs are byte-identical"
173+
174+ # Reproducibility: the same configuration built from a different absolute
175+ # path must give a byte-identical SBOM. This catches any absolute build
176+ # path that leaks into the source list or the config record.
177+ - name : Reproducibility (path independence)
178+ run : |
179+ export SOURCE_DATE_EPOCH=1700000000
180+ cp config/examples/sim.config .config
181+ rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
182+ make sbom TARGET=sim GEN_SBOM="${{ steps.gensbom.outputs.path }}"
183+ cp wolfboot-*.cdx.json /tmp/repro-a.cdx.json
184+ rm -rf /tmp/wb-copy && mkdir -p /tmp/wb-copy
185+ cp -a . /tmp/wb-copy/
186+ git config --global --add safe.directory /tmp/wb-copy
187+ ( cd /tmp/wb-copy \
188+ && export SOURCE_DATE_EPOCH=1700000000 \
189+ && rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json \
190+ && make sbom TARGET=sim \
191+ GEN_SBOM="${{ steps.gensbom.outputs.path }}" )
192+ cp /tmp/wb-copy/wolfboot-*.cdx.json /tmp/repro-b.cdx.json
193+ if ! diff -u /tmp/repro-a.cdx.json /tmp/repro-b.cdx.json; then
194+ echo "ERROR: SBOM is not reproducible across build paths." >&2
195+ exit 1
196+ fi
197+ echo "reproducibility OK: identical SBOM from two paths"
198+
199+ # Path scrub in a real build: rp2350 injects -DPICO_SDK_PATH=<abs path>
200+ # through arch.mk. The absolute path must be redacted while the macro key
201+ # is kept. This exercises the scrub on a genuine build, not a synthetic
202+ # command line.
203+ - name : Path scrub in a real build (rp2350 / PICO_SDK_PATH)
204+ run : |
205+ cp config/examples/rp2350.config .config
206+ rm -f wolfboot-*.cdx.json wolfboot-*.spdx.json
207+ PICO_SDK_PATH=/home/ci-secret/pico-sdk \
208+ make sbom TARGET=rp2350 \
209+ GEN_SBOM="${{ steps.gensbom.outputs.path }}"
210+ if grep -q 'ci-secret' wolfboot-*.cdx.json wolfboot-*.spdx.json; then
211+ echo "ERROR: absolute PICO_SDK_PATH leaked in a real rp2350 build." >&2
212+ exit 1
213+ fi
214+ grep -q 'PICO_SDK_PATH' wolfboot-*.cdx.json || {
215+ echo "ERROR: PICO_SDK_PATH macro was dropped (should be redacted)." >&2
216+ exit 1; }
217+ python3 tools/scripts/ide-sbom/validate_sbom.py \
218+ wolfboot-*.cdx.json wolfboot-*.spdx.json
219+ echo "rp2350 scrub OK: path redacted, macro key preserved"
220+
131221 - name : Upload SBOM artifacts
132222 if : always()
133223 uses : actions/upload-artifact@v4
@@ -145,3 +235,72 @@ jobs:
145235 zephyr.cdx.json
146236 zephyr.spdx.json
147237 if-no-files-found : warn
238+
239+ # Proves the source route (make sbom) works for a spread of embedded targets
240+ # with no IDE and no cross-toolchain installed. The driver never calls the
241+ # cross compiler, so every target below produces a valid SBOM on a plain
242+ # runner. This is the "any target, any toolchain, no hardware" guarantee.
243+ cross_targets :
244+ runs-on : ubuntu-latest
245+ timeout-minutes : 15
246+ strategy :
247+ fail-fast : false
248+ matrix :
249+ # A spread of architectures whose cross-toolchains are NOT installed:
250+ # stm32h7 Arm Cortex-M7 nrf52840 Arm Cortex-M4
251+ # imx-rt1060 Arm Cortex-M7 sama5d3 Arm Cortex-A5
252+ # nxp-t1040 PowerPC e5500 renesas-rx65n Renesas RX
253+ # hifive1 RISC-V
254+ target :
255+ - stm32h7
256+ - nrf52840
257+ - imx-rt1060
258+ - sama5d3
259+ - nxp-t1040
260+ - renesas-rx65n
261+ - hifive1
262+
263+ steps :
264+ - uses : actions/checkout@v4
265+ with :
266+ submodules : true
267+
268+ - name : Trust workspace
269+ run : git config --global --add safe.directory "$GITHUB_WORKSPACE"
270+
271+ - name : Install tooling
272+ run : |
273+ sudo apt-get update
274+ sudo apt-get install -y python3
275+
276+ - name : Locate gen-sbom
277+ id : gensbom
278+ run : |
279+ if [ -f lib/wolfssl/scripts/gen-sbom ]; then
280+ echo "path=$GITHUB_WORKSPACE/lib/wolfssl/scripts/gen-sbom" >> "$GITHUB_OUTPUT"
281+ else
282+ mkdir -p .sbom-tools
283+ curl -fsSL \
284+ https://raw.githubusercontent.com/wolfSSL/wolfssl/master/scripts/gen-sbom \
285+ -o .sbom-tools/gen-sbom
286+ chmod +x .sbom-tools/gen-sbom
287+ echo "path=$GITHUB_WORKSPACE/.sbom-tools/gen-sbom" >> "$GITHUB_OUTPUT"
288+ fi
289+
290+ - name : make sbom (no cross-toolchain present)
291+ run : |
292+ cp config/examples/${{ matrix.target }}.config .config
293+ make sbom TARGET=${{ matrix.target }} \
294+ GEN_SBOM="${{ steps.gensbom.outputs.path }}"
295+ python3 tools/scripts/ide-sbom/validate_sbom.py \
296+ wolfboot-*.cdx.json wolfboot-*.spdx.json
297+
298+ - name : Upload SBOM artifact
299+ if : always()
300+ uses : actions/upload-artifact@v4
301+ with :
302+ name : wolfboot-sbom-${{ matrix.target }}
303+ path : |
304+ wolfboot-*.cdx.json
305+ wolfboot-*.spdx.json
306+ if-no-files-found : warn
0 commit comments