]> git.ipfire.org Git - thirdparty/zstd.git/commitdiff
[fuzz] Add libFuzzer targets
authorNick Terrell <terrelln@fb.com>
Thu, 29 Jun 2017 23:53:52 +0000 (16:53 -0700)
committerNick Terrell <terrelln@fb.com>
Sat, 1 Jul 2017 00:39:56 +0000 (17:39 -0700)
* The regression driver serves both as a regression test, and as a binary for afl-fuzz.
* Next, we want to check in a seed corpus for each target. Then we can run the regression
  test binary on them on Travis or Circle CI.

fuzz/Makefile [new file with mode: 0644]
fuzz/README.md [new file with mode: 0644]
fuzz/fuzz.h [new file with mode: 0644]
fuzz/fuzz_helpers.h [new file with mode: 0644]
fuzz/regression_driver.c [new file with mode: 0644]
fuzz/simple_decompress.c [new file with mode: 0644]
fuzz/simple_round_trip.c [new file with mode: 0644]
fuzz/stream_decompress.c [new file with mode: 0644]
fuzz/stream_round_trip.c [new file with mode: 0644]

diff --git a/fuzz/Makefile b/fuzz/Makefile
new file mode 100644 (file)
index 0000000..9b084fd
--- /dev/null
@@ -0,0 +1,108 @@
+# ##########################################################################
+# Copyright (c) 2016-present, Facebook, Inc.
+# All rights reserved.
+#
+# This Makefile is validated for Linux, and macOS targets
+#
+# This source code is licensed under the BSD-style license found in the
+# LICENSE file in the root directory of this source tree. An additional grant
+# of patent rights can be found in the PATENTS file in the same directory.
+# ##########################################################################
+
+CFLAGS ?= -O3
+CXXFLAGS ?= -O3
+
+ZSTDDIR = ../lib
+PRGDIR = ../programs
+
+FUZZ_CPPFLAGS := -I$(ZSTDDIR) -I$(ZSTDDIR)/common -I$(ZSTDDIR)/compress \
+       -I$(ZSTDDIR)/dictBuilder -I$(ZSTDDIR)/deprecated -I$(PRGDIR) \
+       -DZSTD_DEBUG=1 -DMEM_FORCE_MEMORY_ACCESS=0 \
+       -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION $(CPPFLAGS)
+FUZZ_CFLAGS := -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \
+       -Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement \
+       -Wstrict-prototypes -Wundef -Wformat-security \
+       -Wvla -Wformat=2 -Winit-self -Wfloat-equal -Wwrite-strings \
+       -Wredundant-decls \
+       -g -fno-omit-frame-pointer $(CFLAGS)
+FUZZ_CXXFLAGS := -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \
+       -Wstrict-aliasing=1 -Wswitch-enum \
+       -Wdeclaration-after-statement -Wstrict-prototypes -Wundef \
+       -Wformat-security -Wvla -Wformat=2 -Winit-self -Wfloat-equal \
+       -Wwrite-strings -Wredundant-decls \
+       -g -fno-omit-frame-pointer -std=c++11 $(CXXFLAGS)
+FUZZ_LDFLAGS := $(LDFLAGS)
+FUZZ_ARFLAGS := $(ARFLAGS)
+FUZZ_TARGET_FLAGS = $(FUZZ_CPPFLAGS) $(FUZZ_CXXFLAGS) $(FUZZ_LDFLAGS)
+
+FUZZ_HEADERS := fuzz_helpers.h fuzz.h
+
+ZSTDCOMMON_FILES := $(ZSTDDIR)/common/*.c
+ZSTDCOMP_FILES   := $(ZSTDDIR)/compress/*.c
+ZSTDDECOMP_FILES := $(ZSTDDIR)/decompress/*.c
+ZSTD_FILES       := $(ZSTDDECOMP_FILES) $(ZSTDCOMMON_FILES) $(ZSTDCOMP_FILES)
+
+ZSTD_OBJ  := $(patsubst %.c,%.o, $(wildcard $(ZSTD_FILES)))
+
+LIBFUZZER ?= -lFuzzer
+
+.PHONY: default all clean
+
+default: all
+
+all: round_trip simple_decompress
+
+%.o: %.c
+       $(CC) $(FUZZ_CPPFLAGS) $(FUZZ_CFLAGS) $^ -c -o $@
+
+simple_round_trip: $(FUZZ_HEADERS) $(ZSTD_OBJ) simple_round_trip.o
+       $(CXX) $(FUZZ_TARGET_FLAGS) $(ZSTD_OBJ) simple_round_trip.o $(LIBFUZZER) -o $@
+
+stream_round_trip: $(FUZZ_HEADERS) $(ZSTD_OBJ) stream_round_trip.o
+       $(CXX) $(FUZZ_TARGET_FLAGS) $(ZSTD_OBJ) stream_round_trip.o $(LIBFUZZER) -o $@
+
+simple_decompress: $(FUZZ_HEADERS) $(ZSTD_OBJ) simple_decompress.o
+       $(CXX) $(FUZZ_TARGET_FLAGS) $(ZSTD_OBJ) simple_decompress.o $(LIBFUZZER) -o $@
+
+stream_decompress: $(FUZZ_HEADERS) $(ZSTD_OBJ) stream_decompress.o
+       $(CXX) $(FUZZ_TARGET_FLAGS) $(ZSTD_OBJ) stream_decompress.o $(LIBFUZZER) -o $@
+
+libregression.a: $(FUZZ_HEADERS) $(PRGDIR)/util.h regression_driver.o
+       $(AR) $(FUZZ_ARFLAGS) $@ regression_driver.o
+
+%-regression: libregression.a
+       $(RM) $*
+       $(MAKE) $* LDFLAGS="$(FUZZ_LDFLAGS) -L." LIBFUZZER=-lregression
+
+%-regression-test: %-regression
+       ./$* corpora/$*
+
+regression-test: \
+       simple_round_trip-regression-test \
+       stream_round_trip-regression-test \
+       simple_decompress-regression-test \
+       stream_decompress-regression-test
+
+%-msan: clean
+       $(MAKE) $* CFLAGS="-fsanitize=memory $(FUZZ_CFLAGS)" \
+               CXXFLAGS="-fsanitize=memory $(FUZZ_CXXFLAGS)"
+
+UASAN_FLAGS := -fsanitize=address,undefined -fno-sanitize-recover=undefined \
+       -fno-sanitize=pointer-overflow
+%-uasan: clean
+       $(MAKE) $* CFLAGS="$(FUZZ_CFLAGS) $(UASAN_FLAGS)" \
+               CXXFLAGS="$(FUZZ_CXXFLAGS) $(UASAN_FLAGS)"
+
+# Install libfuzzer (not usable for MSAN testing)
+# Provided for convienence. To use this library run make libFuzzer and
+# set LDFLAGS=-L.
+.PHONY: libFuzzer
+libFuzzer:
+       @$(RM) -rf Fuzzer
+       @git clone https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer
+       @./Fuzzer/build.sh
+
+clean:
+       @$(MAKE) -C $(ZSTDDIR) clean
+       @$(RM) -f *.a *.o
+       @$(RM) -f simple_round_trip stream_round_trip simple_decompress stream_decompress
diff --git a/fuzz/README.md b/fuzz/README.md
new file mode 100644 (file)
index 0000000..38a4f3d
--- /dev/null
@@ -0,0 +1,34 @@
+# Fuzzing
+
+Each fuzzing target can be built with multiple engines.
+
+## LibFuzzer
+
+You can install `libFuzzer` with `make libFuzzer`. Then you can make each target
+with `make target LDFLAGS=-L. CC=clang CXX=clang++`.
+
+## AFL
+
+The regression driver also serves as a binary for `afl-fuzz`. You can make each
+target with one of these commands:
+
+```
+make target-regression CC=afl-clang CXX=afl-clang++
+AFL_MSAN=1 make target-regression-msan CC=afl-clang CXX=afl-clang++
+AFL_ASAN=1 make target-regression-uasan CC=afl-clang CXX=afl-clang++
+```
+
+Then run as `./target @@`.
+
+## Regression Testing
+
+Each fuzz target has a corpus checked into the repo under `fuzz/corpora/`.
+You can run regression tests on the corpora to ensure that inputs which
+previously exposed bugs still pass. You can make these targets to run the
+regression tests with different sanitizers.
+
+```
+make regression-test
+make regression-test-msan
+make regression-test-uasan
+```
diff --git a/fuzz/fuzz.h b/fuzz/fuzz.h
new file mode 100644 (file)
index 0000000..5b71aba
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * Fuzz target interface.
+ * Fuzz targets have some common parameters passed as macros during compilation.
+ * Check the documentation for each individual fuzzer for more parameters.
+ *
+ * @param STATEFULL_FUZZING:
+ *        Define this to reuse state between fuzzer runs. This can be useful to
+ *        test code paths which are only executed when contexts are reused.
+ *        WARNING: Makes reproducing crashes much harder.
+ *        Default: Not defined.
+ * @param FUZZ_RNG_SEED_SIZE:
+ *        The number of bytes of the source to look at when constructing a seed
+ *        for the deterministic RNG.
+ *        Default: 128.
+ * @param ZSTD_DEBUG:
+ *        This is a parameter for the zstd library. Defining `ZSTD_DEBUG=1`
+ *        enables assert() statements in the zstd library. Higher levels enable
+ *        logging, so aren't recommended. Defining `ZSTD_DEBUG=1` is
+ *        recommended.
+ * @param MEM_FORCE_MEMORY_ACCESS:
+ *        This flag controls how the zstd library accesses unaligned memory.
+ *        It can be undefined, or 0 through 2. If it is undefined, it selects
+ *        the method to use based on the compiler. If testing with UBSAN set
+ *        MEM_FORCE_MEMORY_ACCESS=0 to use the standard compliant method.
+ * @param FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ *        This is the canonical flag to enable deterministic builds for fuzzing.
+ *        Changes to zstd for fuzzing are gated behind this define.
+ *        It is recommended to define this when building zstd for fuzzing.
+ */
+
+#ifndef FUZZ_H
+#define FUZZ_H
+
+#ifndef FUZZ_RNG_SEED_SIZE
+#  define FUZZ_RNG_SEED_SIZE 128
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size);
+
+#endif
diff --git a/fuzz/fuzz_helpers.h b/fuzz/fuzz_helpers.h
new file mode 100644 (file)
index 0000000..5f07fa4
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * Helper functions for fuzzing.
+ */
+
+#ifndef FUZZ_HELPERS_H
+#define FUZZ_HELPERS_H
+
+#include "fuzz.h"
+#include "xxhash.h"
+#include <stdint.h>
+#include <stdio.h>
+
+#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)
+
+/**
+ * Asserts for fuzzing that are always enabled.
+ */
+#define FUZZ_ASSERT_MSG(cond, msg)                                             \
+  ((cond) ? (void)0                                                            \
+          : (fprintf(stderr, "%s: %u: Assertion: `%s' failed. %s\n", __FILE__, \
+                     __LINE__, FUZZ_QUOTE(cond), (msg)),                       \
+             abort()))
+#define FUZZ_ASSERT(cond) FUZZ_ASSERT_MSG((cond), "");
+
+#if defined(__GNUC__)
+#define FUZZ_STATIC static __inline __attribute__((unused))
+#elif defined(__cplusplus) ||                                                  \
+    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+#define FUZZ_STATIC static inline
+#elif defined(_MSC_VER)
+#define FUZZ_STATIC static __inline
+#else
+#define FUZZ_STATIC static
+#endif
+
+/**
+ * Determininistically constructs a seed based on the fuzz input.
+ * Only looks at the first FUZZ_RNG_SEED_SIZE bytes of the input.
+ */
+FUZZ_STATIC uint32_t FUZZ_seed(const uint8_t *src, size_t size) {
+  size_t const toHash = MIN(FUZZ_RNG_SEED_SIZE, size);
+  return XXH32(src, toHash, 0);
+}
+
+#define FUZZ_rotl32(x, r) (((x) << (r)) | ((x) >> (32 - (r))))
+FUZZ_STATIC uint32_t FUZZ_rand(uint32_t *state) {
+  static const uint32_t prime1 = 2654435761U;
+  static const uint32_t prime2 = 2246822519U;
+  uint32_t rand32 = *state;
+  rand32 *= prime1;
+  rand32 += prime2;
+  rand32 = FUZZ_rotl32(rand32, 13);
+  *state = rand32;
+  return rand32 >> 5;
+}
+
+#endif
diff --git a/fuzz/regression_driver.c b/fuzz/regression_driver.c
new file mode 100644 (file)
index 0000000..eee5f0a
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "fuzz.h"
+#include "fuzz_helpers.h"
+#include "util.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char const **argv) {
+  size_t const kMaxFileSize = (size_t)1 << 20;
+  int const kFollowLinks = 1;
+  char *fileNamesBuf = NULL;
+  char const **files = argv + 1;
+  unsigned numFiles = argc - 1;
+  uint8_t *buffer = NULL;
+  size_t bufferSize = 0;
+  unsigned i;
+  int ret;
+
+#ifdef UTIL_HAS_CREATEFILELIST
+  files = UTIL_createFileList(files, numFiles, &fileNamesBuf, &numFiles,
+                              kFollowLinks);
+  FUZZ_ASSERT(files);
+#endif
+
+  for (i = 0; i < numFiles; ++i) {
+    char const *fileName = files[i];
+    size_t const fileSize = UTIL_getFileSize(fileName);
+    size_t readSize;
+    FILE *file;
+
+    /* Check that it is a regular file, and that the fileSize is valid */
+    FUZZ_ASSERT_MSG(UTIL_isRegFile(fileName), fileName);
+    FUZZ_ASSERT_MSG(fileSize <= kMaxFileSize, fileName);
+    /* Ensure we have a large enough buffer allocated */
+    if (fileSize > bufferSize) {
+      free(buffer);
+      buffer = (uint8_t *)malloc(fileSize);
+      FUZZ_ASSERT_MSG(buffer, fileName);
+      bufferSize = fileSize;
+    }
+    /* Open the file */
+    file = fopen(fileName, "rb");
+    FUZZ_ASSERT_MSG(file, fileName);
+    /* Read the file */
+    readSize = fread(buffer, 1, fileSize, file);
+    FUZZ_ASSERT_MSG(readSize == fileSize, fileName);
+    /* Close the file */
+    fclose(file);
+    /* Run the fuzz target */
+    LLVMFuzzerTestOneInput(buffer, fileSize);
+  }
+
+  ret = 0;
+  free(buffer);
+#ifdef UTIL_HAS_CREATEFILELIST
+  UTIL_freeFileList(files, fileNamesBuf);
+#endif
+  return ret;
+}
diff --git a/fuzz/simple_decompress.c b/fuzz/simple_decompress.c
new file mode 100644 (file)
index 0000000..c22ad7c
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2016-present, Yann Collet, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * This fuzz target attempts to decompress the fuzzed data with the simple
+ * decompression function to ensure the decompressor never crashes.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "fuzz_helpers.h"
+#include "zstd.h"
+
+static ZSTD_DCtx *dctx = NULL;
+static void* rBuf = NULL;
+static size_t bufSize = 0;
+
+int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size)
+{
+    size_t const neededBufSize = MAX(20 * size, (size_t)256 << 10);
+
+    /* Allocate all buffers and contexts if not already allocated */
+    if (neededBufSize > bufSize) {
+        free(rBuf);
+        rBuf = malloc(neededBufSize);
+        bufSize = neededBufSize;
+        FUZZ_ASSERT(rBuf);
+    }
+    if (!dctx) {
+        dctx = ZSTD_createDCtx();
+        FUZZ_ASSERT(dctx);
+    }
+    ZSTD_decompressDCtx(dctx, rBuf, neededBufSize, src, size);
+
+#ifndef STATEFULL_FUZZING
+    ZSTD_freeDCtx(dctx); dctx = NULL;
+#endif
+    return 0;
+}
diff --git a/fuzz/simple_round_trip.c b/fuzz/simple_round_trip.c
new file mode 100644 (file)
index 0000000..703ea58
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * This fuzz target performs a zstd round-trip test (compress & decompress),
+ * compares the result with the original, and calls abort() on corruption.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "fuzz_helpers.h"
+#include "zstd.h"
+
+static const int kMaxClevel = 19;
+
+static ZSTD_CCtx *cctx = NULL;
+static ZSTD_DCtx *dctx = NULL;
+static void* cBuf = NULL;
+static void* rBuf = NULL;
+static size_t bufSize = 0;
+static uint32_t seed;
+
+static size_t roundTripTest(void *result, size_t resultCapacity,
+                            void *compressed, size_t compressedCapacity,
+                            const void *src, size_t srcSize)
+{
+  int const cLevel = FUZZ_rand(&seed) % kMaxClevel;
+  size_t const cSize = ZSTD_compressCCtx(cctx, compressed, compressedCapacity,
+                                         src, srcSize, cLevel);
+  if (ZSTD_isError(cSize)) {
+    fprintf(stderr, "Compression error: %s\n", ZSTD_getErrorName(cSize));
+    return cSize;
+  }
+  return ZSTD_decompressDCtx(dctx, result, resultCapacity, compressed, cSize);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size)
+{
+    size_t const neededBufSize = ZSTD_compressBound(size);
+
+    seed = FUZZ_seed(src, size);
+
+    /* Allocate all buffers and contexts if not already allocated */
+    if (neededBufSize > bufSize) {
+        free(cBuf);
+        free(rBuf);
+        cBuf = malloc(neededBufSize);
+        rBuf = malloc(neededBufSize);
+        bufSize = neededBufSize;
+        FUZZ_ASSERT(cBuf && rBuf);
+    }
+    if (!cctx) {
+        cctx = ZSTD_createCCtx();
+        FUZZ_ASSERT(cctx);
+    }
+    if (!dctx) {
+        dctx = ZSTD_createDCtx();
+        FUZZ_ASSERT(dctx);
+    }
+
+    {
+        size_t const result =
+            roundTripTest(rBuf, neededBufSize, cBuf, neededBufSize, src, size);
+        FUZZ_ASSERT_MSG(!ZSTD_isError(result), ZSTD_getErrorName(result));
+        FUZZ_ASSERT_MSG(result == size, "Incorrect regenerated size");
+        FUZZ_ASSERT_MSG(!memcmp(src, rBuf, size), "Corruption!");
+    }
+#ifndef STATEFULL_FUZZING
+    ZSTD_freeCCtx(cctx); cctx = NULL;
+    ZSTD_freeDCtx(dctx); dctx = NULL;
+#endif
+    return 0;
+}
diff --git a/fuzz/stream_decompress.c b/fuzz/stream_decompress.c
new file mode 100644 (file)
index 0000000..778a426
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2016-present, Yann Collet, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * This fuzz target attempts to decompress the fuzzed data with the simple
+ * decompression function to ensure the decompressor never crashes.
+ */
+
+#define ZSTD_STATIC_LINKING_ONLY
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "fuzz_helpers.h"
+#include "zstd.h"
+
+static size_t const kBufSize = ZSTD_BLOCKSIZE_ABSOLUTEMAX;
+
+static ZSTD_DStream *dstream = NULL;
+static void* buf = NULL;
+uint32_t seed;
+
+static ZSTD_outBuffer makeOutBuffer(void)
+{
+  ZSTD_outBuffer buffer = { buf, 0, 0 };
+
+  buffer.size = (FUZZ_rand(&seed) % kBufSize) + 1;
+  FUZZ_ASSERT(buffer.size <= kBufSize);
+
+  return buffer;
+}
+
+static ZSTD_inBuffer makeInBuffer(const uint8_t **src, size_t *size)
+{
+  ZSTD_inBuffer buffer = { *src, 0, 0 };
+
+  FUZZ_ASSERT(*size > 0);
+  buffer.size = (FUZZ_rand(&seed) % *size) + 1;
+  FUZZ_ASSERT(buffer.size <= *size);
+  *src += buffer.size;
+  *size -= buffer.size;
+
+  return buffer;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size)
+{
+    seed = FUZZ_seed(src, size);
+
+    /* Allocate all buffers and contexts if not already allocated */
+    if (!buf) {
+      buf = malloc(kBufSize);
+      FUZZ_ASSERT(buf);
+    }
+
+    if (!dstream) {
+        dstream = ZSTD_createDStream();
+        FUZZ_ASSERT(dstream);
+        FUZZ_ASSERT(!ZSTD_isError(ZSTD_initDStream(dstream)));
+    } else {
+        FUZZ_ASSERT(!ZSTD_isError(ZSTD_resetDStream(dstream)));
+    }
+
+    while (size > 0) {
+        ZSTD_inBuffer in = makeInBuffer(&src, &size);
+        while (in.pos != in.size) {
+            ZSTD_outBuffer out = makeOutBuffer();
+            size_t const rc = ZSTD_decompressStream(dstream, &out, &in);
+            if (ZSTD_isError(rc)) goto error;
+            if (rc == 0) FUZZ_ASSERT(!ZSTD_isError(ZSTD_resetDStream(dstream)));
+        }
+    }
+
+error:
+#ifndef STATEFULL_FUZZING
+    ZSTD_freeDStream(dstream); dstream = NULL;
+#endif
+    return 0;
+}
diff --git a/fuzz/stream_round_trip.c b/fuzz/stream_round_trip.c
new file mode 100644 (file)
index 0000000..17c7dfd
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * This fuzz target performs a zstd round-trip test (compress & decompress),
+ * compares the result with the original, and calls abort() on corruption.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "fuzz_helpers.h"
+#include "zstd.h"
+
+static const int kMaxClevel = 19;
+
+static ZSTD_CStream *cstream = NULL;
+static ZSTD_DCtx *dctx = NULL;
+static uint8_t* cBuf = NULL;
+static uint8_t* rBuf = NULL;
+static size_t bufSize = 0;
+static uint32_t seed;
+
+static ZSTD_outBuffer makeOutBuffer(uint8_t *dst, size_t capacity)
+{
+  ZSTD_outBuffer buffer = { dst, 0, 0 };
+
+  FUZZ_ASSERT(capacity > 0);
+  buffer.size = (FUZZ_rand(&seed) % capacity) + 1;
+  FUZZ_ASSERT(buffer.size <= capacity);
+
+  return buffer;
+}
+
+static ZSTD_inBuffer makeInBuffer(const uint8_t **src, size_t *size)
+{
+  ZSTD_inBuffer buffer = { *src, 0, 0 };
+
+  FUZZ_ASSERT(*size > 0);
+  buffer.size = (FUZZ_rand(&seed) % *size) + 1;
+  FUZZ_ASSERT(buffer.size <= *size);
+  *src += buffer.size;
+  *size -= buffer.size;
+
+  return buffer;
+}
+
+static size_t compress(uint8_t *dst, size_t capacity,
+                       const uint8_t *src, size_t srcSize)
+{
+    int cLevel = FUZZ_rand(&seed) % kMaxClevel;
+    size_t dstSize = 0;
+    FUZZ_ASSERT(!ZSTD_isError(ZSTD_initCStream(cstream, cLevel)));
+
+    while (srcSize > 0) {
+        ZSTD_inBuffer in = makeInBuffer(&src, &srcSize);
+        /* Mode controls the action. If mode == -1 we pick a new mode */
+        int mode = -1;
+        while (in.pos < in.size) {
+          ZSTD_outBuffer out = makeOutBuffer(dst, capacity);
+          /* Previous action finished, pick a new mode. */
+          if (mode == -1) mode = FUZZ_rand(&seed) % 10;
+          switch (mode) {
+            case 0: /* fall-though */
+            case 1: /* fall-though */
+            case 2: {
+                size_t const ret = ZSTD_flushStream(cstream, &out);
+                FUZZ_ASSERT_MSG(!ZSTD_isError(ret), ZSTD_getErrorName(ret));
+                if (ret == 0) mode = -1;
+                break;
+            }
+            case 3: {
+                size_t ret = ZSTD_endStream(cstream, &out);
+                FUZZ_ASSERT_MSG(!ZSTD_isError(ret), ZSTD_getErrorName(ret));
+                /* Reset the compressor when the frame is finished */
+                if (ret == 0) {
+                    cLevel = FUZZ_rand(&seed) % kMaxClevel;
+                    ret = ZSTD_initCStream(cstream, cLevel);
+                    FUZZ_ASSERT(!ZSTD_isError(ret));
+                    mode = -1;
+                }
+                break;
+            }
+            default: {
+                size_t const ret = ZSTD_compressStream(cstream, &out, &in);
+                FUZZ_ASSERT_MSG(!ZSTD_isError(ret), ZSTD_getErrorName(ret));
+                mode = -1;
+            }
+          }
+          dst += out.pos;
+          dstSize += out.pos;
+          capacity -= out.pos;
+        }
+    }
+    for (;;) {
+        ZSTD_outBuffer out = makeOutBuffer(dst, capacity);
+        size_t const ret = ZSTD_endStream(cstream, &out);
+        FUZZ_ASSERT_MSG(!ZSTD_isError(ret), ZSTD_getErrorName(ret));
+
+        dst += out.pos;
+        dstSize += out.pos;
+        capacity -= out.pos;
+        if (ret == 0) break;
+    }
+    return dstSize;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size)
+{
+    size_t const neededBufSize = ZSTD_compressBound(size) * 2;
+
+    seed = FUZZ_seed(src, size);
+
+    /* Allocate all buffers and contexts if not already allocated */
+    if (neededBufSize > bufSize) {
+        free(cBuf);
+        free(rBuf);
+        cBuf = (uint8_t*)malloc(neededBufSize);
+        rBuf = (uint8_t*)malloc(neededBufSize);
+        bufSize = neededBufSize;
+        FUZZ_ASSERT(cBuf && rBuf);
+    }
+    if (!cstream) {
+        cstream = ZSTD_createCStream();
+        FUZZ_ASSERT(cstream);
+    }
+    if (!dctx) {
+        dctx = ZSTD_createDCtx();
+        FUZZ_ASSERT(dctx);
+    }
+
+    {
+        size_t const cSize = compress(cBuf, neededBufSize, src, size);
+        size_t const rSize =
+            ZSTD_decompressDCtx(dctx, rBuf, neededBufSize, cBuf, cSize);
+        FUZZ_ASSERT_MSG(!ZSTD_isError(rSize), ZSTD_getErrorName(rSize));
+        FUZZ_ASSERT_MSG(rSize == size, "Incorrect regenerated size");
+        FUZZ_ASSERT_MSG(!memcmp(src, rBuf, size), "Corruption!");
+    }
+
+#ifndef STATEFULL_FUZZING
+    ZSTD_freeCStream(cstream); cstream = NULL;
+    ZSTD_freeDCtx(dctx); dctx = NULL;
+#endif
+    return 0;
+}