Compiled-In Harnessing
Using Provided Headers
The TSFFS project provides harnessing headers for each supported combination of
architecture and build toolchain. These headers can be found in the harness
directory
in the repository. There is also a monolithic header tsffs.h
which conditionally
compiles to whichever architecture is in use and can be used on any supported
architecture and platform.
Each header provides the macros HARNESS_START
and HARNESS_STOP
.
HARNESS_START(testcase_ptr, size_ptr)
takes two arguments, a buffer for the fuzzer to
write testcases into each fuzzing iteration and a pointer to a pointer-sized variable,
which the fuzzer will write the size of each testcase to each fuzzing iteration. The
variable pointed to by size_pointer
should be initially equal to the maximum size of a
testcase, typically the size of the buffer passed as the first argument.
HARNESS_STOP
takes no arguments.
For example, the following code will invoke the start and stop harnesses correctly:
#include "tsffs.h"
int main() {
char buffer[20];
size_t size = sizeof(buffer);
// Each fuzzer iteration, execution will start from here, with a different buffer content
// and size=len(buffer).
HARNESS_START(buffer, &size);
// Check if we got enough data from the fuzzer -- this is not always necessary
if (size < 3) {
// Stop early if we didn't get enough bytes from the fuzzer
HARNESS_STOP();
}
// Do something with buffer and size
int retval = function_under_test(buffer, size);
if (retval == SOMETHING_IMPOSSIBLE_HAPPENED) {
/// Some exceptional condition occurred -- note, don't use this for normal "bad" return
/// values, use it for instances where something that you are fuzzing for happened.
HARNESS_ASSERT();
}
// Stop normally
HARNESS_STOP();
return 0;
}
By default, TSFFS is enabled to use these harnesses, so no explicit configuration is necessary. However, the defaults are equivalent to the configuration:
@tsffs.start_on_harness = True
@tsffs.stop_on_harness = True
@tsffs.magic_start_index = 0
@tsffs.magic_stop_indices = [0]
@tsffs.magic_assert_indices = [0]
This sets TSFFS to start the fuzzing loop on a magic
harness with magic number 1
(used by HARNESS_START
)
and index 0
(the default) and stop execution and
restore to the initial snapshot on magic harnesses
with magic number 2
(used by HARNESS_STOP
) and
index 0
(the default).
Multiple Harnesses in One Binary
If multiple fuzzing campaigns will be run on the same target software, it is sometimes advantageous to compile multiple harnesses into the same target software ahead of time, and choose which to enable at runtime.Each provided header also provides two lower-level macros for this purpose.
HARNESS_START_INDEX(index, testcase_ptr, size_ptr)
HARNESS_STOP(index)
These macros are used in the same way as HARNESS_START
and HARNESS_STOP
,
with the additional first argument. The default value of index
is 0, but
TSFFS can be configured to treat a different index as the trigger to start or
stop the fuzzing loop.
#include "tsffs.h"
int main() {
char buf[20];
size_t size = sizeof(buf);
HARNESS_START(buf, &size);
if (size < 3) {
// Stop early if there is not enough data
HARNESS_STOP();
}
char * result = function_under_test(buf);
// Stop normally on success
HARNESS_STOP();
HARNESS_START_INDEX(1, result, &size);
second_function_under_test(result);
HARNESS_STOP();
return 0;
}
And configuration settings like:
@tsffs.start_on_harness = True
@tsffs.stop_on_harness = True
@tsffs.magic_start_index = 1
With this runtime configuration, the first harness will be ignored, and only the second set of harness calls will be used.
Alternative Start Harnesses
Several additional variants of the start harness are provided to allow different target software to be used with as little modification as possible.
HARNESS_START_WITH_MAXIMUM_SIZE(void *buffer, size_t max_size)
takes a pointer to a buffer likeHARNESS_START
but takes a size instead of a pointer to a size as the second argument. Use this harness when the target software does not need to read the actual buffer size.HARNESS_START_WITH_MAXIMUM_SIZE_AND_PTR(void *buffer, void *size_ptr, size_t max_size)
takes a pointer to both a buffer and size likeHARNESS_START
, and takes a size as the third argument. Use this harness when the target software does not initially have*size_ptr
set to the maximum size, but still needs to read the actual buffer size.
Troubleshooting
Compile Errors About Temporaries
Some toolchains or compiler versions may reject the use of an &
reference in the
HARNESS_START
macro (like HARNESS_START(buffer, &size)
). In this case, create a
temporary to hold the address and pass it instead, like:
char buffer[100];
size_t size = sizeof(buffer);
size_t size_ptr = &size;
HARNESS_START(buffer, size_ptr);