[fuzz] Add HC fuzzers for round trip, compress, and streaming
authorNick Terrell <terrelln@fb.com>
Thu, 18 Jul 2019 01:31:44 +0000 (18:31 -0700)
committerNick Terrell <terrelln@fb.com>
Thu, 18 Jul 2019 19:29:15 +0000 (12:29 -0700)
ossfuzz/Makefile
ossfuzz/compress_hc_fuzzer.c [new file with mode: 0644]
ossfuzz/fuzz_helpers.h
ossfuzz/round_trip_hc_fuzzer.c [new file with mode: 0644]
ossfuzz/round_trip_stream_fuzzer.c

index 2bb40ec..9974b81 100644 (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)
 
diff --git a/ossfuzz/compress_hc_fuzzer.c b/ossfuzz/compress_hc_fuzzer.c
new file mode 100644 (file)
index 0000000..4841367
--- /dev/null
@@ -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;
+}
index 626f209..c4a8645 100644 (file)
 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)
diff --git a/ossfuzz/round_trip_hc_fuzzer.c b/ossfuzz/round_trip_hc_fuzzer.c
new file mode 100644 (file)
index 0000000..325cdf0
--- /dev/null
@@ -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;
+}
index 4facfc9..abfcd2d 100644 (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);