Add Absolution-based stateful fuzzing framework#1585
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1585 +/- ##
=======================================
Coverage 91.87% 91.88%
=======================================
Files 39 39
Lines 4653 4656 +3
Branches 596 598 +2
=======================================
+ Hits 4275 4278 +3
Misses 266 266
Partials 112 112
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
8f36ba5 to
e007cf6
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces an Absolution-based, coverage-guided state-aware fuzzing framework for the Ledger Secure SDK and opt-in Ledger apps, and includes four small defensive SDK fixes uncovered by fuzzing (NULL/empty-input handling + UB shift fix). It also replaces the previous ClusterFuzzLite workflow setup with a single reusable workflow.
Changes:
- Add the new fuzzing framework structure (generic harness, mutators, mock layer, campaign scripts, invariants/layout tooling, sanitizer configs) plus SDK self-fuzz targets under
fuzzing/sdk-fuzz/. - Replace/retire legacy fuzz harnesses, docs, and scripts in favor of the new pipeline (
app-campaign.sh+ manifest-driven seed/dictionary handling). - Apply four one-line SDK library hardening fixes (TLV, swap error helper, SHA-256 update, CRC32 bit-reverse).
Reviewed changes
Copilot reviewed 95 out of 101 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
lib_tlv/tlv_library.c |
Guard strnlen/memcpy against empty TLV values. |
lib_standard_app/swap_error_code_helpers.c |
Guard memcpy when buffer count is zero. |
lib_cxng/src/cx_sha256.c |
Guard memcpy when len == 0 in cx_sha256_update. |
lib_cxng/src/cx_crc32.c |
Fix signed-shift UB by using uint32_t shifts. |
fuzzing/sdk-fuzz/mock/mocks.h |
SDK self-fuzz harness globals declarations. |
fuzzing/sdk-fuzz/mock/mocks.c |
SDK self-fuzz harness globals definitions + BSS stub. |
fuzzing/sdk-fuzz/mock/mock_check_signature_with_pki.c |
PKI signature-check stub for TLV fuzzing targets. |
fuzzing/sdk-fuzz/macros/exclude_macros.txt |
SDK self-fuzz macro exclusions. |
fuzzing/sdk-fuzz/macros/add_macros.txt |
SDK self-fuzz extra macros. |
fuzzing/sdk-fuzz/invariants/zero-symbols.txt |
Placeholder list for per-target invariant zeroing policy. |
fuzzing/sdk-fuzz/invariants/domain-overrides.txt |
Placeholder for per-target invariant domain overrides. |
fuzzing/sdk-fuzz/harness/fuzz_tlv_trusted_name.c |
Self-fuzz target for TLV trusted-name use case. |
fuzzing/sdk-fuzz/harness/fuzz_tlv_dynamic_descriptor.c |
Self-fuzz target for TLV dynamic-descriptor use case. |
fuzzing/sdk-fuzz/harness/fuzz_qrcodegen.c |
Self-fuzz target for qrcodegen encode paths. |
fuzzing/sdk-fuzz/harness/fuzz_nfc_ndef.c |
Self-fuzz target for NFC NDEF parsing/formatting. |
fuzzing/sdk-fuzz/harness/fuzz_bip32.c |
Self-fuzz target for BIP32 path read/format round-trips. |
fuzzing/sdk-fuzz/harness/fuzz_base58.c |
Self-fuzz target for base58 encode/decode round-trips. |
fuzzing/sdk-fuzz/harness/fuzz_apdu_parser.c |
Self-fuzz target for APDU parsing correctness invariants. |
fuzzing/sdk-fuzz/harness/fuzz_alloc.c |
Self-fuzz target for allocator operations stream. |
fuzzing/sdk-fuzz/harness/fuzz_alloc_utils.c |
Self-fuzz target for app_mem_utils allocator helpers. |
fuzzing/sdk-fuzz/fuzz-manifest.toml |
Multi-target manifest describing SDK self-fuzz targets and dictionaries. |
fuzzing/sdk-fuzz/CMakeLists.txt |
Build Absolution-based SDK self-fuzz targets. |
fuzzing/scripts/update-scenario-layout.py |
Extract Absolution prefix/control offsets into scenario_layout.h. |
fuzzing/scripts/tune-invariant-domains.py |
Apply post-sync domain overrides to .zon invariants. |
fuzzing/scripts/sync-invariant.py |
Sync Absolution-discovered invariants + apply zero-symbol policy. |
fuzzing/scripts/generate-seeds.py |
Manifest-driven seed generation orchestrator. |
fuzzing/scripts/generate-seed-corpus-generic.py |
Generic seed corpus generator (CLA/INS-driven). |
fuzzing/scripts/fuzz_seed_utils.py |
Shared helpers for resolving prefix/layout and generating ctrl bytes. |
fuzzing/scripts/fuzz_manifest.py |
TOML manifest reader/validator + dictionary/compat key helpers. |
fuzzing/scripts/app-config.sh |
Read manifest and export campaign pipeline variables. |
fuzzing/scripts/app-common.sh |
Shared campaign helpers (configure/build/sync/layout/merge/etc.). |
fuzzing/sanitizers/ubsan-suppressions.txt |
Runtime UBSan suppressions placeholder. |
fuzzing/sanitizers/ubsan-options.env |
UBSan runtime options for campaigns. |
fuzzing/sanitizers/ubsan-ignorelist.txt |
Compile-time sanitizer ignorelist (special-case list). |
fuzzing/sanitizers/load-options.sh |
Load sanitizer runtime env options from .env files. |
fuzzing/sanitizers/asan-options.env |
ASan runtime options for campaigns. |
fuzzing/README.md |
Rewrite docs to match the new framework & workflow. |
fuzzing/mock/tlv_mutator.c |
TLV grammar-aware custom mutator implementation. |
fuzzing/mock/README.md |
Document SDK fuzz mock layer layout and conventions. |
fuzzing/mock/os/pic.c |
PIC translation mock implementations. |
fuzzing/mock/os/os_runtime.c |
Host-side OS/libc runtime mocks (strlcpy/strlcat, etc.). |
fuzzing/mock/os/os_exceptions.c |
Exception/NVM mocks; os_sched_exit longjmps to harness. |
fuzzing/mock/mock.cmake |
Build the mock library + syscall stub generation. |
fuzzing/mock/cx/cx_crypto.c |
Crypto/perso mocks + fuzz-controlled failure injection. |
fuzzing/mock/cx/cx_bn_ec.c |
Big-number and EC-point mock implementations. |
fuzzing/mock/custom/pic.c |
Remove legacy custom mock (superseded by new mock layer). |
fuzzing/mock/custom/os_task.c |
Remove legacy custom mock (superseded by new mock layer). |
fuzzing/mock/custom/mocks.c |
Remove legacy custom mocks (superseded by new mock layer). |
fuzzing/mock/custom/main.c |
Remove legacy custom mock entrypoint bits. |
fuzzing/macros/macros.cmake |
Improve macro extraction/override logic for app vs SDK builds. |
fuzzing/local_run.sh |
Remove legacy local-run wrapper script (superseded by campaign). |
fuzzing/libs/lib_tlv.cmake |
Update TLV lib build rules for new framework graph. |
fuzzing/libs/lib_standard_app.cmake |
Update standard_app lib build rules for new framework graph. |
fuzzing/libs/lib_qrcode.cmake |
Update qrcode lib build rules for new framework graph. |
fuzzing/libs/lib_pki.cmake |
Update PKI lib build rules for new framework graph. |
fuzzing/libs/lib_nfc.cmake |
Update NFC lib build rules for new framework graph. |
fuzzing/libs/lib_nbgl.cmake |
Update NBGL lib build rules; exclude nbgl_use_case sources. |
fuzzing/libs/lib_lists.cmake |
Update lists lib build rules for new framework graph. |
fuzzing/libs/lib_io.cmake |
Update IO lib build rules for new framework graph. |
fuzzing/libs/lib_glyphs.cmake |
Update glyph generation rules for new framework graph. |
fuzzing/libs/lib_cxng.cmake |
Update cxng lib build rules; now includes mock module. |
fuzzing/libs/lib_alloc.cmake |
Update alloc lib build rules for new framework graph. |
fuzzing/invariants/sdk-zero-symbols.txt |
Introduce SDK/NBGL global zero-symbol policy. |
fuzzing/include/tlv_mutator.h |
Public TLV mutator interface + optional dispatch helper. |
fuzzing/include/fuzz_mutator.h |
Prefix-aware custom mutator helper for Absolution split inputs. |
fuzzing/include/fuzz_layout_check.h |
Compile-time layout consistency checks. |
fuzzing/include/fuzz_harness.h |
Generic Absolution APDU harness entry helper. |
fuzzing/include/fuzz_defs.h |
Shared constants and command spec type for fuzz harnesses. |
fuzzing/harness/fuzzer_qrcodegen.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/harness/fuzzer_bip32.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/harness/fuzzer_base58.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/harness/fuzzer_apdu_parser.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/harness/fuzzer_alloc.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/harness/fuzzer_alloc_utils.c |
Remove legacy SDK harness (replaced by sdk-fuzz targets). |
fuzzing/extra/qrcodegen.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/extra/lib_lists.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/extra/lib_alloc.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/extra/bip32.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/extra/base58.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/extra/apdu_parser.cmake |
Remove legacy per-fuzzer cmake (replaced by sdk-fuzz CMakeLists). |
fuzzing/docs/fuzz_lists.md |
Remove legacy per-fuzzer doc (superseded by new docs). |
fuzzing/docs/CAMPAIGN_WORKFLOW.md |
Add detailed campaign workflow reference. |
fuzzing/CMakeLists.txt |
Rework fuzzing build graph + sanitizer flags + secure_sdk target. |
fuzzing/cmake/LedgerAppFuzz.cmake |
Add public app entrypoint module + Absolution fetching/wrappers. |
.gitignore |
Ignore generated fuzz artifacts, sdk-fuzz invariants/layout, coverage outputs. |
.github/workflows/clusterfuzzlite.yml |
Replace legacy CFL workflows with one reusable workflow. |
.github/workflows/cflite_pr.yml |
Remove legacy CFL PR workflow. |
.github/workflows/cflite_cron.yml |
Remove legacy CFL cron workflow. |
.clusterfuzzlite/Dockerfile |
Update CFL Dockerfile to match new sdk-fuzz build flow. |
.clusterfuzzlite/build.sh |
Build Absolution-based sdk-fuzz targets and export fuzzers to $OUT. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9b675fa to
7e7650e
Compare
There was a problem hiding this comment.
Just few comments.
For me it's almost ready, and for you? Do you plan any new changes?
In order to understand if it can be easily extended I've coded with Claude the Fuzzing framework nbgl page addon (can be merged either into this PR or later directly to master or refused if incorrect).
Also there are few Copilot warnings and a rebase to do.
49b4056 to
7813e8c
Compare
7813e8c to
27b7984
Compare
Found by SDK fuzzing (UBSan): 1 << i is undefined for a 32-bit signed int at i == 31. Shift as uint32_t instead.
Found by SDK fuzzing (UBSan): skip the copy when len == 0 so a NULL data pointer is never passed to memcpy.
Found by SDK fuzzing (UBSan): guard the scan and copy on value.size > 0 so a NULL value pointer is never passed to strnlen/memcpy.
Found by SDK fuzzing (UBSan): skip the buffer copy when count == 0 so a NULL pointer is never passed to memcpy.
27b7984 to
338b423
Compare
Description
This PR adds a coverage-guided, state-aware fuzzing framework for the Secure SDK (and Ledger apps), built on Absolution, plus four small SDK library fixes the framework uncovered.
Stateful Ledger apps are multi-APDU protocols that classic stateless harnesses can't reach. Instead of replaying full APDU chains or emulating the device, Absolution injects global state from a declarative model: the fuzzer input is split into
[ prefix | tail ], the prefix restores the app's globals to a target state, and the tail is fed to the real APDU dispatcher. This reaches deep protocol logic at near-stateless speed.One generic harness drives any app's dispatcher; the SDK mock layer replaces hardware-dependent syscalls, crypto, and NBGL; and
scripts/app-campaign.shruns the full pipeline (build → sync invariants → fuzz → LLVM coverage report). Apps opt in by adding a smallfuzzing/subtree and includingcmake/LedgerAppFuzz.cmake; nothing is copied out of the SDK.Main additions:
fuzzing/cmake/LedgerAppFuzz.cmake— public entry point; exposes asecure_sdktarget aggregating the SDK libraries, mocks, and framework headers.fuzzing/mock/— SDK-wide syscall / crypto / OS / NBGL mocks.fuzzing/sdk-fuzz/— 10 SDK self-fuzz targets (alloc, base58, bip32, APDU parser, lists, NFC NDEF, qrcodegen, TLV…).fuzzing/template/— scaffold for new apps;fuzzing/docs/— app contract + campaign workflow..clusterfuzzlite/+.github/workflows/clusterfuzzlite.yml— ClusterFuzzLite CI (replaces the previouscflite_cron.yml/cflite_pr.yml).SDK library fixes found by the framework — each isolated in its own cherry-pickable commit with a "found by fuzzing" note:
lib_cxng: signed-int shift UB inreverse_32_bits.lib_cxng: NULLmemcpyincx_sha256_updateon empty input.lib_tlv: NULLstrnlen/memcpyon an empty TLV value.lib_standard_app: NULLmemcpyinsend_swap_error_with_buffers.Changes include
Additional comments
cflite_*workflows are replaced by the Absolution-driven targets and a singleclusterfuzzlite.yml, covering the same libraries and more.Auto cherry-pick in API_LEVEL
[ ] TARGET_API_LEVEL: API_LEVEL_26