Skip to content

Commit

Permalink
Setup for persistent fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
quinox committed Apr 9, 2023
1 parent 08bbe0a commit 47ce529
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 0 deletions.
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,38 @@ endif()

add_compile_options(-Wall)

# Begin fuzz-persistent
# NB: These fuzzing targets require a special compiler!
add_custom_target(fuzzing_targets)
set_target_properties(fuzzing_targets PROPERTIES EXCLUDE_FROM_ALL TRUE)

file(GLOB all_implementations "${PROJECT_SOURCE_DIR}/*.cpp")
list(REMOVE_ITEM all_implementations "${PROJECT_SOURCE_DIR}/main.cpp") # we only want the main() function from the fuzz target
list(REMOVE_ITEM all_implementations "${PROJECT_SOURCE_DIR}/flashmqtestclient.cpp") # it causes problems: /usr/lib/gcc/x86_64-pc-linux-gnu/11.3.0/include/g++-v11/ext/new_allocator.h:162:23: error: call to deleted constructor of 'MqttPacket'
file(GLOB fuzz_targets "fuzz-persistent/targets/*.cpp")
foreach(file ${fuzz_targets})
if (NOT ${CMAKE_VERSION} VERSION_LESS "3.20.0")
# CMake 3.20 (2021-03-23)
cmake_path(GET file STEM fuzzstem)
else()
# Older CMake
string(REGEX MATCH "([^/]*)\\.[a-z]*$" OUTVAR "${file}")
set(fuzzstem "${CMAKE_MATCH_1}")
if(NOT fuzzstem)
message(FATAL_ERROR "Problem in ${CMAKE_CURRENT_LIST_FILE}: cmake_path wasn't available and regex failed to extract stem")
endif()
endif()
set(fuzzbinary "fuzz_${fuzzstem}")
add_dependencies(fuzzing_targets ${fuzzbinary})
add_executable(${fuzzbinary} ${file})
target_sources(${fuzzbinary} PRIVATE ${all_implementations})
target_link_libraries(${fuzzbinary} dl ssl crypto resolv)
set_target_properties(${fuzzbinary} PROPERTIES EXCLUDE_FROM_ALL TRUE)

message(STATUS "Generated fuzzing target ${fuzzbinary}")
endforeach()
# End fuzz-persistent

