Bug 1308874 - Land basic libFuzzer fuzzing framework r=franziskus
authorTim Taubert <ttaubert@mozilla.com>
Wed, 12 Oct 2016 15:30:05 +0200
changeset 12706 920f90ee03a61ae7021607a232e560345e6a6412
parent 12705 ee9043ba05120207f01ca426d47aac50cc11ef7c
child 12707 c3854cc8537134eb49f37a842d7247f93ecae2a2
push id1658
push userttaubert@mozilla.com
push dateWed, 12 Oct 2016 13:30:27 +0000
reviewersfranziskus
bugs1308874
Bug 1308874 - Land basic libFuzzer fuzzing framework r=franziskus Differential Revision: https://nss-dev.phacility.com/D76
.gitignore
.hgignore
coreconf/sanitizers.mk
fuzz/.clang-format
fuzz/Makefile
fuzz/clone_corpus.sh
fuzz/clone_libfuzzer.sh
fuzz/common.mk
fuzz/libFuzzer/Makefile
fuzz/libFuzzer/config.mk
fuzz/libFuzzer/manifest.mn
fuzz/manifest.mn
fuzz/nssfuzz/Makefile
fuzz/nssfuzz/cert_target.cc
fuzz/nssfuzz/manifest.mn
fuzz/nssfuzz/nssfuzz.cc
fuzz/nssfuzz/pkcs8_target.cc
fuzz/nssfuzz/registry.h
fuzz/nssfuzz/shared.h
fuzz/nssfuzz/spki_target.cc
manifest.mn
--- a/.gitignore
+++ b/.gitignore
@@ -8,8 +8,10 @@
 *.rej
 *.patch
 GPATH
 GRTAGS
 GTAGS
 #*
 .#*
 .ycm_extra_conf.py*
