diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile
index 1bbdb7d5a..0ebd40670 100644
--- a/.clusterfuzzlite/Dockerfile
+++ b/.clusterfuzzlite/Dockerfile
@@ -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/
diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh
index 08b092a69..703d7fa7c 100644
--- a/.clusterfuzzlite/build.sh
+++ b/.clusterfuzzlite/build.sh
@@ -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 \
+ -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
diff --git a/.gitignore b/.gitignore
index 1e3520cde..5d8eec1bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ unit-tests/coverage.info
fuzzing/build/
fuzzing/corpus/
fuzzing/out/
+fuzzing/macros/generated/
# Python
*.pyc[cod]
diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt
index 702d20f6a..12d59f916 100644
--- a/fuzzing/CMakeLists.txt
+++ b/fuzzing/CMakeLists.txt
@@ -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)
diff --git a/fuzzing/README.md b/fuzzing/README.md
index 9b0b5325a..424d67a8e 100644
--- a/fuzzing/README.md
+++ b/fuzzing/README.md
@@ -1,72 +1,126 @@
+
+
# 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 \
+ --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:
-
+More info can be found here:
### 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
@@ -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
```
diff --git a/fuzzing/extra/TxParser.cmake b/fuzzing/extra/TxParser.cmake
deleted file mode 100644
index 27349a0c8..000000000
--- a/fuzzing/extra/TxParser.cmake
+++ /dev/null
@@ -1,31 +0,0 @@
-# project information
-project(TxParser
- VERSION 1.0
- DESCRIPTION "Transaction parser of Boilerplate app"
- LANGUAGES C)
-
-# specify C standard
-set(CMAKE_C_STANDARD 11)
-set(CMAKE_C_STANDARD_REQUIRED True)
-set(CMAKE_C_FLAGS_DEBUG
- "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-unused-function -DFUZZ -pedantic -g -O0"
-)
-
-add_library(txparser
- ${BOLOS_SDK}/lib_standard_app/format.c
- ${BOLOS_SDK}/lib_standard_app/buffer.c
- ${BOLOS_SDK}/lib_standard_app/read.c
- ${BOLOS_SDK}/lib_standard_app/varint.c
- ${BOLOS_SDK}/lib_standard_app/bip32.c
- ${BOLOS_SDK}/lib_standard_app/write.c
- ${CMAKE_CURRENT_SOURCE_DIR}/../src/transaction/utils.c
- ${CMAKE_CURRENT_SOURCE_DIR}/../src/transaction/deserialize.c
-)
-
-set_target_properties(txparser PROPERTIES SOVERSION 1)
-
-target_include_directories(txparser PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/../src
- ${CMAKE_CURRENT_SOURCE_DIR}/../src/transaction
- ${BOLOS_SDK}/lib_standard_app
-)
diff --git a/fuzzing/harness/fuzz_dispatcher.c b/fuzzing/harness/fuzz_dispatcher.c
new file mode 100644
index 000000000..042a7763b
--- /dev/null
+++ b/fuzzing/harness/fuzz_dispatcher.c
@@ -0,0 +1,46 @@
+#include
+#include
+#include
+#include
+#include "globals.h"
+#include "dispatcher.h"
+#include "mocks.h"
+
+global_ctx_t G_context;
+const internal_storage_t N_storage_real;
+
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+#message "Use this macro for code only needed in fuzz targets"
+#endif
+
+void print_apdu(command_t cmd) {
+ printf("=> CLA=%02x, INS=%02x, P1=%02x, P2=%02x, LC=%02x, DATA=",
+ cmd.cla,
+ cmd.ins,
+ cmd.p1,
+ cmd.p2,
+ cmd.lc);
+
+ for (int i = 0; i < cmd.lc; i++) {
+ printf("%02X", cmd.data[i]);
+ }
+ printf("\n");
+}
+
+// Fuzz entry point
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (sigsetjmp(fuzz_exit_jump_ctx.jmp_buf, 1)) return 0;
+
+ if (size < 4) return 0;
+
+ command_t cmd;
+ cmd.cla = data[0];
+ cmd.ins = data[1];
+ cmd.p1 = data[2];
+ cmd.p2 = data[3];
+ cmd.lc = size - 4;
+ if (size > 4) cmd.data = (uint8_t *) &data[4];
+ // print_apdu(cmd);
+ apdu_dispatcher(&cmd);
+ return 0;
+}
diff --git a/fuzzing/fuzz_tx_parser.c b/fuzzing/harness/fuzz_tx_parser.c
similarity index 67%
rename from fuzzing/fuzz_tx_parser.c
rename to fuzzing/harness/fuzz_tx_parser.c
index 1d43f5c43..e56293e2a 100644
--- a/fuzzing/fuzz_tx_parser.c
+++ b/fuzzing/harness/fuzz_tx_parser.c
@@ -3,12 +3,17 @@
#include
#include
-#include "deserialize.h"
-#include "utils.h"
-#include "tx_types.h"
+#include "transaction/deserialize.h"
+#include "transaction/utils.h"
+#include "transaction/tx_types.h"
#include "format.h"
+#include "mock/mocks.h"
+#include
+jmp_buf fuzz_exit_jump_buf;
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (sigsetjmp(fuzz_exit_jump_ctx.jmp_buf, 1)) return 0;
+
buffer_t buf = {.ptr = data, .size = size, .offset = 0};
transaction_t tx;
parser_status_e status;
@@ -23,13 +28,13 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (status == PARSING_OK) {
format_u64(nonce, sizeof(nonce), tx.nonce);
- printf("nonce: %s\n", nonce);
+ // printf("nonce: %s\n", nonce);
format_hex(tx.to, ADDRESS_LEN, address, sizeof(address));
- printf("address: %s\n", address);
+ // printf("address: %s\n", address);
format_fpu64(amount, sizeof(amount), tx.value, 3); // exponent of smallest unit is 3
- printf("amount: %s\n", amount);
+ // printf("amount: %s\n", amount);
transaction_utils_format_memo(tx.memo, tx.memo_len, tx_memo, sizeof(tx_memo));
- printf("memo: %s\n", tx_memo);
+ // printf("memo: %s\n", tx_memo);
}
return 0;
diff --git a/fuzzing/macros/add_macros.txt b/fuzzing/macros/add_macros.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/fuzzing/macros/exclude_macros.txt b/fuzzing/macros/exclude_macros.txt
new file mode 100644
index 000000000..d2c773362
--- /dev/null
+++ b/fuzzing/macros/exclude_macros.txt
@@ -0,0 +1,2 @@
+HAVE_SHA512_WITH_BLOCK_ALT_METHOD
+PRINTF(...)=
diff --git a/fuzzing/mock/mocks.c b/fuzzing/mock/mocks.c
new file mode 100644
index 000000000..c399a21d2
--- /dev/null
+++ b/fuzzing/mock/mocks.c
@@ -0,0 +1,38 @@
+#include "mocks.h"
+
+// APPs expect a specific length
+cx_err_t cx_ecdomain_parameters_length(cx_curve_t cv, size_t *length) {
+ (void) cv;
+ *length = (size_t) 32;
+ return 0x00000000;
+}
+
+// Simulates writing to NVM
+void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) {
+ if (!dst_adr || !src_adr || src_len == 0) {
+ return;
+ }
+ memcpy(dst_adr, src_adr, src_len);
+}
+
+try_context_t fuzz_exit_jump_ctx = {0};
+try_context_t *G_exception_context = &fuzz_exit_jump_ctx;
+
+try_context_t *try_context_get(void) {
+ return G_exception_context;
+}
+
+try_context_t *try_context_set(try_context_t *context) {
+ try_context_t *previous = G_exception_context;
+ G_exception_context = context;
+ return previous;
+}
+
+void __attribute__((noreturn))
+os_sched_exit(bolos_task_status_t exit_code __attribute__((unused))) {
+ longjmp(fuzz_exit_jump_ctx.jmp_buf, 1);
+}
+
+void __attribute__((noreturn)) os_lib_end(void) {
+ longjmp(fuzz_exit_jump_ctx.jmp_buf, 1);
+}
diff --git a/fuzzing/mock/mocks.h b/fuzzing/mock/mocks.h
new file mode 100644
index 000000000..c5ea4dfb6
--- /dev/null
+++ b/fuzzing/mock/mocks.h
@@ -0,0 +1,23 @@
+#include "cx_errors.h"
+#include "ox_ec.h"
+#include "os_task.h"
+#include
+#include
+#include "exceptions.h"
+#include
+#include
+
+// to simulate exiting makes a long_jump to fuzzer harness
+extern try_context_t fuzz_exit_jump_ctx;
+
+try_context_t *try_context_get(void);
+
+try_context_t *try_context_set(try_context_t *context);
+
+void __attribute__((noreturn)) os_sched_exit(bolos_task_status_t exit_code);
+
+void __attribute__((noreturn)) os_lib_end(void);
+
+cx_err_t cx_ecdomain_parameters_length(cx_curve_t cv, size_t *length);
+
+void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len);