add_executable(flashmq
forward_declarations.h
mainapp.h
Expand Down
2 changes: 2 additions & 0 deletions fuzz-persistent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build/
/output/
17 changes: 17 additions & 0 deletions fuzz-persistent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Fuzzing with persistent mode

See the [AFL++ documentation](https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.persistent_mode.md) for generic help about fuzzing with persistent mode.

TL;DR: it's _faaaaaast_.

## Fuzzing everything

Simply run `./fuzz-helper.sh`. Findings will be written to the folder called `output`.

## Setting up new test

To add a new test:

* Decide on a testname, the standard format is: `<sourcefilename>__<test name (fe. the function you're fuzzing)>`, for example `cirbuf__write`
* Create `target/${testname}.cpp`
* Run `./fuzz-helper.sh build_and_run ${testname}`
157 changes: 157 additions & 0 deletions fuzz-persistent/fuzz-helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/bin/bash
#
# Script to build+run fuzzing targets in persistent mode using American Fuzzy Lop.

thisfile=$(readlink --canonicalize "$0")
thisdir=$(dirname "$thisfile")
projectdir=$(dirname "$thisdir")

die() {
>&2 echo "Fatal error: $*"
exit 9
}

if [[ -z "$AFL_ROOT" ]]; then
afl_fallback_from_path=$(command -v afl-fuzz)
if [[ -z "$afl_fallback_from_path" ]]
then
echo "ERROR: alf-fuzz not found. Please set the AFL_ROOT environment variable"
exit 1
else
AFL_ROOT=$(dirname "$afl_fallback_from_path")
echo "WARNING: You didn't set AFL_ROOT but I found afl-fuzz in $AFL_ROOT, I hope the other tools are also there"
fi
fi

# AFL_ROOT is our own variable name, newer AFL++ warn that they don't know what
# it means. Solution: unexport the variable
export -n AFL_ROOT

set -u

usage() {
>&2 echo "Usage: [ build | run | build_and_run | attach | clean ]"
}
die() {
>&2 echo "A fatal error occurred: $*"
exit 9
}

TMUXSESSION=flashmqfuzzpersistent
COMMAND="build_and_run"
if [[ $# -ge 1 ]]; then
COMMAND="$1"
fi
SPECIFIC_BINARY=""
if [[ $# -ge 2 ]]; then
SPECIFIC_BINARY="$2"
fi

do_build() {
# We currently need LTO/fast because they understand the macros we use
export CC="$AFL_ROOT/afl-clang-fast"
CC_CLANG_LTO="$AFL_ROOT/afl-clang-lto"
[[ -e "$CC_CLANG_LTO" ]] && export CC="$CC_CLANG_LTO"

export CXX="$AFL_ROOT/afl-clang-fast++"
CXX_CLANG_LTO="$AFL_ROOT/afl-clang-lto++"
[[ -e "$CXX_CLANG_LTO" ]] && export CXX="$CXX_CLANG_LTO"

echo "Using for \$CC: $CC"
echo "Using for \$CXX: $CXX"

mkdir -p "${thisdir}/build"
cd "${thisdir}/build" || die "Something peculiar went wrong."

cmake -DCMAKE_BUILD_TYPE="fuzz-persistent" "$projectdir" || die "CMake failed."
if [[ -z "$SPECIFIC_BINARY" ]]
then
make -j "fuzzing_targets" || die "make failed."
else
make -j "fuzz_${SPECIFIC_BINARY}" || die "make failed."
fi
}

do_run() {
# list-sessions has a -f option but it doesn't seem to work for me
if tmux list-sessions | grep "^$TMUXSESSION:" > /dev/null
then
>&2 echo "Fuzzing is already going on in the background. Use 'attach' to attach."
exit 1
fi


declare -a FUZZING_TARGETS
HUMAN_LIST_OF_TARGETS=$'\n'
for i in "${thisdir}/build/fuzz_"*;
do
if [[ -x "$i" ]];
then
FUZZING_TARGETS+=("$(basename "$i")")
HUMAN_LIST_OF_TARGETS="$HUMAN_LIST_OF_TARGETS- $(basename "$i")"$'\n'
fi
done

INPUT="${projectdir}/fuzztests"

mkdir -p "${thisdir}/output"

if [[ -z "$SPECIFIC_BINARY" ]]
then
echo "AFL output will be stored in ${thisdir}/output"
echo "Fuzzing targets: ${HUMAN_LIST_OF_TARGETS}"

tmux new-session -s "$TMUXSESSION" -d "echo 'Fuzzing targets: ${HUMAN_LIST_OF_TARGETS}Cycle through the windows to see the fuzzing going on'; read"

for BINARY in "${FUZZING_TARGETS[@]}"
do
OUTPUT="${thisdir}/output/${BINARY}"
tmux new-window -t "$TMUXSESSION" -n "$BINARY" "'$AFL_ROOT/afl-fuzz' -i '${INPUT}' -o '${OUTPUT}' -T '$BINARY' -- '${thisdir}/build/$BINARY'; echo 'Press [enter] to exit'; read"
done
tmux next-window -t "$TMUXSESSION" # cycle through to the first window with the README
tmux attach-session -t "$TMUXSESSION"
else
OUTPUT="${thisdir}/output/${SPECIFIC_BINARY}"
BINARY="fuzz_$SPECIFIC_BINARY"

echo "AFL output will be stored in ${OUTPUT}"
echo "Fuzzing target: ${SPECIFIC_BINARY}"

tmux new-session -s "$TMUXSESSION" -n "$BINARY" "'$AFL_ROOT/afl-fuzz' -i '${INPUT}' -o '${OUTPUT}' -T '$BINARY' -- '${thisdir}/build/$BINARY'; echo 'Press [enter] to exit'; read"
fi
}

do_clean() {
rm -rf "$thisdir/build"
}

do_attach() {
tmux attach-session -t "$TMUXSESSION"
}

if [[ "$COMMAND" == "build" ]]
then
>&2 echo "Building..."
do_build
elif [[ "$COMMAND" == "run" ]]
then
>&2 echo "Running..."
do_run
elif [[ "$COMMAND" == "build_and_run" ]]
then
>&2 echo "Building and running..."
do_build
echo ""
echo ""
do_run
elif [[ "$COMMAND" == "clean" ]]
then
do_clean
elif [[ "$COMMAND" == "attach" ]]
then
do_attach
else
>&2 echo "Unknown option $COMMAND"
usage
exit 1
fi
27 changes: 27 additions & 0 deletions fuzz-persistent/targets/cirbuf__write.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <unistd.h>
#include "../../cirbuf.h"

__AFL_FUZZ_INIT();

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

unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT
// and before __AFL_LOOP!

while (__AFL_LOOP(10000))
{

int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a
// call!

CirBuf cirbuf(1024);

cirbuf.write(buf, len);
}

return 0;
}
33 changes: 33 additions & 0 deletions fuzz-persistent/targets/threadlocalutils__isValidUtf8.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <unistd.h>

#ifndef __SSE4_2__
#error "This file is supposed to test SIMD, please fix your setup to support it."
#endif

#include "../../threadlocalutils.h"
thread_local SimdUtils simdUtils;

__AFL_FUZZ_INIT();

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

unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT
// and before __AFL_LOOP!
char *signed_buff = reinterpret_cast<char *>(buf);

while (__AFL_LOOP(10000))
{

int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a
// call!
std::string random_string(signed_buff, len);
simdUtils.isValidUtf8(random_string, false);
simdUtils.isValidUtf8(random_string, true);
}

return 0;
}
34 changes: 34 additions & 0 deletions fuzz-persistent/targets/threadlocalutils__splitTopic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <unistd.h>
#include <stdexcept>

#ifndef __SSE4_2__
#error "This file is supposed to test SIMD, please fix your setup to support it."
#endif

#include "../../threadlocalutils.h"
thread_local SimdUtils simdUtils;

__AFL_FUZZ_INIT();

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

unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT
// and before __AFL_LOOP!
char *signed_buff = reinterpret_cast<char *>(buf);

while (__AFL_LOOP(10000))
{

int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a
// call!
std::string random_string(signed_buff, len);
std::vector<std::string> subtopics;
simdUtils.splitTopic(random_string, subtopics);
}

return 0;
}
25 changes: 25 additions & 0 deletions fuzz-persistent/targets/utils__base64Encode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <unistd.h>
#include "../../utils.h"

__AFL_FUZZ_INIT();

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

unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; // must be after __AFL_INIT
// and before __AFL_LOOP!

while (__AFL_LOOP(10000))
{

int len = __AFL_FUZZ_TESTCASE_LEN; // don't use the macro directly in a
// call!

base64Encode(buf, len);
}

return 0;
}
3 changes: 3 additions & 0 deletions threadlocalutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ std::vector<std::string> *SimdUtils::splitTopic(const std::string &topic, std::v
std::memset(&topicCopy.data()[s], 0, 16);
int n = 0;
int carryi = 0;

assert(s != 15); // whoopsie; bomb.

while (n <= s)
{
const char *i = &topicCopy.data()[n];
Expand Down
4 changes: 4 additions & 0 deletions utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ std::vector<char> base64Decode(const std::string &s)

std::string base64Encode(const unsigned char *input, const int length)
{
if (length == 13)
{
throw std::runtime_error("The bomb went off... kaboom.");
}
const int pl = 4*((length+2)/3);
char *output = reinterpret_cast<char *>(calloc(pl+1, 1));
const int ol = EVP_EncodeBlock(reinterpret_cast<unsigned char *>(output), input, length);
Expand Down

0 comments on commit 47ce529

Please sign in to comment.