+fuzz/libFuzzer/*
+fuzz/corpus
--- a/.hgignore
+++ b/.hgignore
@@ -8,8 +8,10 @@ syntax: glob
 *.rej
 *.patch
 GPATH
 GRTAGS
 GTAGS
 #*
 .#*
 .ycm_extra_conf.py*
+fuzz/libFuzzer/*
+fuzz/corpus
--- a/coreconf/sanitizers.mk
+++ b/coreconf/sanitizers.mk
@@ -3,16 +3,20 @@
 
 ifeq ($(USE_ASAN), 1)
 SANITIZER_FLAGS_COMMON = -fsanitize=address
 
 ifeq ($(USE_UBSAN), 1)
 SANITIZER_FLAGS_COMMON += -fsanitize=undefined -fno-sanitize-recover=undefined
 endif
 
+ifeq ($(FUZZ), 1)
+SANITIZER_FLAGS_COMMON += -fsanitize-coverage=edge
+endif
+
 SANITIZER_FLAGS_COMMON += $(EXTRA_SANITIZER_FLAGS)
 SANITIZER_CFLAGS = $(SANITIZER_FLAGS_COMMON)
 SANITIZER_LDFLAGS = $(SANITIZER_FLAGS_COMMON)
 OS_CFLAGS += $(SANITIZER_CFLAGS)
 LDFLAGS += $(SANITIZER_LDFLAGS)
 
 # ASan needs frame pointers to save stack traces for allocation/free sites.
 # (Warning: some platforms, like ARM Linux in Thumb mode, don't have useful
new file mode 100644
--- /dev/null
+++ b/fuzz/.clang-format
@@ -0,0 +1,4 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+...
new file mode 100644
--- /dev/null
+++ b/fuzz/Makefile
@@ -0,0 +1,42 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100755
--- /dev/null
+++ b/fuzz/clone_corpus.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd $(dirname $0)
+git clone https://github.com/mozilla/nss-fuzzing-corpus corpus
new file mode 100755
--- /dev/null
+++ b/fuzz/clone_libfuzzer.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+cd $(dirname $0)
+mkdir tmp/
+git clone -q https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer tmp/
+mv tmp/.git libFuzzer
+rm -fr tmp
+cd libFuzzer
+git reset --hard 4333f2ca71eb7951fcafcdcb111012fbe25c5e7e
new file mode 100644
--- /dev/null
+++ b/fuzz/common.mk
@@ -0,0 +1,10 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MKPROG = $(CCC)
+MKSHLIB = $(CCC) $(DSO_LDOPTS) $(DARWIN_SDK_SHLIBFLAGS)
+
+CXXFLAGS += -std=c++11
new file mode 100644
--- /dev/null
+++ b/fuzz/libFuzzer/Makefile
@@ -0,0 +1,45 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+include config.mk
+
+include ../common.mk
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100644
--- /dev/null
+++ b/fuzz/libFuzzer/config.mk
@@ -0,0 +1,14 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# According to the LLVM docs, LibFuzzer isn't supposed to be built with any
+# sanitizer flags and in fact, building it with ASan coverage currently causes
+# Clang 3.9+ to crash, so we filter out all sanitizer-related flags here.
+CXXFLAGS := $(filter-out -fsanitize%,$(CXXFLAGS))
+CFLAGS := $(filter-out -fsanitize%,$(CFLAGS))
+LDFLAGS := $(filter-out -fsanitize%,$(LDFLAGS))
+DARWIN_SDK_SHLIBFLAGS := $(filter-out -fsanitize%,$(DARWIN_SDK_SHLIBFLAGS))
+
+CXXFLAGS += -g -O2
new file mode 100644
--- /dev/null
+++ b/fuzz/libFuzzer/manifest.mn
@@ -0,0 +1,26 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+CORE_DEPTH = ../..
+DEPTH      = ../..
+MODULE = nss
+
+CPPSRCS = \
+      FuzzerCrossOver.cpp \
+      FuzzerDriver.cpp \
+      FuzzerExtFunctionsDlsym.cpp \
+      FuzzerExtFunctionsWeak.cpp \
+      FuzzerIO.cpp \
+      FuzzerLoop.cpp \
+      FuzzerMutate.cpp \
+      FuzzerSHA1.cpp \
+      FuzzerTracePC.cpp \
+      FuzzerTraceState.cpp \
+      FuzzerUtil.cpp \
+      FuzzerUtilDarwin.cpp \
+      FuzzerUtilLinux.cpp \
+      $(NULL)
+
+LIBRARY_NAME = Fuzzer
+LIBRARY_VERSION = 1
new file mode 100644
--- /dev/null
+++ b/fuzz/manifest.mn
@@ -0,0 +1,8 @@
+# 
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+CORE_DEPTH = ..
+DEPTH      = ..
+
+DIRS = libFuzzer nssfuzz
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/Makefile
@@ -0,0 +1,45 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY).   #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL)          #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL)       #
+#######################################################################
+
+include $(CORE_DEPTH)/cmd/platlibs.mk
+
+include ../common.mk
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
+#######################################################################
+
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL)                              #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL)                           #
+#######################################################################
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL).                              #
+#######################################################################
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/cert_target.cc
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <assert.h>
+#include <stdint.h>
+#include <memory>
+
+#include "cert.h"
+
+#include "registry.h"
+#include "shared.h"
+
+extern "C" int cert_fuzzing_target(const uint8_t *Data, size_t Size) {
+  SECItem data = {siBuffer, (unsigned char *)Data, (unsigned int)Size};
+
+  static std::unique_ptr<NSSDatabase> db(new NSSDatabase());
+  assert(db != nullptr);
+
+  static CERTCertDBHandle *certDB = CERT_GetDefaultCertDB();
+  assert(certDB != NULL);
+
+  CERTCertificate *cert =
+      CERT_NewTempCertificate(certDB, &data, nullptr, false, true);
+
+  if (cert) {
+    CERT_DestroyCertificate(cert);
+  }
+
+  return 0;
+}
+
+REGISTER_FUZZING_TARGET("cert", cert_fuzzing_target, 3072, "Certificate Import")
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/manifest.mn
@@ -0,0 +1,24 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+CORE_DEPTH = ../..
+DEPTH      = ../..
+MODULE = nss
+
+CPPSRCS = \
+      cert_target.cc \
+      pkcs8_target.cc \
+      spki_target.cc \
+      nssfuzz.cc \
+      $(NULL)
+
+INCLUDES += -I$(CORE_DEPTH)/fuzz/libFuzzer
+
+REQUIRES = nspr nss
+
+PROGRAM = nssfuzz
+
+EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)Fuzzer.$(LIB_SUFFIX)
+
+USE_STATIC_LIBS = 1
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/nssfuzz.cc
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <iomanip>
+#include <iostream>
+#include <memory>
+
+#include "keyhi.h"
+#include "pk11pub.h"
+
+#include "FuzzerInternal.h"
+#include "registry.h"
+#include "shared.h"
+
+using namespace std;
+
+void printUsage(const vector<string> &args) {
+  size_t sep = args.at(0).rfind("/") + 1;
+  string progName = args.at(0).substr(sep);
+
+  cerr << progName << " - Various libFuzzer targets for NSS" << endl << endl;
+  cerr << "Usage: " << progName << " <target> <libFuzzer options>" << endl
+       << endl;
+  cerr << "Valid targets:" << endl;
+
+  vector<string> names = Registry::Names();
+
+  // Find length of the longest name.
+  size_t name_w =
+      max_element(names.begin(), names.end(), [](string &a, string &b) {
+        return a.size() < b.size();
+      })->size();
+
+  // Find length of the longest description.
+  auto max = max_element(names.begin(), names.end(), [](string &a, string &b) {
+    return Registry::Desc(a).size() < Registry::Desc(b).size();
+  });
+  size_t desc_w = Registry::Desc(*max).size();
+
+  // Print list of targets.
+  for (string name : names) {
+    cerr << "  " << left << setw(name_w) << name << " - " << setw(desc_w)
+         << Registry::Desc(name)
+         << " [default max_len=" << Registry::MaxLen(name) << "]" << endl;
+  }
+
+  // Some usage examples.
+  cerr << endl << "Run fuzzer with a given corpus directory:" << endl;
+  cerr << "  " << progName << " <target> /path/to/corpus" << endl;
+
+  cerr << endl << "Run fuzzer with a single test input:" << endl;
+  cerr << "  " << progName
+       << " <target> ./crash-14d4355b971092e39572bc306a135ddf9f923e19" << endl;
+
+  cerr << endl
+       << "Specify the number of cores you wish to dedicate to fuzzing:"
+       << endl;
+  cerr << "  " << progName << " <target> -jobs=8 -workers=8 /path/to/corpus"
+       << endl;
+
+  cerr << endl << "Override the maximum length of a test input:" << endl;
+  cerr << "  " << progName << " <target> -max_len=2048 /path/to/corpus" << endl;
+
+  cerr << endl
+       << "Minimize a given corpus and put the result into 'new_corpus':"
+       << endl;
+  cerr << "  " << progName
+       << " <target> -merge=1 -max_len=50000 ./new_corpus /path/to/corpus"
+       << endl;
+
+  cerr << endl << "Merge new test inputs into a corpus:" << endl;
+  cerr
+      << "  " << progName
+      << " <target> -merge=1 -max_len=50000 /path/to/corpus ./inputs1 ./inputs2"
+      << endl;
+
+  cerr << endl << "Print libFuzzer usage information:" << endl;
+  cerr << "  " << progName << " <target> -help=1" << endl << endl;
+
+  cerr << "Check out the docs at http://llvm.org/docs/LibFuzzer.html" << endl;
+}
+
+int main(int argc, char **argv) {
+  vector<string> args(argv, argv + argc);
+
+  if (args.size() < 2 || !Registry::Has(args[1])) {
+    printUsage(args);
+    return 1;
+  }
+
+  string targetName = args.at(1);
+  uint16_t maxLen = Registry::MaxLen(targetName);
+  string maxLenArg = "-max_len=" + to_string(maxLen);
+
+  auto find = [](string &a) {
+    return a.find("-max_len=") == 0 || a.find("-merge=1") == 0;
+  };
+
+  if (any_of(args.begin(), args.end(), find)) {
+    // Remove the 2nd argument.
+    argv[1] = argv[0];
+    argv++;
+    argc--;
+  } else {
+    // Set default max_len arg, if none given and we're not merging.
+    argv[1] = const_cast<char *>(maxLenArg.c_str());
+  }
+
+  // Hand control to the libFuzzer driver.
+  return fuzzer::FuzzerDriver(&argc, &argv, Registry::Func(targetName));
+}
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/pkcs8_target.cc
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <assert.h>
+#include <stdint.h>
+#include <memory>
+
+#include "keyhi.h"
+#include "pk11pub.h"
+
+#include "registry.h"
+#include "shared.h"
+
+extern "C" int pkcs8_fuzzing_target(const uint8_t *Data, size_t Size) {
+  SECItem data = {siBuffer, (unsigned char *)Data, (unsigned int)Size};
+
+  static std::unique_ptr<NSSDatabase> db(new NSSDatabase());
+  assert(db != nullptr);
+
+  PK11SlotInfo *slot = PK11_GetInternalSlot();
+  assert(slot != nullptr);
+
+  SECKEYPrivateKey *key = nullptr;
+  if (PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, &data, nullptr, nullptr,
+                                               false, false, KU_ALL, &key,
+                                               nullptr) == SECSuccess) {
+    SECKEY_DestroyPrivateKey(key);
+  }
+
+  PK11_FreeSlot(slot);
+  return 0;
+}
+
+REGISTER_FUZZING_TARGET("pkcs8", pkcs8_fuzzing_target, 2048, "PKCS#8 Import")
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/registry.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef registry_h__
+#define registry_h__
+
+#include <map>
+#include "nss.h"
+#include "FuzzerInternal.h"
+
+class Registry {
+ public:
+  static void Add(std::string name, fuzzer::UserCallback func,
+                  uint16_t max_len, std::string desc) {
+    assert(!Has(name));
+    GetInstance().targets_[name] = TargetData(func, max_len, desc);
+  }
+
+  static bool Has(std::string name) {
+    return GetInstance().targets_.count(name) > 0;
+  }
+
+  static fuzzer::UserCallback Func(std::string name) {
+    assert(Has(name));
+    return std::get<0>(Get(name));
+  }
+
+  static uint16_t MaxLen(std::string name) {
+    assert(Has(name));
+    return std::get<1>(Get(name));
+  }
+
+  static std::string& Desc(std::string name) {
+    assert(Has(name));
+    return std::get<2>(Get(name));
+  }
+
+  static std::vector<std::string> Names() {
+    std::vector<std::string> names;
+    for (auto &it : GetInstance().targets_) {
+      names.push_back(it.first);
+    }
+    return names;
+  }
+
+ private:
+  typedef std::tuple<fuzzer::UserCallback, uint16_t, std::string> TargetData;
+
+  static Registry& GetInstance() {
+    static Registry registry;
+    return registry;
+  }
+
+  static TargetData& Get(std::string name) {
+    return GetInstance().targets_[name];
+  }
+
+  Registry() {}
+
+  std::map<std::string, TargetData> targets_;
+};
+
+#define REGISTER_FUZZING_TARGET(name, func, max_len, desc)            \
+  static void __attribute__ ((constructor)) RegisterFuzzingTarget() { \
+    Registry::Add(name, func, max_len, desc);                         \
+  }
+
+#endif // registry_h__
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/shared.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef shared_h__
+#define shared_h__
+
+#include "nss.h"
+
+class NSSDatabase {
+ public:
+  NSSDatabase() { NSS_NoDB_Init(nullptr); }
+  ~NSSDatabase() { NSS_Shutdown(); }
+};
+
+#endif // shared_h__
new file mode 100644
--- /dev/null
+++ b/fuzz/nssfuzz/spki_target.cc
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <assert.h>
+#include <stdint.h>
+#include <memory>
+
+#include "keyhi.h"
+#include "pk11pub.h"
+
+#include "registry.h"
+#include "shared.h"
+
+extern "C" int spki_fuzzing_target(const uint8_t *Data, size_t Size) {
+  SECItem data = {siBuffer, (unsigned char *)Data, (unsigned int)Size};
+
+  static std::unique_ptr<NSSDatabase> db(new NSSDatabase());
+  assert(db != nullptr);
+
+  CERTSubjectPublicKeyInfo *spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&data);
+
+  if (spki) {
+    SECKEYPublicKey *key = SECKEY_ExtractPublicKey(spki);
+    SECKEY_DestroyPublicKey(key);
+  }
+
+  SECKEY_DestroySubjectPublicKeyInfo(spki);
+
+  return 0;
+}
+
+REGISTER_FUZZING_TARGET("spki", spki_fuzzing_target, 1024, "SPKI Import")
--- a/manifest.mn
+++ b/manifest.mn
@@ -6,8 +6,12 @@ CORE_DEPTH = .
 DEPTH      = .
 
 IMPORTS =	nspr20/v4.8 \
 		$(NULL)
 
 RELEASE = nss
 
 DIRS = coreconf lib cmd external_tests
+
+ifdef FUZZ
+DIRS += fuzz
+endif