[zlib] Rewrite deflate_fuzzer: cover more configs, stream the input

Zlib has a lot of parameters that can be used for deflate compression.
This makes the fuzzer cover a much larger set of them.

It also streams the input in an attempt to cover more interesting code
paths.

I have verified that this allows the fuzzer to hit the issues in the
first two linked bugs. Let's see if it finds something else too :-)

Bug: 1113142, 1113596, 708726
Change-Id: Ib345460c25787ee639b626cc37fc887017d65246
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2433344
Reviewed-by: Max Moroz <mmoroz@chromium.org>
Reviewed-by: Adenilson Cavalcanti <cavalcantii@chromium.org>
Commit-Queue: Hans Wennborg <hans@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811613}
GitOrigin-RevId: a7d72757624ece89b5e6c98d25c73f841a2844dc
diff --git a/contrib/tests/fuzzers/deflate_fuzzer.cc b/contrib/tests/fuzzers/deflate_fuzzer.cc
index 6098ff1..c00e715 100644
--- a/contrib/tests/fuzzers/deflate_fuzzer.cc
+++ b/contrib/tests/fuzzers/deflate_fuzzer.cc
@@ -2,46 +2,73 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <fuzzer/FuzzedDataProvider.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <cassert>
 #include <vector>
 
 #include "third_party/zlib/zlib.h"
 
-static Bytef buffer[256 * 1024] = {0};
+// Fuzzer builds often have NDEBUG set, so roll our own assert macro.
+#define ASSERT(cond)                                                           \
+  do {                                                                         \
+    if (!(cond)) {                                                             \
+      fprintf(stderr, "%s:%d Assert failed: %s\n", __FILE__, __LINE__, #cond); \
+      exit(1);                                                                 \
+    }                                                                          \
+  } while (0)
 
-// Entry point for LibFuzzer.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  // zlib's deflate requires non-zero input sizes
-  if (!size)
-    return 0;
-
-  // We need to strip the 'const' for zlib.
-  std::vector<unsigned char> input_buffer{data, data+size};
-
-  uLongf buffer_length = static_cast<uLongf>(sizeof(buffer));
+  FuzzedDataProvider fdp(data, size);
+  int level = fdp.PickValueInArray({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
+  int windowBits = fdp.PickValueInArray({9, 10, 11, 12, 13, 14, 15});
+  int memLevel = fdp.PickValueInArray({1, 2, 3, 4, 5, 6, 7, 8, 9});
+  int strategy = fdp.PickValueInArray(
+      {Z_DEFAULT_STRATEGY, Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED});
+  std::vector<uint8_t> src = fdp.ConsumeRemainingBytes<uint8_t>();
 
   z_stream stream;
-  stream.next_in = input_buffer.data();
-  stream.avail_in = size;
-  stream.total_in = size;
-  stream.next_out = buffer;
-  stream.avail_out = buffer_length;
-  stream.total_out = buffer_length;
   stream.zalloc = Z_NULL;
   stream.zfree = Z_NULL;
 
-  if (Z_OK != deflateInit(&stream, Z_DEFAULT_COMPRESSION)) {
-    deflateEnd(&stream);
-    assert(false);
+  // Compress the data one byte at a time to exercise the streaming code.
+  int ret =
+      deflateInit2(&stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
+  ASSERT(ret == Z_OK);
+  std::vector<uint8_t> compressed(src.size() * 2 + 1000);
+  stream.next_out = compressed.data();
+  stream.avail_out = compressed.size();
+  for (uint8_t b : src) {
+    stream.next_in = &b;
+    stream.avail_in = 1;
+    ret = deflate(&stream, Z_NO_FLUSH);
+    ASSERT(ret == Z_OK);
   }
-
-  auto deflate_result = deflate(&stream, Z_NO_FLUSH);
+  stream.next_in = Z_NULL;
+  stream.avail_in = 0;
+  ret = deflate(&stream, Z_FINISH);
+  ASSERT(ret == Z_STREAM_END);
+  compressed.resize(compressed.size() - stream.avail_out);
   deflateEnd(&stream);
-  if (Z_OK != deflate_result)
-    assert(false);
+
+  // Verify that the data decompresses correctly.
+  ret = inflateInit2(&stream, windowBits);
+  ASSERT(ret == Z_OK);
+  // Make room for at least one byte so it's never empty.
+  std::vector<uint8_t> decompressed(src.size() + 1);
+  stream.next_in = compressed.data();
+  stream.avail_in = compressed.size();
+  stream.next_out = decompressed.data();
+  stream.avail_out = decompressed.size();
+  ret = inflate(&stream, Z_FINISH);
+  ASSERT(ret == Z_STREAM_END);
+  decompressed.resize(decompressed.size() - stream.avail_out);
+  inflateEnd(&stream);
+
+  ASSERT(decompressed == src);
 
   return 0;
 }