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 May 11, 2024
1 parent 962c4d9 commit da3314b
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 0 deletions.
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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
${FLASHMQ_HEADERS}
${FLASHMQ_IMPLS}
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"

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}"
mkdir -p "$OUTPUT"
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}"

echo mkdir -p "$OUTPUT"
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
25 changes: 25 additions & 0 deletions fuzz-persistent/targets/cirbuf__write.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <unistd.h>
#include "../../cirbuf.h"

__AFL_FUZZ_INIT();

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

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

while (__AFL_LOOP(10000))
{
// Don't use the __AFL_FUZZ_TESTCASE_LEN macro direct in a call
int len = __AFL_FUZZ_TESTCASE_LEN;

CirBuf cirbuf(1024);
cirbuf.write(buf, len);
}

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

#include "../../cirbuf.h"
#include "../../mqttpacket.h"
#include "../../settings.h"
#include "../../subscriptionstore.h"
#include "../../threaddata.h"
#include "../../threadglobals.h"
#include "../../exceptions.h"

__AFL_FUZZ_INIT();

int main()
{
Settings settings;
std::shared_ptr<SubscriptionStore> store(new SubscriptionStore());
PluginLoader pluginLoader;
std::shared_ptr<ThreadData> t(new ThreadData(0, settings, pluginLoader));

// Kind of a hack...
Authentication auth(settings);
ThreadGlobals::assign(&auth);
ThreadGlobals::assignSettings(&settings);
ThreadGlobals::assignThreadData(t.get());

std::shared_ptr<Client> dummyClient(new Client(0, t, nullptr, false, false, nullptr, settings, false));
dummyClient->setClientProperties(ProtocolVersion::Mqtt5, "qostestclient", "user1", true, 60);
store->registerClientAndKickExistingOne(dummyClient, false, 512, 120);

#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif

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

while (__AFL_LOOP(10000))
{
// Don't use the __AFL_FUZZ_TESTCASE_LEN macro direct in a call
int len = __AFL_FUZZ_TESTCASE_LEN;
CirBuf readbuf(1024);
readbuf.ensureFreeSpace(len);
readbuf.write(buf, len);
std::vector<MqttPacket> parsedPackets;
try
{
MqttPacket::bufferToMqttPackets(readbuf, parsedPackets, dummyClient);
}
catch (ProtocolError &)
{
}
}
return 0;
}
24 changes: 24 additions & 0 deletions fuzz-persistent/targets/utils__base64Encode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <unistd.h>
#include "../../utils.h"

__AFL_FUZZ_INIT();

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

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

while (__AFL_LOOP(10000))
{
// Don't use the __AFL_FUZZ_TESTCASE_LEN macro direct in a call
int len = __AFL_FUZZ_TESTCASE_LEN;

base64Encode(buf, len);
}

return 0;
}
4 changes: 4 additions & 0 deletions utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,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("Test 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 da3314b

Please sign in to comment.