Skip to content

Commit

Permalink
Add fuzzing in CI
Browse files Browse the repository at this point in the history
Change submodules to track using https instead of git protocol,
since ClusterFuzzLite does not support git submodules.

Copy from https://github.com/LedgerHQ/app-plugin-boilerplate:
- /.clusterfuzzlite/
- /.github/workflows/cflite_{cron,pr}.yml
- /fuzzing/

Change "plugin.h" imports to "kiln_plugin.h" in `/fuzzing/`.
  • Loading branch information
qbLedger authored and turtQb committed Dec 18, 2024
1 parent 10eee14 commit 246c52c
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 2 deletions.
14 changes: 14 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -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-plugin-boilerplate
COPY --from=LITE_BUILDER /opt/nanox-secure-sdk $SRC/app-plugin-boilerplate/BOLOS_SDK

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

# Copy build.sh into $SRC dir.
COPY ./.clusterfuzzlite/build.sh $SRC/
9 changes: 9 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash -eu

# build fuzzers

pushd fuzzing
cmake -DBOLOS_SDK=../BOLOS_SDK -Bbuild -H.
make -C build
mv ./build/fuzz "${OUT}"
popd
1 change: 1 addition & 0 deletions .clusterfuzzlite/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: c
41 changes: 41 additions & 0 deletions .github/workflows/cflite_cron.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 }}

43 changes: 43 additions & 0 deletions .github/workflows/cflite_pr.yml
Original file line number Diff line number Diff line change
@@ -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".
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "ethereum-plugin-sdk"]
path = ethereum-plugin-sdk
url = git@github.com:LedgerHQ/ethereum-plugin-sdk.git
url = https://github.com/LedgerHQ/ethereum-plugin-sdk
[submodule "app-ethereum"]
path = app-ethereum
url = git@github.com:LedgerHQ/app-ethereum.git
url = https://github.com/LedgerHQ/app-ethereum
117 changes: 117 additions & 0 deletions fuzzing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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 "Contract parser of Boilerplate plugin app"
LANGUAGES C)

# guard against bad build-type strings
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
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()

# 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()

# specify C standard
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)

# compatible with ClusterFuzzLite
if (NOT DEFINED ENV{LIB_FUZZING_ENGINE})
set(COMPILATION_FLAGS_ "-fsanitize=fuzzer,address,undefined,signed-integer-overflow")
else()
set(COMPILATION_FLAGS_ "$ENV{LIB_FUZZING_ENGINE} $ENV{CFLAGS}")
endif()
string(REPLACE " " ";" COMPILATION_FLAGS ${COMPILATION_FLAGS_})

add_compile_options(-Wall -Wextra -g -pedantic)
# Just to limit compilation warnings of the plugin sources
add_compile_options(-Wno-implicit-function-declaration)
# Flag depending on the Build Type
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ggdb2 -O3")

set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src")
set(ETH_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../ethereum-plugin-sdk")

add_compile_definitions(
APPNAME="PluginBoilerplate"
)

add_compile_definitions(
IO_HID_EP_LENGTH=64
HAVE_ECC
HAVE_ECC_WEIERSTRASS
HAVE_SECP_CURVES
HAVE_ECC_TWISTED_EDWARDS
HAVE_ED_CURVES
HAVE_ECDSA
HAVE_EDDSA
HAVE_HASH
HAVE_BLAKE2
HAVE_SHA224
HAVE_SHA256
HAVE_SHA3
HAVE_SHA512
)

include_directories(
${BOLOS_SDK}/include
${BOLOS_SDK}/lib_standard_app
${BOLOS_SDK}/lib_cxng/include
${BOLOS_SDK}/lib_cxng/src
${BOLOS_SDK}/target/nanox/include
${ETH_DIR}/src
${SRC_DIR}
)

# Take all source files from the application and the sdk
file(GLOB_RECURSE APPLICATION_SRC
# Take all plugin sources
${SRC_DIR}/*.c

# Take all sdk sources
${ETH_DIR}/src/*.c
)
# Filter out main.c from the SDK, the fuzzing has its own main
list(FILTER APPLICATION_SRC EXCLUDE REGEX "${ETH_DIR}/src/main")

add_executable(fuzz
${APPLICATION_SRC}

# fuzzing specific files
fuzz_plugin.c
mocks.c

# sdk utils
${BOLOS_SDK}/src/ledger_assert.c
${BOLOS_SDK}/lib_standard_app/format.c

# cxng
${BOLOS_SDK}/lib_cxng/src/cx_hash.c
${BOLOS_SDK}/lib_cxng/src/cx_sha256.c
${BOLOS_SDK}/lib_cxng/src/cx_sha512.c
${BOLOS_SDK}/lib_cxng/src/cx_sha3.c
${BOLOS_SDK}/lib_cxng/src/cx_blake2b.c
${BOLOS_SDK}/lib_cxng/src/cx_utils.c
${BOLOS_SDK}/lib_cxng/src/cx_ram.c
)

target_compile_options(fuzz PUBLIC ${COMPILATION_FLAGS})
target_link_options(fuzz PUBLIC ${COMPILATION_FLAGS})
87 changes: 87 additions & 0 deletions fuzzing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Fuzzing on transaction parser

[//]: # (Comment)
[//]: # (This file when in the plugin-boilerplate repository is included in the Ethereum Plugin SDK Github page, keep that in mind when editing it.)

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

In the case of `ledger-plugin-coinbase-staking` we want to test the code that is responsible for handling the contract data.
The fuzzer 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, 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

Before being able to use the fuzzing tests, the environment must be prepared with all submodules.
To install them, use the following command in the repository root directory:

```shell
git submodule update --init
```

The fuzzer can run from the docker `ledger-app-builder-legacy`. You can download it from the `ghcr.io` docker repository:

```shell
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:

```shell
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 `fuzzing` folder to compile the fuzzer:

```shell
cd fuzzing

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

# Fuzzer compilation
make -C build
```

### Run

```shell
./build/fuzz
```

## Full usage based on `clusterfuzzlite` container

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

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.
```shell
# Prepare directory tree
mkdir fuzzing/{corpus,out}
# Container generation
docker build -t app-plugin-boilerplate --file .clusterfuzzlite/Dockerfile .
```

### Compilation

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

### Run

```shell
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
```
Loading

0 comments on commit 246c52c

Please sign in to comment.