|
Embedded SDK
Embedded SDK
|
This page assumes an app that already ships a fuzzing/ folder. To add one, see Integrating an app.
The framework runs out of the box in the standard ledger-app-builder/ledger-app-dev-tools image, which ships Clang, the matching llvm-* tools, and Ninja. The only required setup is BOLOS_SDK pointing at an SDK checkout. On first configure, CMake downloads the pinned Absolution release; set LEDGER_FUZZ_ABSOLUTION_LOCAL_DIR to a local install to run offline (see The CMake integration).
Run the campaign script from the SDK, pointing it at the app. The last argument is an optional run name (a UTC timestamp is used if omitted):
BOLOS_SDK=/path/to/ledger-secure-sdk \ "$BOLOS_SDK"/fuzzing/scripts/app-campaign.sh \ --app-dir /path/to/app my-campaign
Prefer an absolute --app-dir (or export APP_DIR=...) so the run does not depend on the current directory.
The SDK's own targets are fuzzed the same way, by pointing at the SDK and its self-fuzz subtree:
"$BOLOS_SDK"/fuzzing/scripts/app-campaign.sh \ --app-dir "$BOLOS_SDK" \ --fuzz-subdir fuzzing/sdk-fuzz sdk-sanity
Each run writes to <app>/.fuzz-artifacts/<run-name>/:
| Path | Contents |
|---|---|
report/index.html | Source-level coverage for the whole run |
global-coverage.txt | One-line coverage summary (all files) |
key-files-coverage.txt | Coverage restricted to the manifest's key_files |
targets/<target>/corpus/ | The merged corpus, plus its .compat-key |
targets/<target>/crashes/ | Any crash / timeout / OOM inputs found |
targets/<target>/sanitizer-reports.txt | Recoverable sanitizer diagnostics, by site and count |
targets/<target>/<target>.dict | Dictionary used for the run |
targets/<target>/meta.env | Build parameters (prefix size, build dirs, workers…) |
targets/<target>/replay.log | Coverage-replay log |
A clean run finds zero crashes. When a crash is found, its input is saved under crashes/ and the summary is printed to the console.
runtime error: ... and carries on, so libFuzzer never treats the input as a crash and writes no artifact. The campaign therefore scans the worker logs and prints every reporting site, also saving them to sanitizer-reports.txt. Read that file — a run can exit 0 while having reported real integer truncations or overflows. Unsigned overflow specifically is compiled with -fsanitize-recover=unsigned-integer-overflow because deliberate wrapping is common (PRNGs, hashing), so it will always appear here rather than as a crash.Replay a saved input against the sanitizer ("fast") binary to get the full stack trace:
"<build-dir-fast>/<target>" \ "<app>/.fuzz-artifacts/<run>/targets/<target>/crashes/crash-<hash>"
<build-dir-fast> is reported in meta.env (default <app>/build/fast). The binary is an ordinary LibFuzzer executable, so all of its flags (-runs=1, -help=1, …) are available.
| Flag | Description |
|---|---|
--app-dir DIR | Path to the app root (required) |
--fuzz-subdir DIR | Fuzzing subtree relative to the app (default fuzzing) |
--target NAME | Restrict a multi-target app to one target (repeatable) |
--clean | Delete build directories before configuring (full rebuild) |
Short defaults keep the first run fast; raise the duration and workers for real coverage growth. Only the first three change what a campaign measures; the rest are plumbing. Values that decide what is measured but that nobody ever set – the LibFuzzer seed, the per-input timeout, value profiling, and the input-length budget – are fixed constants in the script rather than environment variables, so two runs cannot silently differ.
| Variable | Default | Meaning |
|---|---|---|
FUZZ_TIME | 90 | Seconds of fuzzing per target, per worker |
WORKERS | min(2, nproc) | Parallel LibFuzzer processes |
BUILD_TYPE | RelWithDebInfo | CMake build type for both build trees |
BASE_CORPUS_ZIP | app's fuzzing/base-corpus.zip if present | Promoted seeds; set empty to skip |
OVERWRITE | unset | Set to 1 to overwrite an existing run directory |
APP_TARGET | flex | Device target passed to CMake (flex, stax, …) |
BUILD_JOBS | from CPU count | Parallel cmake --build jobs |
SKIP_INVARIANT_SYNC | unset | Keep the current .zon instead of re-syncing it |
BOOTSTRAP_INVARIANT | auto | Reset each invariants/<target>.zon to .{} before configure. 1 always, 0 never, auto only with --clean. CI should set 1 |
ARTIFACTS_ROOT | <app>/.fuzz-artifacts | Root directory for run output |
BUILD_DIR_FAST / BUILD_DIR_COV | <app>/build/fast and .../cov | Sanitizer binary vs coverage-replay binary |
LEDGER_FUZZ_ABSOLUTION_LOCAL_DIR | unset | Local Absolution install; skips the download |
The default (90 s, two workers) is a smoke test. For meaningful coverage growth or regression hunting, raise the budget, for example:
FUZZ_TIME=3600 WORKERS=4 \ "$BOLOS_SDK"/fuzzing/scripts/app-campaign.sh --app-dir /path/to/app long-run
When a long run finds good coverage, promote its corpus so the next run and CI start from it — see Corpus and compatibility keys.
libFuzzer names an artifact by what happened, and only one kind is a finding:
| prefix | meaning |
|---|---|
crash- | a real finding: sanitizer report, assertion, or signal |
oom- | the run hit -rss_limit_mb |
timeout- | one input exceeded -timeout, which also ends that worker |
leak- | LeakSanitizer at exit |
The campaign counts them separately; only the crash- count is reported as crashes.
TIMEOUT_SEC (default 2) bounds a single input. libFuzzer treats a timeout as an error: it writes an artifact and the worker exits, so a slow-but-valid input costs the rest of that worker's campaign. Raise it for long unattended runs; measured mean input cost on app-bitcoin-new is about 1 ms.
WORKERS and FUZZ_TIME are the other two knobs. Each worker keeps a private corpus and they are merged at the end, so workers do not share finds during a run.