-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
161 changed files
with
5,345 additions
and
1,405 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# This container is used to build Meshtastic with the libraries required by the fuzzer. | ||
# ClusterFuzzLite starts the container, runs the build.sh script, and then exits. | ||
|
||
# As this is not a long running service, health-checks are not required. ClusterFuzzLite | ||
# also only works if the user remains unchanged from the base image (it expects to run | ||
# as root). | ||
# trunk-ignore-all(trivy/DS026): No healthcheck is needed for this builder container | ||
# trunk-ignore-all(checkov/CKV_DOCKER_2): No healthcheck is needed for this builder container | ||
# trunk-ignore-all(checkov/CKV_DOCKER_3): We must run as root for this container | ||
# trunk-ignore-all(trivy/DS002): We must run as root for this container | ||
# trunk-ignore-all(checkov/CKV_DOCKER_8): We must run as root for this container | ||
# trunk-ignore-all(hadolint/DL3002): We must run as root for this container | ||
|
||
FROM gcr.io/oss-fuzz-base/base-builder:v1 | ||
|
||
ENV PIP_ROOT_USER_ACTION=ignore | ||
|
||
# trunk-ignore(hadolint/DL3008): apt packages are not pinned. | ||
# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned. | ||
RUN apt-get update && apt-get install --no-install-recommends -y \ | ||
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \ | ||
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \ | ||
libusb-1.0-0-dev libssl-dev pkg-config && \ | ||
apt-get clean && rm -rf /var/lib/apt/lists/* && \ | ||
pip install --no-cache-dir -U \ | ||
platformio==6.1.16 \ | ||
grpcio-tools==1.68.1 \ | ||
meshtastic==2.5.9 | ||
|
||
# Ugly hack to avoid clang detecting a conflict between the math "log" function and the "log" function in framework-portduino/cores/portduino/logging.h | ||
RUN sed -i -e 's/__MATHCALL_VEC (log,, (_Mdouble_ __x));//' /usr/include/x86_64-linux-gnu/bits/mathcalls.h | ||
|
||
# A few dependencies are too old on the base-builder image. More recent versions are built from source. | ||
WORKDIR $SRC | ||
RUN git config --global advice.detachedHead false && \ | ||
git clone --depth 1 --branch 0.8.0 https://github.com/jbeder/yaml-cpp.git && \ | ||
git clone --depth 1 --branch v2.3.3 https://github.com/babelouest/orcania.git && \ | ||
git clone --depth 1 --branch v1.4.20 https://github.com/babelouest/yder.git && \ | ||
git clone --depth 1 --branch v2.7.15 https://github.com/babelouest/ulfius.git | ||
|
||
COPY ./.clusterfuzzlite/build.sh $SRC/ | ||
|
||
WORKDIR $SRC/firmware | ||
COPY . $SRC/firmware/ | ||
|
||
# https://docs.platformio.org/en/latest/envvars.html | ||
ENV PLATFORMIO_CORE_DIR=$SRC/pio/core \ | ||
PLATFORMIO_LIBDEPS_DIR=$SRC/pio/libdeps \ | ||
PLATFORMIO_PACKAGES_DIR=$SRC/pio/packages \ | ||
PLATFORMIO_SETTING_ENABLE_CACHE=No \ | ||
PIO_ENV=buildroot | ||
RUN platformio pkg install --environment $PIO_ENV |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# ClusterFuzzLite for Meshtastic | ||
|
||
This directory contains the fuzzer implementation for Meshtastic using the ClusterFuzzLite framework. | ||
See the [ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/) for more details. | ||
|
||
## Running locally | ||
|
||
ClusterFuzzLite uses the OSS-Fuzz toolchain. To build the fuzzer manually, first grab a copy of OSS-Fuzz. | ||
|
||
```shell | ||
git clone https://github.com/google/oss-fuzz.git | ||
cd oss-fuzz | ||
``` | ||
|
||
To build the fuzzer, run: | ||
|
||
```shell | ||
python3 infra/helper.py build_image --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY | ||
python3 infra/helper.py build_fuzzers --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY --sanitizer address | ||
``` | ||
|
||
To run the fuzzer, run: | ||
|
||
```shell | ||
python3 infra/helper.py run_fuzzer --external --corpus-dir=<path-to-temp-corpus-dir> $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY router_fuzzer | ||
``` | ||
|
||
More background on these commands can be found in the | ||
[ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/build-integration/#testing-locally). | ||
|
||
## router_fuzzer.cpp | ||
|
||
This fuzzer submits MeshPacket protos to the `Router::enqueueReceivedMessage` method. It takes the binary | ||
data from the fuzzer and decodes that data to a MeshPacket using nanopb. A few fields in | ||
the MeshPacket are modified by the fuzzer. | ||
|
||
- If the `to` field is 0, it will be replaced with the NodeID of the running node. | ||
- If the `from` field is 0, it will be replaced with the NodeID of the running node. | ||
- If the `id` field is 0, it will be replaced with an incrementing counter value. | ||
- If the `pki_encrypted` field is true, the `public_key` field will be populated with the first admin key. | ||
|
||
The `router_fuzzer_seed_corpus.py` file contains a list of MeshPackets. It is run from inside build.sh and | ||
writes the binary MeshPacket protos to files. These files are use used by the fuzzer as its initial seed data, | ||
helping the fuzzer to start off with a few known inputs. | ||
|
||
### Interpreting a fuzzer crash | ||
|
||
If the fuzzer crashes, it'll write the input bytes used for the test case to a file and notify about the | ||
location of that file. The contents of the file are a binary serialized MeshPacket protobuf. The following | ||
snippet of Python code can be used to parse the file into a human readable form. | ||
|
||
```python | ||
from meshtastic.protobuf import mesh_pb2 | ||
|
||
mesh_pb2.MeshPacket.FromString(open("crash-XXXX-file", "rb").read()) | ||
``` | ||
|
||
Consider adding any such crash results to the `router_fuzzer_seed_corpus.py` file to ensure there a isn't | ||
a future regression for that crash test case. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#!/bin/bash -eu | ||
|
||
# Build Meshtastic and a few needed dependencies using clang++ | ||
# and the OSS-Fuzz required build flags. | ||
|
||
env | ||
|
||
cd "$SRC" | ||
NPROC=$(nproc || echo 1) | ||
|
||
LDFLAGS=-lpthread cmake -S "$SRC/yaml-cpp" -B "$WORK/yaml-cpp/$SANITIZER" \ | ||
-DBUILD_SHARED_LIBS=OFF | ||
cmake --build "$WORK/yaml-cpp/$SANITIZER" -j "$NPROC" | ||
cmake --install "$WORK/yaml-cpp/$SANITIZER" --prefix /usr | ||
|
||
cmake -S "$SRC/orcania" -B "$WORK/orcania/$SANITIZER" \ | ||
-DBUILD_STATIC=ON | ||
cmake --build "$WORK/orcania/$SANITIZER" -j "$NPROC" | ||
cmake --install "$WORK/orcania/$SANITIZER" --prefix /usr | ||
|
||
cmake -S "$SRC/yder" -B "$WORK/yder/$SANITIZER" \ | ||
-DBUILD_STATIC=ON -DWITH_JOURNALD=OFF | ||
cmake --build "$WORK/yder/$SANITIZER" -j "$NPROC" | ||
cmake --install "$WORK/yder/$SANITIZER" --prefix /usr | ||
|
||
cmake -S "$SRC/ulfius" -B "$WORK/ulfius/$SANITIZER" \ | ||
-DBUILD_STATIC=ON -DWITH_JANSSON=OFF -DWITH_CURL=OFF -DWITH_WEBSOCKET=OFF | ||
cmake --build "$WORK/ulfius/$SANITIZER" -j "$NPROC" | ||
cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr | ||
|
||
cd "$SRC/firmware" | ||
|
||
PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py") | ||
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors) | ||
export PLATFORMIO_EXTRA_SCRIPTS | ||
export STATIC_LIBS | ||
export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER" | ||
export TARGET_CC=$CC | ||
export TARGET_CXX=$CXX | ||
export TARGET_LD=$CXX | ||
export TARGET_AR=llvm-ar | ||
export TARGET_AS=llvm-as | ||
export TARGET_OBJCOPY=llvm-objcopy | ||
export TARGET_RANLIB=llvm-ranlib | ||
|
||
mkdir -p "$OUT/lib" | ||
|
||
cp .clusterfuzzlite/*_fuzzer.options "$OUT/" | ||
|
||
for f in .clusterfuzzlite/*_fuzzer.cpp; do | ||
fuzzer=$(basename "$f" .cpp) | ||
cp -f "$f" src/fuzzer.cpp | ||
pio run -vvv --environment "$PIO_ENV" | ||
program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/program" | ||
cp "$program" "$OUT/$fuzzer" | ||
|
||
# Copy shared libraries used by the fuzzer. | ||
read -d '' -ra shared_libs < <(ldd "$program" | sed -n 's/[^=]\+=> \([^ ]\+\).*/\1/p') || true | ||
cp -f "${shared_libs[@]}" "$OUT/lib/" | ||
|
||
# Build the initial fuzzer seed corpus. | ||
corpus_name="${fuzzer}_seed_corpus" | ||
corpus_generator="$PWD/.clusterfuzzlite/${corpus_name}.py" | ||
if [[ -f $corpus_generator ]]; then | ||
mkdir "$corpus_name" | ||
pushd "$corpus_name" | ||
python3 "$corpus_generator" | ||
popd | ||
zip -D "$OUT/${corpus_name}.zip" "$corpus_name"/* | ||
fi | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""PlatformIO build script (post: runs after other Meshtastic scripts).""" | ||
|
||
import os | ||
import shlex | ||
|
||
from SCons.Script import DefaultEnvironment | ||
|
||
env = DefaultEnvironment() | ||
|
||
# Remove any static libraries from the LIBS environment. Static libraries are | ||
# handled in platformio-clusterfuzzlite-pre.py. | ||
static_libs = set(lib[2:] for lib in shlex.split(os.getenv("STATIC_LIBS"))) | ||
env.Replace( | ||
LIBS=[ | ||
lib for lib in env["LIBS"] if not (isinstance(lib, str) and lib in static_libs) | ||
], | ||
) | ||
|
||
# FrameworkArduino/portduino/main.cpp contains the "main" function the binary. | ||
# The fuzzing framework also provides a "main" function and needs to be run | ||
# before Meshtastic is started. We rename the "main" function for Meshtastic to | ||
# "portduino_main" here so that it can be called inside the fuzzer. | ||
env.AddPostAction( | ||
"$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o", | ||
env.VerboseAction( | ||
" ".join( | ||
[ | ||
"$OBJCOPY", | ||
"--redefine-sym=main=portduino_main", | ||
"$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o", | ||
] | ||
), | ||
"Renaming main symbol to portduino_main", | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
"""PlatformIO build script (pre: runs before other Meshtastic scripts). | ||
ClusterFuzzLite executes in a different container from the build. During the build, | ||
attempt to link statically to as many dependencies as possible. For dependencies that | ||
do not have static libraries, the shared library files are copied to the output | ||
directory by the build.sh script. | ||
""" | ||
|
||
import glob | ||
import os | ||
import shlex | ||
|
||
from SCons.Script import DefaultEnvironment, Literal | ||
|
||
env = DefaultEnvironment() | ||
|
||
cxxflags = shlex.split(os.getenv("CXXFLAGS")) | ||
sanitizer_flags = shlex.split(os.getenv("SANITIZER_FLAGS")) | ||
lib_fuzzing_engine = shlex.split(os.getenv("LIB_FUZZING_ENGINE")) | ||
statics = glob.glob("/usr/lib/lib*.a") + glob.glob("/usr/lib/*/lib*.a") | ||
no_static = set(("-ldl",)) | ||
|
||
|
||
def replaceStatic(lib): | ||
"""Replace -l<libname> with the static .a file for the library.""" | ||
if not lib.startswith("-l") or lib in no_static: | ||
return lib | ||
static_name = f"/lib{lib[2:]}.a" | ||
static = [s for s in statics if s.endswith(static_name)] | ||
if len(static) == 1: | ||
return static[0] | ||
return lib | ||
|
||
|
||
# Setup the environment for building with Clang and the OSS-Fuzz required build flags. | ||
env.Append( | ||
CFLAGS=os.getenv("CFLAGS"), | ||
CXXFLAGS=cxxflags, | ||
LIBSOURCE_DIRS=["/usr/lib/x86_64-linux-gnu"], | ||
LINKFLAGS=cxxflags | ||
+ sanitizer_flags | ||
+ lib_fuzzing_engine | ||
+ ["-stdlib=libc++", "-std=c++17"], | ||
_LIBFLAGS=[replaceStatic(s) for s in shlex.split(os.getenv("STATIC_LIBS"))] | ||
+ [ | ||
"/usr/lib/x86_64-linux-gnu/libunistring.a", # Needs to be at the end. | ||
# Find the shared libraries in a subdirectory named lib | ||
# within the same directory as the binary. | ||
Literal("-Wl,-rpath,$ORIGIN/lib"), | ||
"-Wl,-z,origin", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
language: c++ |
Oops, something went wrong.