|
Embedded SDK
Embedded SDK
|
Fuzzing earns its keep in continuous integration. On every pull request CI fuzzes the changed code for a short budget, so a regression that introduces a crash, memory error, or undefined behaviour is caught in review — before it reaches master and ships in a release. The local campaign (Running a campaign) is for developing and reproducing; CI is where fuzzing runs continuously.
CI runs through ClusterFuzzLite. An app ships a small .clusterfuzzlite/ folder:
| File | Purpose |
|---|---|
build.sh | Builds the fuzz targets (a thin caller, below) |
Dockerfile | The build image (based on the Ledger fuzz dev-tools image) |
project.yaml | ClusterFuzzLite project settings (language: c) |
On a PR, ClusterFuzzLite builds the targets with build.sh, runs each one under a sanitizer for a time budget, and reports any crash on the PR with the input attached.
Every fuzz tree — apps and the SDK self-fuzz suite alike — builds through one shared script, scripts/cfl-build.sh. An app's build.sh only sets a few variables and delegates:
#!/bin/bash -eu
export BOLOS_SDK="${SRC:-/src}/ledger-secure-sdk"
export APP_DIR="${SRC:-/src}/<app>"
exec "${BOLOS_SDK}/fuzzing/scripts/cfl-build.sh"
Add export APP_FUZZ_SUBDIR=tests/fuzzing before the exec line for apps that keep their fuzzing tree outside the default fuzzing/ folder.
cfl-build.sh builds each target, rediscovers the invariant for the CI sanitizer, ships the binary, and packs its seed corpus — merging the promoted base corpus when its key matches. Because each sanitizer in the matrix shifts the global layout, CI builds always bootstrap the invariant from scratch (BOOTSTRAP_INVARIANT=1); the committed .zon models are never reused across CI entries.
CI typically runs an address, an undefined-behaviour, and a memory sanitizer entry. Each catches a different class of bug and each has its own global layout, which is why the invariant is regenerated per entry rather than taken from a committed model.
Fuzzing only guards what it can still build and interpret. As an app changes:
harness_version in the manifest so the stale base corpus is retired instead of silently misinterpreted.fuzz_commands[] (see The harness) so CI actually exercises it.key-files-coverage.txt for the files you care about; low numbers usually mean the prefix needs tuning (Tuning invariants) or the seeds/dictionary need work.