From 4be7f0d45c6886f2fb8ca2bc4ca56186665e283c Mon Sep 17 00:00:00 2001 From: Nick Terrell Date: Thu, 29 Jun 2017 16:53:52 -0700 Subject: [PATCH] [fuzz] Add libFuzzer targets * 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 | 108 +++++++++++++++++++++++++++ fuzz/README.md | 34 +++++++++ fuzz/fuzz.h | 52 +++++++++++++ fuzz/fuzz_helpers.h | 70 ++++++++++++++++++ fuzz/regression_driver.c | 69 ++++++++++++++++++ fuzz/simple_decompress.c | 46 ++++++++++++ fuzz/simple_round_trip.c | 81 +++++++++++++++++++++ fuzz/stream_decompress.c | 85 ++++++++++++++++++++++ fuzz/stream_round_trip.c | 153 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 698 insertions(+) create mode 100644 fuzz/Makefile create mode 100644 fuzz/README.md create mode 100644 fuzz/fuzz.h create mode 100644 fuzz/fuzz_helpers.h create mode 100644 fuzz/regression_driver.c create mode 100644 fuzz/simple_decompress.c create mode 100644 fuzz/simple_round_trip.c create mode 100644 fuzz/stream_decompress.c create mode 100644 fuzz/stream_round_trip.c diff --git a/fuzz/Makefile b/fuzz/Makefile new file mode 100644 index 00000000..9b084fd3 --- /dev/null +++ b/fuzz/Makefile @@ -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 index 00000000..38a4f3d1 --- /dev/null +++ b/fuzz/README.md @@ -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 index 00000000..5b71aba8 --- /dev/null +++ b/fuzz/fuzz.h @@ -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 +#include + +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 index 00000000..5f07fa4d --- /dev/null +++ b/fuzz/fuzz_helpers.h @@ -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 +#include + +#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 index 00000000..eee5f0a2 --- /dev/null +++ b/fuzz/regression_driver.c @@ -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 +#include +#include +#include + +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 index 00000000..c22ad7c5 --- /dev/null +++ b/fuzz/simple_decompress.c @@ -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 +#include +#include +#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 index 00000000..703ea582 --- /dev/null +++ b/fuzz/simple_round_trip.c @@ -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 +#include +#include +#include +#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 index 00000000..778a426d --- /dev/null +++ b/fuzz/stream_decompress.c @@ -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 +#include +#include +#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 index 00000000..17c7dfdd --- /dev/null +++ b/fuzz/stream_round_trip.c @@ -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 +#include +#include +#include +#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; +}