Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup for persistent fuzzing #18

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading