[fuzz] Add HC fuzzers for round trip, compress, and streaming

This commit is contained in:
Nick Terrell 2019-07-17 18:31:44 -07:00
parent 399a80d48e
commit 675ef9a9fc
5 changed files with 202 additions and 14 deletions

View File

@ -39,7 +39,9 @@ FUZZERS := \
compress_fuzzer \
decompress_fuzzer \
round_trip_fuzzer \
round_trip_stream_fuzzer
round_trip_stream_fuzzer \
compress_hc_fuzzer \
round_trip_hc_fuzzer
all: $(FUZZERS)

View File

@ -0,0 +1,57 @@
/**
* This fuzz target attempts to compress the fuzzed data with the simple
* compression function with an output buffer that may be too small to
* ensure that the compressor never crashes.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "fuzz_helpers.h"
#include "lz4.h"
#include "lz4hc.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
uint32_t seed = FUZZ_seed(&data, &size);
size_t const dstCapacity = FUZZ_rand32(&seed, 0, LZ4_compressBound(size));
char* const dst = (char*)malloc(dstCapacity);
char* const rt = (char*)malloc(size);
int const level = FUZZ_rand32(&seed, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
FUZZ_ASSERT(dst);
FUZZ_ASSERT(rt);
/* If compression succeeds it must round trip correctly. */
{
int const dstSize = LZ4_compress_HC((const char*)data, dst, size,
dstCapacity, level);
if (dstSize > 0) {
int const rtSize = LZ4_decompress_safe(dst, rt, dstSize, size);
FUZZ_ASSERT_MSG(rtSize == size, "Incorrect regenerated size");
FUZZ_ASSERT_MSG(!memcmp(data, rt, size), "Corruption!");
}
}
if (dstCapacity > 0) {
/* Compression succeeds and must round trip correctly. */
void* state = malloc(LZ4_sizeofStateHC());
FUZZ_ASSERT(state);
int compressedSize = size;
int const dstSize = LZ4_compress_HC_destSize(state, (const char*)data,
dst, &compressedSize,
dstCapacity, level);
FUZZ_ASSERT(dstSize > 0);
int const rtSize = LZ4_decompress_safe(dst, rt, dstSize, size);
FUZZ_ASSERT_MSG(rtSize == compressedSize, "Incorrect regenerated size");
FUZZ_ASSERT_MSG(!memcmp(data, rt, compressedSize), "Corruption!");
free(state);
}
free(dst);
free(rt);
return 0;
}

View File

@ -24,8 +24,13 @@
extern "C" {
#endif
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LZ4_COMMONDEFS_ONLY
#ifndef LZ4_SRC_INCLUDED
#include "lz4.c" /* LZ4_count, constants, mem */
#endif
#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
#define FUZZ_QUOTE_IMPL(str) #str
#define FUZZ_QUOTE(str) FUZZ_QUOTE_IMPL(str)

View File

@ -0,0 +1,39 @@
/**
* This fuzz target performs a lz4 round-trip test (compress & decompress),
* compares the result with the original, and calls abort() on corruption.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "fuzz_helpers.h"
#include "lz4.h"
#include "lz4hc.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
uint32_t seed = FUZZ_seed(&data, &size);
size_t const dstCapacity = LZ4_compressBound(size);
char* const dst = (char*)malloc(dstCapacity);
char* const rt = (char*)malloc(size);
int const level = FUZZ_rand32(&seed, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
FUZZ_ASSERT(dst);
FUZZ_ASSERT(rt);
/* Compression must succeed and round trip correctly. */
int const dstSize = LZ4_compress_HC((const char*)data, dst, size,
dstCapacity, level);
FUZZ_ASSERT(dstSize > 0);
int const rtSize = LZ4_decompress_safe(dst, rt, dstSize, size);
FUZZ_ASSERT_MSG(rtSize == size, "Incorrect size");
FUZZ_ASSERT_MSG(!memcmp(data, rt, size), "Corruption!");
free(dst);
free(rt);
return 0;
}

View File

@ -12,6 +12,8 @@
#include "fuzz_helpers.h"
#define LZ4_STATIC_LINKING_ONLY
#include "lz4.h"
#define LZ4_HC_STATIC_LINKING_ONLY
#include "lz4hc.h"
typedef struct {
char const* buf;
@ -27,11 +29,13 @@ typedef struct {
typedef struct {
LZ4_stream_t* cstream;
LZ4_streamHC_t* cstreamHC;
LZ4_streamDecode_t* dstream;
const_cursor_t data;
cursor_t compressed;
cursor_t roundTrip;
uint32_t seed;
int level;
} state_t;
cursor_t cursor_create(size_t size)
@ -44,6 +48,8 @@ cursor_t cursor_create(size_t size)
return cursor;
}
typedef void (*round_trip_t)(state_t* state);
void cursor_free(cursor_t cursor)
{
free(cursor.buf);
@ -65,6 +71,8 @@ state_t state_create(char const* data, size_t size, uint32_t seed)
state.cstream = LZ4_createStream();
FUZZ_ASSERT(state.cstream);
state.cstreamHC = LZ4_createStreamHC();
FUZZ_ASSERT(state.cstream);
state.dstream = LZ4_createStreamDecode();
FUZZ_ASSERT(state.dstream);
@ -76,12 +84,15 @@ void state_free(state_t state)
cursor_free(state.compressed);
cursor_free(state.roundTrip);
LZ4_freeStream(state.cstream);
LZ4_freeStreamHC(state.cstreamHC);
LZ4_freeStreamDecode(state.dstream);
}
static void state_reset(state_t* state, uint32_t seed)
{
state->level = FUZZ_rand32(&seed, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
LZ4_resetStream_fast(state->cstream);
LZ4_resetStreamHC_fast(state->cstreamHC, state->level);
LZ4_setStreamDecode(state->dstream, NULL, 0);
state->data.pos = 0;
state->compressed.pos = 0;
@ -109,14 +120,19 @@ static void state_checkRoundTrip(state_t const* state)
/**
* Picks a dictionary size and trims the dictionary off of the data.
* We copy the dictionary to the roundTrip so our validation passes.
*/
static size_t state_trimDict(state_t* state)
{
/* 64 KB is the max dict size, allow slightly beyond that to test trim. */
uint32_t maxDictSize = MIN(70 * 1024, state->data.size);
size_t const dictSize = FUZZ_rand32(&state->seed, 0, maxDictSize);
FUZZ_ASSERT(state->compressed.pos == 0);
state->compressed.pos += dictSize;
DEBUGLOG(2, "dictSize = %zu", dictSize);
FUZZ_ASSERT(state->data.pos == 0);
FUZZ_ASSERT(state->roundTrip.pos == 0);
memcpy(state->roundTrip.buf, state->data.buf, dictSize);
state->data.pos += dictSize;
state->roundTrip.pos += dictSize;
return dictSize;
}
@ -159,43 +175,111 @@ static void state_extDictRoundTrip(state_t* state)
cursor_free(data2);
}
static void state_randomRoundTrip(state_t* state)
static void state_randomRoundTrip(state_t* state, round_trip_t rt0,
round_trip_t rt1)
{
if (FUZZ_rand32(&state->seed, 0, 1)) {
state_prefixRoundTrip(state);
rt0(state);
} else {
state_extDictRoundTrip(state);
rt1(state);
}
}
static void state_loadDictRoundTrip(state_t* state)
{
char const* dict = state->compressed.buf;
char const* dict = state->data.buf;
size_t const dictSize = state_trimDict(state);
LZ4_loadDict(state->cstream, dict, dictSize);
LZ4_setStreamDecode(state->dstream, dict, dictSize);
state_randomRoundTrip(state);
state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip);
}
static void state_attachDictRoundTrip(state_t* state)
{
char const* dict = state->compressed.buf;
char const* dict = state->data.buf;
size_t const dictSize = state_trimDict(state);
LZ4_stream_t* dictStream = LZ4_createStream();
LZ4_loadDict(dictStream, dict, dictSize);
LZ4_attach_dictionary(state->cstream, dictStream);
LZ4_setStreamDecode(state->dstream, dict, dictSize);
state_randomRoundTrip(state);
state_randomRoundTrip(state, state_prefixRoundTrip, state_extDictRoundTrip);
LZ4_freeStream(dictStream);
}
typedef void (*round_trip_t)(state_t* state);
static void state_prefixHCRoundTrip(state_t* state)
{
while (state->data.pos != state->data.size) {
char const* src = state->data.buf + state->data.pos;
char* dst = state->compressed.buf + state->compressed.pos;
int const srcRemaining = state->data.size - state->data.pos;
int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining);
int const dstCapacity = state->compressed.size - state->compressed.pos;
int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst,
srcSize, dstCapacity);
FUZZ_ASSERT(cSize > 0);
state->data.pos += srcSize;
state->compressed.pos += cSize;
state_decompress(state, dst, cSize);
}
}
static void state_extDictHCRoundTrip(state_t* state)
{
int i = 0;
cursor_t data2 = cursor_create(state->data.size);
DEBUGLOG(2, "extDictHC");
memcpy(data2.buf, state->data.buf, state->data.size);
while (state->data.pos != state->data.size) {
char const* data = (i++ & 1) ? state->data.buf : data2.buf;
char const* src = data + state->data.pos;
char* dst = state->compressed.buf + state->compressed.pos;
int const srcRemaining = state->data.size - state->data.pos;
int const srcSize = FUZZ_rand32(&state->seed, 0, srcRemaining);
int const dstCapacity = state->compressed.size - state->compressed.pos;
int const cSize = LZ4_compress_HC_continue(state->cstreamHC, src, dst,
srcSize, dstCapacity);
FUZZ_ASSERT(cSize > 0);
DEBUGLOG(2, "srcSize = %d", srcSize);
state->data.pos += srcSize;
state->compressed.pos += cSize;
state_decompress(state, dst, cSize);
}
cursor_free(data2);
}
static void state_loadDictHCRoundTrip(state_t* state)
{
char const* dict = state->data.buf;
size_t const dictSize = state_trimDict(state);
LZ4_loadDictHC(state->cstreamHC, dict, dictSize);
LZ4_setStreamDecode(state->dstream, dict, dictSize);
state_randomRoundTrip(state, state_prefixHCRoundTrip,
state_extDictHCRoundTrip);
}
static void state_attachDictHCRoundTrip(state_t* state)
{
char const* dict = state->data.buf;
size_t const dictSize = state_trimDict(state);
LZ4_streamHC_t* dictStream = LZ4_createStreamHC();
LZ4_setCompressionLevel(dictStream, state->level);
LZ4_loadDictHC(dictStream, dict, dictSize);
LZ4_attach_HC_dictionary(state->cstreamHC, dictStream);
LZ4_setStreamDecode(state->dstream, dict, dictSize);
state_randomRoundTrip(state, state_prefixHCRoundTrip,
state_extDictHCRoundTrip);
LZ4_freeStreamHC(dictStream);
}
round_trip_t roundTrips[] = {
&state_prefixRoundTrip,
&state_extDictRoundTrip,
&state_loadDictRoundTrip,
&state_attachDictRoundTrip,
&state_prefixHCRoundTrip,
&state_extDictHCRoundTrip,
&state_loadDictHCRoundTrip,
&state_attachDictHCRoundTrip,
};
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
@ -206,9 +290,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
int i;
for (i = 0; i < n; ++i) {
DEBUGLOG(2, "Round trip %d", i);
state_reset(&state, seed);
roundTrips[i](&state);
state_checkRoundTrip(&state);
state_reset(&state, seed);
}
state_free(state);