diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile
new file mode 100644
index 0000000000..f65fbeac44
--- /dev/null
+++ b/.clusterfuzzlite/Dockerfile
@@ -0,0 +1,14 @@
+FROM ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest AS LITE_BUILDER
+
+# Base image with clang toolchain
+FROM gcr.io/oss-fuzz-base/base-builder:v1
+
+# Copy the project's source code.
+COPY . $SRC/app-ethereum
+COPY --from=LITE_BUILDER /opt/ledger-secure-sdk $SRC/app-ethereum/BOLOS_SDK
+
+# Working directory for build.sh
+WORKDIR $SRC/app-ethereum
+
+# Copy build.sh into $SRC dir.
+COPY ./.clusterfuzzlite/build.sh $SRC/
diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh
new file mode 100644
index 0000000000..782a29b3a2
--- /dev/null
+++ b/.clusterfuzzlite/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -eu
+
+# build fuzzers
+
+pushd tests/fuzzing
+cmake -DBOLOS_SDK=../BOLOS_SDK -Bbuild -H.
+make -C build
+mv ./build/fuzz_app_eth "${OUT}"
+popd
diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml
new file mode 100644
index 0000000000..b455aa397c
--- /dev/null
+++ b/.clusterfuzzlite/project.yaml
@@ -0,0 +1 @@
+language: c
diff --git a/.github/workflows/cflite_cron.yml b/.github/workflows/cflite_cron.yml
new file mode 100644
index 0000000000..17c1e65a23
--- /dev/null
+++ b/.github/workflows/cflite_cron.yml
@@ -0,0 +1,40 @@
+name: ClusterFuzzLite cron tasks
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main # Use your actual default branch here.
+ schedule:
+ - cron: '0 13 * * 6' # At 01:00 PM, only on Saturday
+permissions: read-all
+jobs:
+ Fuzzing:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - mode: batch
+ sanitizer: address
+ - mode: batch
+ sanitizer: memory
+ - mode: prune
+ sanitizer: address
+ - mode: coverage
+ sanitizer: coverage
+ steps:
+ - name: Build Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }})
+ id: build
+ uses: google/clusterfuzzlite/actions/build_fuzzers@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ language: c # Change this to the language you are fuzzing.
+ sanitizer: ${{ matrix.sanitizer }}
+ - name: Run Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }})
+ id: run
+ uses: google/clusterfuzzlite/actions/run_fuzzers@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ fuzz-seconds: 300 # 5 minutes
+ mode: ${{ matrix.mode }}
+ sanitizer: ${{ matrix.sanitizer }}
diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml
new file mode 100644
index 0000000000..09f91dafe6
--- /dev/null
+++ b/.github/workflows/cflite_pr.yml
@@ -0,0 +1,43 @@
+name: ClusterFuzzLite PR fuzzing
+on:
+ pull_request:
+ paths:
+ - '**'
+permissions: read-all
+jobs:
+ PR:
+ runs-on: ubuntu-latest
+ concurrency:
+ group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
+ cancel-in-progress: true
+ strategy:
+ fail-fast: false
+ matrix:
+ sanitizer: [address, undefined, memory] # Override this with the sanitizers you want.
+ steps:
+ - name: Build Fuzzers (${{ matrix.sanitizer }})
+ id: build
+ uses: google/clusterfuzzlite/actions/build_fuzzers@v1
+ with:
+ language: c # Change this to the language you are fuzzing.
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ sanitizer: ${{ matrix.sanitizer }}
+ # Optional but recommended: used to only run fuzzers that are affected
+ # by the PR.
+ # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git
+ # storage-repo-branch: main # Optional. Defaults to "main"
+ # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages".
+ - name: Run Fuzzers (${{ matrix.sanitizer }})
+ id: run
+ uses: google/clusterfuzzlite/actions/run_fuzzers@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ fuzz-seconds: 300 # 5 minutes
+ mode: 'code-change'
+ sanitizer: ${{ matrix.sanitizer }}
+ output-sarif: true
+ # Optional but recommended: used to download the corpus produced by
+ # batch fuzzing.
+ # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git
+ # storage-repo-branch: main # Optional. Defaults to "main"
+ # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages".
diff --git a/tests/fuzzing/CMakeLists.txt b/tests/fuzzing/CMakeLists.txt
new file mode 100644
index 0000000000..ea36c8abd0
--- /dev/null
+++ b/tests/fuzzing/CMakeLists.txt
@@ -0,0 +1,170 @@
+cmake_minimum_required(VERSION 3.10)
+
+if(${CMAKE_VERSION} VERSION_LESS 3.10)
+ cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+endif()
+
+# project information
+project(Fuzzer
+ VERSION 1.0
+ DESCRIPTION "Eth Fuzzer"
+ LANGUAGES C)
+
+set(CMAKE_C_COMPILER clang)
+
+set(CMAKE_BUILD_TYPE "Debug")
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_FLAGS
+ "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-embedded-directive -g -O0 -fsanitize=fuzzer,address,undefined -fprofile-instr-generate -fcoverage-mapping"
+)
+
+# 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(TARGET_DEVICE flex)
+set(BOLOS_SDK /opt/${TARGET_DEVICE}-secure-sdk)
+if (NOT DEFINED BOLOS_SDK)
+ message(FATAL_ERROR "BOLOS_SDK environment variable not found.")
+endif()
+
+set(DEFINES
+ gcc
+ APPNAME=\"Fuzzing\"
+ API_LEVEL=21
+ TARGET=\"flex\"
+ TARGET_NAME=\"TARGET_FLEX\"
+ APPVERSION=\"1.1.0\"
+ SDK_NAME=\"ledger-secure-sdk\"
+ SDK_VERSION=\"v21.3.3\"
+ SDK_HASH=\"d88d4db3c93665f52b5b1f45099d9d36dfaa06ba\"
+ gcc
+ __IO=volatile
+ NDEBUG
+ HAVE_BAGL_FONT_INTER_REGULAR_28PX
+ HAVE_BAGL_FONT_INTER_SEMIBOLD_28PX
+ HAVE_BAGL_FONT_INTER_MEDIUM_36PX
+ HAVE_INAPP_BLE_PAIRING
+ HAVE_NBGL
+ HAVE_PIEZO_SOUND
+ HAVE_SE_TOUCH
+ HAVE_SE_EINK_DISPLAY
+ NBGL_PAGE
+ NBGL_USE_CASE
+ SCREEN_SIZE_WALLET
+ HAVE_FAST_HOLD_TO_APPROVE
+ HAVE_LEDGER_PKI
+ HAVE_NES_CRYPT
+ HAVE_ST_AES
+ NATIVE_LITTLE_ENDIAN
+ HAVE_CRC
+ HAVE_HASH
+ HAVE_RIPEMD160
+ HAVE_SHA224
+ HAVE_SHA256
+ HAVE_SHA3
+ HAVE_SHA384
+ HAVE_SHA512
+ HAVE_SHA512_WITH_BLOCK_ALT_METHOD
+ HAVE_SHA512_WITH_BLOCK_ALT_METHOD_M0
+ HAVE_BLAKE2
+ HAVE_HMAC
+ HAVE_PBKDF2
+ HAVE_AES
+ HAVE_MATH
+ HAVE_RNG
+ HAVE_RNG_RFC6979
+ HAVE_RNG_SP800_90A
+ HAVE_ECC
+ HAVE_ECC_WEIERSTRASS
+ HAVE_ECC_TWISTED_EDWARDS
+ HAVE_ECC_MONTGOMERY
+ HAVE_SECP256K1_CURVE
+ HAVE_SECP256R1_CURVE
+ HAVE_SECP384R1_CURVE
+ HAVE_SECP521R1_CURVE
+ HAVE_FR256V1_CURVE
+ HAVE_STARK256_CURVE
+ HAVE_BRAINPOOL_P256R1_CURVE
+ HAVE_BRAINPOOL_P256T1_CURVE
+ HAVE_BRAINPOOL_P320R1_CURVE
+ HAVE_BRAINPOOL_P320T1_CURVE
+ HAVE_BRAINPOOL_P384R1_CURVE
+ HAVE_BRAINPOOL_P384T1_CURVE
+ HAVE_BRAINPOOL_P512R1_CURVE
+ HAVE_BRAINPOOL_P512T1_CURVE
+ HAVE_BLS12_381_G1_CURVE
+ HAVE_CV25519_CURVE
+ HAVE_CV448_CURVE
+ HAVE_ED25519_CURVE
+ HAVE_ED448_CURVE
+ HAVE_ECDH
+ HAVE_ECDSA
+ HAVE_EDDSA
+ HAVE_ECSCHNORR
+ HAVE_X25519
+ HAVE_X448
+ HAVE_AES_GCM
+ HAVE_CMAC
+ HAVE_AES_SIV
+ COIN_VARIANT=1
+ HAVE_BOLOS_APP_STACK_CANARY
+ IO_SEPROXYHAL_BUFFER_SIZE_B=300
+ HAVE_BLE
+ BLE_COMMAND_TIMEOUT_MS=2000
+ HAVE_BLE_APDU
+ BLE_SEGMENT_SIZE=32
+ HAVE_DEBUG_THROWS
+ NBGL_QRCODE
+ MAJOR_VERSION=1
+ MINOR_VERSION=1
+ PATCH_VERSION=0
+ IO_HID_EP_LENGTH=64
+ HAVE_SPRINTF
+ HAVE_SNPRINTF_FORMAT_U
+ HAVE_IO_USB
+ HAVE_L4_USBLIB
+ IO_USB_MAX_ENDPOINTS=4
+ HAVE_USB_APDU
+ USB_SEGMENT_SIZE=64
+ HAVE_WEBUSB
+ WEBUSB_URL_SIZE_B=0
+ WEBUSB_URL=
+ OS_IO_SEPROXYHAL
+ STANDARD_APP_SYNC_RAPDU
+ HAVE_GENERIC_TX_PARSER
+ HAVE_TRUSTED_NAME
+ HAVE_DYN_MEM_ALLOC
+ HAVE_SWAP
+ HAVE_ENUM_VALUE
+ HAVE_NFT_SUPPORT
+)
+set(DEFINE ${DEFINES} HAVE_PRINTF PRINTF=printf)
+
+add_compile_definitions(${DEFINES})
+
+FILE(GLOB_RECURSE SDK_STD_SOURCES ${BOLOS_SDK}/lib_standard_app/write.c src/mock.c)
+
+
+include_directories(
+ ${CMAKE_SOURCE_DIR}/../../ethereum-plugin-sdk/src/
+ ${CMAKE_SOURCE_DIR}/../../src
+ ${CMAKE_SOURCE_DIR}/../../src_features/provideDynamicNetwork/
+ ${BOLOS_SDK}/include
+ ${BOLOS_SDK}/lib_standard_app
+ ${BOLOS_SDK}/target/${TARGET_DEVICE}/include
+ ${BOLOS_SDK}/lib_cxng/include
+ ${BOLOS_SDK}/lib_cxng/src
+ ${BOLOS_SDK}/lib_ux_nbgl
+ ${BOLOS_SDK}/lib_nbgl/include
+ ${CMAKE_SOURCE_DIR}/src
+)
+
+FILE(GLOB_RECURSE SOURCES
+ ${CMAKE_SOURCE_DIR}/../../src_features/provideDynamicNetwork/*.c
+ ${CMAKE_SOURCE_DIR}/../../src/hash_bytes.c
+)
+
+add_executable(fuzz_app_eth src/fuzz_app_eth.c ${SDK_STD_SOURCES} ${SOURCES})
diff --git a/tests/fuzzing/README.md b/tests/fuzzing/README.md
new file mode 100644
index 0000000000..7dc17ab567
--- /dev/null
+++ b/tests/fuzzing/README.md
@@ -0,0 +1,83 @@
+# Fuzzing Tests
+
+## Fuzzing
+
+Fuzzing allows us to test how a program behaves when provided with invalid, unexpected, or random data as input.
+
+Our fuzz target 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 buffer.
+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.
+
+## 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:
+
+```console
+sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest
+```
+
+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
+```
+
+### Compilation
+
+Once in the container, go into the `tests/fuzzing` folder to compile the fuzzer:
+
+```console
+cd tests/fuzzing
+
+# cmake initialization
+cmake -DBOLOS_SDK=/opt/ledger-secure-sdk -DCMAKE_C_COMPILER=/usr/bin/clang -Bbuild -H.
+
+# Fuzzer compilation
+make -C build
+```
+
+### Run
+
+```console
+./build/fuzz_app_eth
+```
+
+## Full usage based on `clusterfuzzlite` container
+
+Exactly the same context as the CI, directly using the `clusterfuzzlite` environment.
+
+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.
+
+```console
+# Prepare directory tree
+mkdir tests/fuzzing/{corpus,out}
+# Container generation
+docker build -t app-ethereum --file .clusterfuzzlite/Dockerfile .
+```
+
+### Compilation
+
+```console
+docker run --rm --privileged -e FUZZING_LANGUAGE=c -v "$(realpath .)/tests/fuzzing/out:/out" -ti app-ethereum
+```
+
+### Run
+
+```console
+docker run --rm --privileged -e FUZZING_ENGINE=libfuzzer -e RUN_FUZZER_MODE=interactive -v "$(realpath .)/tests/fuzzing/corpus:/tmp/fuzz_corpus" -v "$(realpath .)/tests/fuzzing/out:/out" -ti gcr.io/oss-fuzz-base/base-runner run_fuzzer fuzz_app_eth
+```
diff --git a/tests/fuzzing/src/fuzz_app_eth.c b/tests/fuzzing/src/fuzz_app_eth.c
new file mode 100644
index 0000000000..c4f55501aa
--- /dev/null
+++ b/tests/fuzzing/src/fuzz_app_eth.c
@@ -0,0 +1,36 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "shared_context.h"
+#include "network_dynamic.h"
+
+unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE];
+tmpContent_t tmpContent;
+const chain_config_t *chainConfig;
+txContext_t txContext;
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ explicit_bzero(G_io_apdu_buffer, 500);
+ explicit_bzero(&tmpContent, sizeof(tmpContent_t));
+ explicit_bzero(&txContext, sizeof(txContext_t));
+ size_t offset = 0;
+ size_t len = 0;
+ uint8_t p1;
+ uint8_t p2;
+ unsigned int tx;
+
+ while (size - offset > 4) {
+ if (data[offset++] == 0) break;
+ p1 = data[offset++];
+ p2 = data[offset++];
+ len = data[offset++];
+ if (size - offset < len) return 0;
+ handleNetworkConfiguration(p1, p2, data + offset, len, &tx);
+ offset += len;
+ }
+ return 0;
+}
diff --git a/tests/fuzzing/src/glyphs.h b/tests/fuzzing/src/glyphs.h
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/fuzzing/src/mock.c b/tests/fuzzing/src/mock.c
new file mode 100644
index 0000000000..886990a21b
--- /dev/null
+++ b/tests/fuzzing/src/mock.c
@@ -0,0 +1,93 @@
+#include
+
+#include "cx_errors.h"
+#include "cx_sha256.h"
+#include "cx_sha3.h"
+
+cx_err_t cx_sha256_init_no_throw(cx_sha256_t *hash) {
+ memset(hash, 0, sizeof(cx_sha256_t));
+ return CX_OK;
+}
+
+cx_err_t cx_hash_no_throw(cx_hash_t *hash,
+ uint32_t mode,
+ const uint8_t *in,
+ size_t len,
+ uint8_t *out,
+ size_t out_len) {
+ if (len > 0 && out_len > 0) out[out_len - 1] = in[len - 1];
+ return CX_OK;
+}
+
+void assert_exit(bool confirm) {
+ exit(1);
+}
+
+cx_err_t cx_keccak_256_hash_iovec(const cx_iovec_t *iovec,
+ size_t iovec_len,
+ uint8_t digest[static CX_KECCAK_256_SIZE]) {
+ digest[CX_KECCAK_256_SIZE - 1] = 0;
+ return CX_OK;
+}
+
+cx_err_t cx_sha256_hash_iovec(const cx_iovec_t *iovec,
+ size_t iovec_len,
+ uint8_t digest[static CX_SHA256_SIZE]) {
+ digest[CX_SHA256_SIZE - 1] = 0;
+ return CX_OK;
+}
+
+int check_signature_with_pubkey(const char *tag,
+ uint8_t *buffer,
+ const uint8_t bufLen,
+ const uint8_t *PubKey,
+ const uint8_t keyLen,
+#ifdef HAVE_LEDGER_PKI
+ const uint8_t keyUsageExp,
+#endif
+ uint8_t *signature,
+ const uint8_t sigLen) {
+ return CX_OK;
+}
+
+uint64_t u64_from_BE(const uint8_t *in, uint8_t size) {
+ uint8_t i = 0;
+ uint64_t res = 0;
+
+ while (i < size && i < sizeof(res)) {
+ res <<= 8;
+ res |= in[i];
+ i++;
+ }
+
+ return res;
+}
+
+bool u64_to_string(uint64_t src, char *dst, uint8_t dst_size) {
+ // Copy the numbers in ASCII format.
+ uint8_t i = 0;
+ do {
+ // Checking `i + 1` to make sure we have enough space for '\0'.
+ if (i + 1 >= dst_size) {
+ return false;
+ }
+ dst[i] = src % 10 + '0';
+ src /= 10;
+ i++;
+ } while (src);
+
+ // Null terminate string
+ dst[i] = '\0';
+
+ // Revert the string
+ i--;
+ uint8_t j = 0;
+ while (j < i) {
+ char tmp = dst[i];
+ dst[i] = dst[j];
+ dst[j] = tmp;
+ i--;
+ j++;
+ }
+ return true;
+}