Skip to content

Commit

Permalink
Merge pull request #227 from riebl/fuzz_harness
Browse files Browse the repository at this point in the history
Fuzz harness
  • Loading branch information
riebl authored May 27, 2024
2 parents a1ce9e7 + 59ef097 commit 067c01a
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ if(BUILD_BENCHMARK)
add_subdirectory(tools/benchmark)
endif()

option(BUILD_FUZZ "Build fuzz harness" OFF)
if(BUILD_FUZZ)
add_subdirectory(tools/fuzz-harness)
endif()

# interface library for convenience
get_property(_components GLOBAL PROPERTY VANETZA_COMPONENTS)
add_library(vanetza INTERFACE)
Expand Down
27 changes: 27 additions & 0 deletions docs/tools/fuzz-harness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# fuzz-harness

The fuzz harness for Vanetza is a testing tool designed to identify bugs, vulnerabilities, and unexpected behaviors within Vanetza's codebase. Fuzz testing, also known as fuzzing, involves providing invalid, unexpected, or random data as input to a program to uncover errors or security issues.

## Requirements

You will need AFL++ installed on your system.
Please refer to the [AFL++ documentation](https://aflplus.plus/docs/install/) for installation instructions.
Alternatively, you can use the scripts located at *tools/fuzz-harness* for running fuzz tests in a Docker container.

## Usage

Running the script *fuzz-harness/docker.sh* will build a suitable Docker container based on the official *aflplusplus/aflplusplus* image.
As soon as the container is ready, the script launches the built container and maps your local user and some Vanetza directories into it.

Within the container, you can compile the *fuzz-harness* using the AFL++ toolchain by invoking the *compile.sh* script.
The *fuzz.sh* script is a convenient way to run the built harness with *afl-fuzz*.
If it crashes immediately, try again a few times.

### Analyse

Fuzzing is executing the *fuzzing-persistent* executable.
You can use its sibling *fuzzing-run* to investigate a particular crash and get more information about the possible problems.
The address sanitizer is enabled by default. If you're not interested in memory leaks, make sure to disable the leak sanatizer by setting the environment variable `ASAN_OPTIONS=detect_leaks=0`.

You may also want to classify the found issues using *casr-afl*: `casr-afl -i output -o output/casr`
The classification process also eliminates duplicate issues.
1 change: 1 addition & 0 deletions tools/fuzz-harness/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output/
5 changes: 5 additions & 0 deletions tools/fuzz-harness/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
add_executable(fuzzing-persistent router_fuzzing_context.cpp persistent.cpp)
target_link_libraries(fuzzing-persistent PUBLIC vanetza)

add_executable(fuzzing-run router_fuzzing_context.cpp run.cpp)
target_link_libraries(fuzzing-run PUBLIC vanetza)
15 changes: 15 additions & 0 deletions tools/fuzz-harness/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ARG VERSION=latest
FROM aflplusplus/aflplusplus:${VERSION}

# install build dependencies for Vanetza
RUN apt-get update && apt-get install --no-install-recommends -y \
libboost-all-dev libcrypto++-dev libgeographic-dev libssl-dev

# install casr-afl tool
RUN cargo install --root /usr/local casr

# set up "fuzz" user and mapping of host user
RUN useradd -m -s /bin/bash fuzz
RUN cp /root/.bashrc /home/fuzz/.bashrc && chown fuzz:fuzz /home/fuzz/.bashrc
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
24 changes: 24 additions & 0 deletions tools/fuzz-harness/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash -eu

if [[ ! -d "/AFLplusplus" ]] ; then
echo "This script shall be run inside the AFL++ container"
exit 1
fi

cd /home/fuzz

export CC=${CC:=afl-clang-lto}
export CXX=${CXX:=afl-clang-lto++}

export AFL_LLVM_CMPLOG=1
mkdir -p build/cmplog
cmake -S source -B build/cmplog -G Ninja -DBUILD_FUZZ=ON
cmake --build build/cmplog
unset AFL_LLVM_CMPLOG

# see https://aflplus.plus/docs/env_variables/ for supported environment variables
export AFL_USE_ASAN=1
export AFL_USE_UBSAN=1
mkdir -p build/asan
cmake -S source -B build/asan -G Ninja -DBUILD_FUZZ=ON
cmake --build build/asan
11 changes: 11 additions & 0 deletions tools/fuzz-harness/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash -eu
afl-system-config

usermod -u ${HOST_USER_ID} -g ${HOST_GROUP_ID} fuzz
ln -sf /source /home/fuzz/source
ln -sf /input /home/fuzz/input
ln -sf /output /home/fuzz/output
ln -sf /source/tools/fuzz-harness/compile.sh /home/fuzz/compile.sh
ln -sf /source/tools/fuzz-harness/fuzz.sh /home/fuzz/fuzz.sh
cd /home/fuzz
su fuzz
15 changes: 15 additions & 0 deletions tools/fuzz-harness/docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash -eu
HARNESS_DIR=$(realpath $(dirname $0))
SOURCE_DIR=$HARNESS_DIR/../..

docker build $HARNESS_DIR
IMAGE=$(docker build -q $HARNESS_DIR)

mkdir -p $HARNESS_DIR/output
docker run --rm -it \
--security-opt seccomp=unconfined \
-v$SOURCE_DIR:/source:ro \
-v$HARNESS_DIR/input:/input:ro \
-v$HARNESS_DIR/output:/output \
-e HOST_USER_ID=$(id -u) -e HOST_GROUP_ID=$(id -g) \
$IMAGE
5 changes: 5 additions & 0 deletions tools/fuzz-harness/fuzz.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -eu
: ${FUZZ_INPUT:="$HOME/input"}
: ${FUZZ_OUTPUT:="$HOME/output"}
: ${FUZZ_BUILD:="$HOME/build"}
afl-fuzz -i $FUZZ_INPUT -o $FUZZ_OUTPUT -c $FUZZ_BUILD/cmplog/bin/fuzzing-persistent -m none -- $FUZZ_BUILD/asan/bin/fuzzing-persistent
Binary file added tools/fuzz-harness/input/cam_v3_certificate.dat
Binary file not shown.
34 changes: 34 additions & 0 deletions tools/fuzz-harness/persistent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "router_fuzzing_context.hpp"
#include <stdio.h>
#include <unistd.h>

#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif

__AFL_FUZZ_INIT();

int main()
{
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif

vanetza::RouterFuzzingContext context;

unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
vanetza::ByteBuffer buffer { buf, buf + len };
context.initialize();
context.indicate(std::move(buffer));
}

return 0;
}
44 changes: 44 additions & 0 deletions tools/fuzz-harness/router_fuzzing_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "router_fuzzing_context.hpp"

