Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9faf339
Update CMakeLists to use Fuzzing Framework
Jun 3, 2025
154681b
Update README
Jun 3, 2025
06a761e
Add macro generation code
Jun 3, 2025
2443964
Add specific mocks
Jun 3, 2025
1a795eb
Add run script
Jun 3, 2025
eb8a40c
Update harnesses
Jun 3, 2025
3b73ef6
Simplified version of macros generation
Jun 4, 2025
5eb1e12
Move context functions to app mocks.h
Jun 4, 2025
054865d
Delete fuzzing/macros/generated/macros.txt
Gustavo-Jodar Jun 4, 2025
766a9d4
Delete fuzzing/macros/generated/used_macros.json
Gustavo-Jodar Jun 4, 2025
1ac8d25
Ignore generated macros and verify if macros/generated exists
Jun 4, 2025
43ad9ef
Ignore generated macros and verify if macros/generated exists
Jun 4, 2025
e5e395b
Simplify local_run and CMakeLists
Jun 11, 2025
46f416b
CLang format
Jun 11, 2025
cc567bd
Fix try_context os_long_jump mock
Jun 11, 2025
9ec1455
Minor fixes for long_jump_mock
Jun 12, 2025
4b8c174
Remove unused variables
Jun 12, 2025
acab7fb
Use sigsetjmp
Jun 23, 2025
2e66a0d
Add custom_macros command
Jul 4, 2025
00497f2
Use the interface from the sdk and make it more simple
Jul 4, 2025
e5d5019
Move harness to folder
Jul 4, 2025
2888dc8
pre-commit
Jul 4, 2025
9130cb7
move to harness
Jul 4, 2025
8536afa
Improve structure of framework
Jul 18, 2025
a94414a
Exclude USE_OS_IO_STACK macro
Aug 4, 2025
3a277cb
Update READMe.md
Aug 4, 2025
5e85d76
Update clusterFuzz
Oct 6, 2025
db2e8a4
Update README
Oct 6, 2025
c53f23a
remove casting warning
Oct 6, 2025
29ef6a8
Use macro USE_OS_IO_STACK
Oct 6, 2025
ced17aa
cast to handle warning
Oct 7, 2025
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
11 changes: 7 additions & 4 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
FROM ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest AS LITE_BUILDER
FROM ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest AS app-builder

# Base image with clang toolchain
FROM gcr.io/oss-fuzz-base/base-builder:v1

RUN pip3 install --break-system-packages --no-cache-dir pillow>=3.4.0
RUN apt update && apt install -y ninja-build zip

# Copy the project's source code.
COPY . $SRC/app-boilerplate
COPY --from=LITE_BUILDER /opt/ledger-secure-sdk $SRC/app-boilerplate/BOLOS_SDK
COPY . /app
COPY --from=app-builder /opt/flex-secure-sdk /ledger-secure-sdk

# Working directory for build.sh
WORKDIR $SRC/app-boilerplate
WORKDIR /app

# Copy build.sh into $SRC dir.
COPY ./.clusterfuzzlite/build.sh $SRC/
24 changes: 21 additions & 3 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,26 @@

# build fuzzers

export BOLOS_SDK=/ledger-secure-sdk

pushd fuzzing
cmake -DBOLOS_SDK=../BOLOS_SDK -Bbuild -H.
make -C build
mv ./build/fuzz_tx_parser "${OUT}"
cmake -S . -B build -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Debug \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not calling here directly the build.sh from the SDK? It seems it is a duplicate file 🤔 ?

-G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DBOLOS_SDK=${BOLOS_SDK} -DTARGET=flex \
-DAPP_BUILD_PATH=/app

