Appendix B — Sample Generated Harnesses
This appendix presents a selection of harnesses generated by OverHAuL, originating from the benchmark run 16351915455. The examples included herein demonstrate a notable diversity, ranging from concise and straightforward harnesses to those that are more elaborate and complex. The crash inputs are provided in the form of their corresponding xxd
hexadecimal representations.
B.1 clibs/buffer
Generated harness:
#include "buffer.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* LLVMFuzzerTestOneInput harness to fuzz the function buffer_appendf.
*
* The fuzzing input data is treated as a format string argument to
* buffer_appendf. This targets potential format string vulnerabilities and
* buffer resizing issues within buffer_appendf as flagged by static analysis.
*
* We create a fresh buffer_t instance with buffer_new(), then call
* buffer_appendf() with the fuzz input as the format string. We do not supply
* additional variadic arguments as the input is a single buffer.
*
* After the call, we free the buffer_t instance to avoid memory leaks.
*/
int
LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
if (size == 0)
{
// No data to test; skip
return 0;
}
// Allocate a writable null-terminated string for the format input
// We add one byte for the null terminator
char *fmt = (char *)malloc (size + 1);
if (!fmt)
return 0; // Allocation failed, skip
// Copy input and null terminate
memcpy (fmt, data, size);
fmt[size] = '\0';
// Create a new buffer instance
buffer_t *buf = buffer_new ();
if (!buf)
{
free (fmt);
return 0;
}
// Call buffer_appendf with user-controlled format string
// No additional arguments are supplied
(void)buffer_appendf (buf, fmt);
// Cleanup
buffer_free (buf);
free (fmt);
return 0;
}
Body of function that the harness targets:
int buffer_appendf(buffer_t *self, const char *format, ...) {
va_list ap;
va_list tmpa;
char *dst = NULL;
int length = 0;
int required = 0;
int bytes = 0;
va_start(ap, format);
length = buffer_length(self);
// First, we compute how many bytes are needed
// for the formatted string and allocate that
// much more space in the buffer.
va_copy(tmpa, ap);
required = vsnprintf(NULL, 0, format, tmpa);
va_end(tmpa);
if (-1 == buffer_resize(self, length + required)) {
va_end(ap);
return -1;
}
// Next format the string into the space that we
// have made room for.
dst = self->data + length;
bytes = vsnprintf(dst, 1 + required, format, ap);
va_end(ap);
return bytes < 0
? -1
: 0;
}
Crash input which triggers an exception in buffer_appendf()
>vsnprintf()
:
00000000: 1b2d 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d .---------------
00000010: 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d ----------------
00000020: 2d2d 2d2d 2d2d 2d2d 2d2d 2d25 2d2d 2d2d -----------%----
00000030: 2d2d 2d2d 2d2d 2d2d 2d2d 2d2d 2d -------------
B.2 willemt/cbuffer
Generated harness:
#include "cbuffer.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// New improved fuzzer harness for cbuf_offer with multiple writes and boundary
// tests
int
LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
if (size == 0)
{
// allow zero-size input to test zero-length write
cbuf_t *cbuf = cbuf_new (12);
if (cbuf == NULL)
return 0;
// calling offer with zero length should be tested
(void)cbuf_offer (cbuf, NULL, 0);
cbuf_free (cbuf);
return 0;
}
cbuf_t *cbuf = cbuf_new (12);
if (cbuf == NULL)
return 0;
// Split input roughly into two parts; first fills the buffer partially,
// second part is used for fuzzing cbuf_offer calls
size_t split = size / 2;
const uint8_t *prefix_data = data;
size_t prefix_size = split;
const uint8_t *main_data = data + split;
size_t main_size = size - split;
// Initially fill the buffer partially with prefix_data to simulate used
// space
if (prefix_size > 0)
{
int space = cbuf_unusedspace (cbuf);
int to_write
= prefix_size < (size_t)space ? (int)prefix_size : space - 1;
if (to_write > 0)
{
(void)cbuf_offer (cbuf, prefix_data, to_write);
}
}
// Now fuzz cbuf_offer with main_data
// Derive write size from first byte of main_data if available, else zero.
int write_size = 0;
if (main_size > 0)
{
write_size = main_data[0];
// Allow write size to be zero (edge case) and up to larger than buffer
// size to test rejection path Normalize write_size to a range: 0 to 2 *
// cbuf->size to test boundary and overflow cases clearly
int max_test_size = (int)(cbuf->size * 2);
write_size
= (write_size
% (max_test_size + 1)); // allows 0 to max_test_size inclusive
}
// Pointer to data for writing is after first byte in main_data if exists
const uint8_t *write_data = main_data + 1;
size_t write_data_len = (main_size > 0) ? main_size - 1 : 0;
// Clamp write_size to write_data_len but allow write_size > write_data_len
// to simulate out of bounds sizes by assigning write_data_len as is -- note
// cbuf_offer will only read up to write_size bytes anyway but fuzzing with
// invalid sizes tests boundary conditions.
if ((size_t)write_size > write_data_len)
{
// We keep write_size as is for boundary fuzzing (even if it overflows
// input), but avoid buffer overflow by passing smaller data pointer as
// NULL if no data available
if (write_data_len == 0)
{
write_data = NULL;
}
}
// Call cbuf_offer with fuzz parameters (including zero and oversized writes)
(void)cbuf_offer (cbuf, write_data, write_size);
// Additionally try a zero length call explicitly to test that edge case
(void)cbuf_offer (cbuf, NULL, 0);
// Optionally perform a few repeated calls with no data to simulate further
// state changes
(void)cbuf_offer (cbuf, NULL, 0);
(void)cbuf_offer (cbuf, NULL, 0);
cbuf_free (cbuf);
return 0;
}
Body of function that the harness targets:
int cbuf_offer(cbuf_t *me, const unsigned char *data, const int size)
{
/* prevent buffer from getting completely full or over commited */
if (cbuf_unusedspace(me) <= size)
return 0;
int written = cbuf_unusedspace(me);
written = size < written ? size : written;
memcpy(me->data + me->tail, data, written);
me->tail += written;
if (me->size < me->tail)
me->tail %= me->size;
return written;
}
Crash input which triggers an exception in cbuf_offer()
>memcpy()
:
00000000: 0a .
B.3 dvhar/dateparse
Generated harness:
#include <stddef.h>
#include <stdint.h>
#include "dateparse.h"
// LibFuzzer entry point
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
date_t parsed_date = 0;
int offset = 0;
// Pass the input data directly without copying
(void)dateparse((const char *)data, &parsed_date, &offset, (int)size);
return 0;
}
Body of function that the harness targets:
int dateparse(const char* datestr, date_t* t, int *offset, int stringlen){
struct parser p;
*t = 0;
if (!stringlen)
stringlen = strlen(datestr);
if (parseTime(datestr, &p, stringlen))
return -1;
return parse(&p, t, offset);
}
Crash input that causes a heap buffer overflow in dateparse()
>parseTime()
>setMonth()
>strncpy()
.
00000000: 6632 2066 f2 f
B.4 h2non/semver.c
Generated harness:
#include "semver.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// Enhanced fuzzer harness for semver_parse including calls to semver_numeric
// and semver_render to help increase coverage and trigger more bugs faster.
// Dynamically allocates input buffer with no size limit other than system
// memory. Frees allocated semver_t string members after use.
int
LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
// Allocate buffer dynamically to handle any input size.
char *input_str = (char *)malloc (size + 1);
if (input_str == NULL)
{
return 0;
}
memcpy (input_str, data, size);
input_str[size] = '\0';
semver_t ver;
memset (&ver, 0, sizeof (ver));
// Parse the semver string.
(void)semver_parse (input_str, &ver);
// Call semver_numeric to exercise more code paths.
(void)semver_numeric (&ver);
// Call semver_render with buffer large enough for rendered output.
// Typical semantic version strings are short, but use a buffer of size
// size+20 to be safe.
char *render_buf = (char *)malloc (size + 20);
if (render_buf != NULL)
{
memset (render_buf, 0, size + 20);
semver_render (&ver, render_buf);
free (render_buf);
}
// Free dynamically allocated members inside semver_t.
if (ver.prerelease)
{
free (ver.prerelease);
ver.prerelease = NULL;
}
if (ver.metadata)
{
free (ver.metadata);
ver.metadata = NULL;
}
free (input_str);
return 0;
}
Bodies of functions that the harness targets:
/**
* Parses a string as semver expression.
*
* Returns:
*
* `0` - Parsed successfully
* `-1` - In case of error
*/
int
semver_parse (const char *str, semver_t *ver)
{
int valid, res;
size_t len;
char *buf;
valid = semver_is_valid (str);
if (!valid)
return -1;
len = strlen (str);
buf = (char *)calloc (len + 1, sizeof (*buf));
if (buf == NULL)
return -1;
strcpy (buf, str);
ver->metadata = parse_slice (buf, MT_DELIMITER[0]);
ver->prerelease = parse_slice (buf, PR_DELIMITER[0]);
res = semver_parse_version (buf, ver);
free (buf);
#if DEBUG > 0
printf ("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major,
ver->minor, ver->patch, ver->prerelease, ver->metadata);
#endif
return res;
}
//...
/**
* Render a given semver as string
*/
void
semver_render (semver_t *x, char *dest)
{
concat_num (dest, x->major, NULL);
concat_num (dest, x->minor, DELIMITER);
concat_num (dest, x->patch, DELIMITER);
if (x->prerelease)
concat_char (dest, x->prerelease, PR_DELIMITER);
if (x->metadata)
concat_char (dest, x->metadata, MT_DELIMITER);
}
Crash input that causes a stack buffer overflow in semver_render()
>concat_char()
>sprintf()
:
00000000: 392d 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 9-++++++++++++++
00000010: 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b ++++++++++++++++
00000020: 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b ++++++++++++++++
00000030: 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b ++++++++++++++++
00000040: 2b2b 2b2b 2b2b 2b46 4c +++++++FL