namespace vanetza
{

class FuzzingRequestInterface : public dcc::RequestInterface
{
void request(const dcc::DataRequest&, std::unique_ptr<ChunkPacket>) override {}
};

class FuzzingTransportInterface : public geonet::TransportInterface
{
void indicate(const geonet::DataIndication&, std::unique_ptr<geonet::UpPacket>) override {}
};

RouterFuzzingContext::RouterFuzzingContext() :
runtime(vanetza::Clock::at("2010-12-23 18:29")), security(runtime),
req_ifc(std::make_unique<FuzzingRequestInterface>()),
ind_ifc(std::make_unique<FuzzingTransportInterface>())
{
initialize();
}

void RouterFuzzingContext::initialize()
{
router = std::make_unique<geonet::Router>(runtime, mib);
router->set_security_entity(&security.entity());
router->set_access_interface(req_ifc.get());
router->set_transport_handler(geonet::UpperProtocol::BTP_B, ind_ifc.get());

geonet::Address gn_addr;
gn_addr.mid(MacAddress{0, 0, 0, 0, 0, 1});
router->set_address(gn_addr);
}

void RouterFuzzingContext::indicate(ByteBuffer&& buffer)
{
MacAddress source { 0, 0, 0, 0, 0, 2 };
MacAddress destination { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
auto packet = std::make_unique<geonet::UpPacket>(CohesivePacket { std::move(buffer), OsiLayer::Network });
router->indicate(std::move(packet), source, destination);
}

} // namespace vanetza
30 changes: 30 additions & 0 deletions tools/fuzz-harness/router_fuzzing_context.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef VANETZA_ROUTER_FUZZING_CONTEXT_HPP
#define VANETZA_ROUTER_FUZZING_CONTEXT_HPP

#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/dcc/interface.hpp>
#include <vanetza/geonet/router.hpp>
#include <vanetza/geonet/transport_interface.hpp>
#include <vanetza/geonet/tests/security_context.hpp>

namespace vanetza
{

class RouterFuzzingContext {
public:
RouterFuzzingContext();
void initialize();
void indicate(ByteBuffer&& buffer);

private:
ManualRuntime runtime;
geonet::ManagementInformationBase mib;
std::unique_ptr<geonet::Router> router;
std::unique_ptr<dcc::RequestInterface> req_ifc;
std::unique_ptr<geonet::TransportInterface> ind_ifc;
SecurityContext security;
};

} // namespace vanetza

#endif //VANETZA_ROUTER_FUZZING_CONTEXT_HPP
42 changes: 42 additions & 0 deletions tools/fuzz-harness/run.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "router_fuzzing_context.hpp"
#include <iostream>
#include <fstream>

vanetza::ByteBuffer readFileIntoBuffer(const std::string &filename)
{
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Error opening file: " << filename << std::endl;
return {};
}

const std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);

vanetza::ByteBuffer buffer(size);
if (!file.read(reinterpret_cast<char *>(buffer.data()), size)) {
std::cerr << "Error reading file: " << filename << std::endl;
return {};
}

return buffer;
}

int main(int argc, char* argv[])
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filepath>" << std::endl;
return 1;
}

const std::string filename = argv[1];
vanetza::ByteBuffer buffer = readFileIntoBuffer(filename);

if (buffer.empty()) {
return 1;
}

vanetza::RouterFuzzingContext context;
context.indicate(std::move(buffer));
return 0;
}

0 comments on commit 067c01a

Please sign in to comment.