# Generates .zip for initial corpus in clusterFuzz
for dir in harness/*; do
if [ -d "$dir" ]; then
fuzzer_name=$(basename "$dir")
zip_name="${fuzzer_name}_seed_corpus.zip"
echo "Zipping corpus from $dir into $zip_name"

(cd "$dir" && zip -q -r "$zip_name" .)

mv "$dir/$zip_name" "$OUT/"
fi
done
cmake --build build
mv ./build/fuzz_* "${OUT}"
popd
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ unit-tests/coverage.info
fuzzing/build/
fuzzing/corpus/
fuzzing/out/
fuzzing/macros/generated/

# Python
*.pyc[cod]
Expand Down
85 changes: 45 additions & 40 deletions fuzzing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
cmake_minimum_required(VERSION 3.10)
include_guard()
cmake_minimum_required(VERSION 3.14)

if(${CMAKE_VERSION} VERSION_LESS 3.10)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
if(${CMAKE_VERSION} VERSION_LESS 3.14)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()

# project information
project(FuzzTxParser
VERSION 1.0
DESCRIPTION "Fuzzing of transaction parser"
LANGUAGES C)

# guard against bad build-type strings
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()

if (NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR "Fuzzer needs to be built with Clang")
endif()

if (NOT DEFINED BOLOS_SDK)
message(FATAL_ERROR "BOLOS_SDK environment variable not found.")
endif()
project(
BoilerPlateFuzzer
VERSION 1.0
DESCRIPTION "App Boilerplate example Fuzzer"
LANGUAGES C)

# guard against in-source builds
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ")
endif()
set(DEFINES FUZZ)

# compatible with ClusterFuzzLite
if (NOT DEFINED ENV{LIB_FUZZING_ENGINE})
set(COMPILATION_FLAGS_ "-g -Wall -fsanitize=fuzzer,address,undefined")
else()
set(COMPILATION_FLAGS_ "$ENV{LIB_FUZZING_ENGINE} $ENV{CXXFLAGS}")
if(NOT DEFINED BOLOS_SDK)
message(FATAL_ERROR "BOLOS_SDK must be defined, CMake will exit.")
return()
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

string(REPLACE " " ";" COMPILATION_FLAGS ${COMPILATION_FLAGS_})

include(extra/TxParser.cmake)

add_executable(fuzz_tx_parser fuzz_tx_parser.c)

target_compile_options(fuzz_tx_parser PUBLIC ${COMPILATION_FLAGS})
target_link_options(fuzz_tx_parser PUBLIC ${COMPILATION_FLAGS})
target_link_libraries(fuzz_tx_parser PUBLIC txparser)
add_subdirectory(${BOLOS_SDK}/fuzzing ${CMAKE_CURRENT_BINARY_DIR}/ledger-secure-sdk EXCLUDE_FROM_ALL)

file(GLOB_RECURSE C_SOURCES "${CMAKE_SOURCE_DIR}/../src/*.c" "${CMAKE_SOURCE_DIR}/mock/*.c")
list(REMOVE_ITEM C_SOURCES "${CMAKE_SOURCE_DIR}/../src/app_main.c")

add_library(code_lib ${C_SOURCES})

target_include_directories(
code_lib
PUBLIC ${CMAKE_SOURCE_DIR}/../src/
${CMAKE_SOURCE_DIR}/../src/apdu/
${CMAKE_SOURCE_DIR}/../src/swap/
${CMAKE_SOURCE_DIR}/../src/handler/
${CMAKE_SOURCE_DIR}/../src/helper/
${CMAKE_SOURCE_DIR}/../src/transaction/
${CMAKE_SOURCE_DIR}/../src/ui/
${CMAKE_SOURCE_DIR}/../src/ui/action/
${CMAKE_SOURCE_DIR}/mock/
${CMAKE_SOURCE_DIR}/)

target_link_libraries(code_lib PUBLIC secure_sdk)
target_compile_definitions(code_lib PUBLIC ${DEFINES} FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1)

# fuzz_dispatcher
add_executable(fuzz_dispatcher "${CMAKE_SOURCE_DIR}/harness/fuzz_dispatcher.c")
target_compile_definitions(fuzz_dispatcher PUBLIC macros FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1)
target_link_libraries(fuzz_dispatcher PUBLIC secure_sdk code_lib)

# fuzz_tx_parser
add_executable(fuzz_tx_parser "${CMAKE_SOURCE_DIR}/harness/fuzz_tx_parser.c")
target_compile_definitions(fuzz_tx_parser PUBLIC macros FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1)
target_link_libraries(fuzz_tx_parser PUBLIC secure_sdk code_lib)
126 changes: 91 additions & 35 deletions fuzzing/README.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,126 @@
<!-- markdownlint-disable MD013 -->

# Fuzzing on transaction parser

## Fuzzing

Fuzzing allows us to test how a program behaves when provided with invalid, unexpected, or random data as input.
Fuzzing allows us to test how a program behaves when provided with invalid, unexpected, or random
data as input.

In the case of the harness `fuzz_tx_parser.c`, we want to test the code that is responsible for
parsing the transaction data, which is `transaction_deserialize()`.

To test `transaction_deserialize()`, our fuzz target, `fuzz_tx_parser.c`, needs to implement
`int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)`, which provides an array of random
bytes that can be used to simulate a serialized transaction.

In the case of `app-boilerplate` we want to test the code that is responsible for parsing the transaction data,
which is `transaction_deserialize()`.
To test `transaction_deserialize()`, our fuzz target, `fuzz_tx_parser.c`,
needs to implement `int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)`,
which provides an array of random bytes that can be used to simulate a serialized transaction.
If the application crashes, or a [sanitizer](https://github.com/google/sanitizers) detects any kind of
access violation, the fuzzing process is stopped, a report regarding the vulnerability is shown,
If the application crashes, or a [sanitizer](https://github.com/google/sanitizers) detects any kind
of access violation, the fuzzing process is stopped, a report regarding the vulnerability is shown,
and the input that triggered the bug is written to disk under the name `crash-*`.

The vulnerable input file created can be passed as an argument to the fuzzer to triage the issue.

> **Note**: Usually we want to write a separate fuzz target for each functionality.

However, it is also possible to target the main function/dispatcher, so that we can cover more code,
as it is done in `fuzz_dispatcher.c`.

## Manual usage based on Ledger container

### Preparation

The fuzzer can run from the docker `ledger-app-builder-legacy`. You can download it from the `ghcr.io` docker repository:
The fuzzer can be run using the Docker image `ledger-app-dev-tools`. You can download it from the
`ghcr.io` docker repository:

```console
sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest
```bash
docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest
```

You can then enter this development environment by executing the following command from the repository root directory:
You can then enter this development environment by executing the following command from the
repository root directory:

```console
sudo docker run --rm -ti --user "$(id -u):$(id -g)" -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest
```bash
docker run --rm -ti -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest
```

### Compilation
### Writing your Harness

Once in the container, go into the `fuzzing` folder to compile the fuzzer:
When writing your harness, keep the following points in mind:

```console
cd fuzzing
- An SDK's interface for compilation is provided via the target `secure_sdk` in CMakeLists.txt
- If you are running it for the first time, consider using the script `local_run` from inside the
Docker container using the flag build=1, if you need to manually
add/remove macros you can then do it using the files macros/add_macros.txt or
macros/exclude_macros.txt and rerunning it, or directly change the macros/generated/macros.txt.
- A typical harness looks like this:

# cmake initialization
cmake -DBOLOS_SDK=/opt/ledger-secure-sdk -DCMAKE_C_COMPILER=/usr/bin/clang -Bbuild -H.
```C

# Fuzzer compilation
make -C build
```
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (sigsetjmp(fuzz_exit_jump_ctx.jmp_buf, 1)) return 0;

### Run
### harness code ###

return 0;
}

```

```console
./build/fuzz_tx_parser
This allows a return point when the `os_sched_exit()` function is mocked.

- To provide an SDK interface, we automatically generate syscall mock functions located in
`SECURE_SDK_PATH/fuzzing/mock/generated/generated_syscalls.c`, if you need a more specific mock,
you can define it in `APP_PATH/fuzzing/mock` with the same name and without the WEAK attribute.

### Compile and run the fuzzer from the container

Once inside the container, navigate to the `fuzzing` folder to compile the fuzzer:

```bash
export BOLOS_SDK=/opt/flex-secure-sdk

cd fuzzing

${BOLOS_SDK}/fuzzing/local_run.sh --build=1 \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be rather use the script from the SDK?

--BOLOS_SDK=${BOLOS_SDK} \
--fuzzer=build/fuzz_dispatcher \
--j=4 \
--run-fuzzer=1 \
--compute-coverage=1
```

## Full usage based on `clusterfuzzlite` container
### About local_run.sh

| Parameter | Type | Description |
| :--------------------- | :------------------ | :------------------------------------------------------------------- |
| `--BOLOS_SDK` | `PATH TO BOLOS SDK` | **Required**. Path to the BOLOS SDK |
| `--build` | `bool` | **Optional**. Whether to build the project (default: 0) |
| `--fuzzer` | `PATH` | **Required**. Path to the fuzzer binary |
| `--compute-coverage` | `bool` | **Optional**. Whether to compute coverage after fuzzing (default: 0) |
| `--run-fuzzer` | `bool` | **Optional**. Whether to run or not the fuzzer (default: 0) |
| `--run-crash` | `FILENAME` | **Optional**. Run the on a specific crash input file (default: 0) |
| `--sanitizer` | `address or memory` | **Optional**. Compile with sanitizer (default: address) |
| `--j` | `int` | **Optional**. N-parallel jobs for build and fuzzing (default: 1) |
| `--help` | | **Optional**. Display help message |

### Visualizing code coverage

After running your fuzzer, if `--compute-coverage=1` the coverage will be available in your browser.

## Full usage based on `clusterfuzzlite` container - TODO after SDK FUZZING RELEASE

Exactly the same context as the CI, directly using the `clusterfuzzlite` environment.

More info can be found here:
<https://google.github.io/clusterfuzzlite/>
More info can be found here: <https://google.github.io/clusterfuzzlite/>

### Preparation

The principle is to build the container, and run it to perform the fuzzing.

> **Note**: The container contains a copy of the sources (they are not cloned),
> which means the `docker build` command must be re-executed after each code modification.
> **Note**: The container contains a copy of the sources (they are not cloned), which means the
> `docker build` command must be re-executed after each code modification.

```console
```bash
# Prepare directory tree
mkdir fuzzing/{corpus,out}
# Container generation
Expand All @@ -75,12 +129,14 @@ docker build -t app-boilerplate --file .clusterfuzzlite/Dockerfile .

### Compilation

```console
```bash
docker run --rm --privileged -e FUZZING_LANGUAGE=c -v "$(realpath .)/fuzzing/out:/out" -ti app-boilerplate
```

### Run

```console
docker run --rm --privileged -e FUZZING_ENGINE=libfuzzer -e RUN_FUZZER_MODE=interactive -v "$(realpath .)/fuzzing/corpus:/tmp/fuzz_corpus" -v "$(realpath .)/fuzzing/out:/out" -ti gcr.io/oss-fuzz-base/base-runner run_fuzzer fuzz_tx_parser
```bash
docker run --rm --privileged -e FUZZING_ENGINE=libfuzzer -e RUN_FUZZER_MODE=interactive -v
"$(realpath .)/fuzzing/corpus:/tmp/fuzz_corpus" -v "$(realpath .)/fuzzing/out:/out"
-ti gcr.io/oss-fuzz-base/base-runner run_fuzzer fuzz_tx_parser
```
31 changes: 0 additions & 31 deletions fuzzing/extra/TxParser.cmake

This file was deleted.

Loading
Loading