Bug 1326416 - Update libFuzzer revision r=franziskus
authorTim Taubert <ttaubert@mozilla.com>
Mon, 09 Jan 2017 12:15:40 -0500
changeset 13009 9382ecc1b8a8ccd297cf3591e5c7cec5fa4da5fc
parent 13008 1029a9d94607af2f8e433ca716be97c75bb9b996
child 13010 04a60a1de49e9c2f007ef5ca3532c54a66ffbb5c
push id1914
push userttaubert@mozilla.com
push dateMon, 09 Jan 2017 17:16:45 +0000
reviewersfranziskus
bugs1326416
Bug 1326416 - Update libFuzzer revision r=franziskus Differential Revision: https://nss-review.dev.mozaws.net/D132
automation/taskcluster/scripts/fuzz.sh
fuzz/cert_target.cc
fuzz/clone_libfuzzer.sh
fuzz/fuzz.gyp
fuzz/initialize.cc
fuzz/nssfuzz.cc
fuzz/pkcs8_target.cc
fuzz/quickder_targets.cc
fuzz/registry.h
fuzz/shared.h
fuzz/spki_target.cc
--- a/automation/taskcluster/scripts/fuzz.sh
+++ b/automation/taskcluster/scripts/fuzz.sh
@@ -12,9 +12,9 @@ fetch_dist
 
 # Clone corpus.
 ./nss/fuzz/clone_corpus.sh
 
 # Fetch objdir name.
 objdir=$(cat dist/latest)
 
 # Run nssfuzz.
-LD_LIBRARY_PATH=$LD_LIBRARY_PATH:dist/$objdir/lib dist/$objdir/bin/nssfuzz $*
+LD_LIBRARY_PATH=$LD_LIBRARY_PATH:dist/$objdir/lib dist/$objdir/bin/nssfuzz-$*
new file mode 100644
--- /dev/null
+++ b/fuzz/cert_target.cc
@@ -0,0 +1,18 @@
+/* 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 "FuzzerInternal.h"
+#include "FuzzerRandom.h"
+#include "asn1_mutators.h"
+#include "shared.h"
+
+extern const uint16_t DEFAULT_MAX_LENGTH = 3072U;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+  CERTCertificate cert;
+  QuickDERDecode(&cert, SEC_SignedCertificateTemplate, Data, Size);
+  return 0;
+}
+
+ADD_CUSTOM_MUTATORS({&ASN1MutatorFlipConstructed, &ASN1MutatorChangeType})
--- a/fuzz/clone_libfuzzer.sh
+++ b/fuzz/clone_libfuzzer.sh
@@ -1,22 +1,68 @@
 #!/bin/sh
 
 d=$(dirname $0)
-$d/git-copy.sh https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer 1b543d6e5073b56be214394890c9193979a3d7e1 $d/libFuzzer
+$d/git-copy.sh https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer e6cbbd6ba1cd57e52cb3a237974c89911b08b5d7 $d/libFuzzer
 
+# [https://llvm.org/bugs/show_bug.cgi?id=31583]
+# Libfuzzer currently disables all internal default mutators when we specify
+# a custom one. Need to file a bug to maybe have an option to disable this, or
+# make ours the default behavior.
 cat <<EOF | patch -p0 -d $d
 diff --git libFuzzer/FuzzerMutate.cpp libFuzzer/FuzzerMutate.cpp
 --- libFuzzer/FuzzerMutate.cpp
 +++ libFuzzer/FuzzerMutate.cpp
-@@ -53,10 +53,9 @@
+@@ -52,10 +52,9 @@
      DefaultMutators.push_back(
          {&MutationDispatcher::Mutate_AddWordFromTORC, "CMP"});
 
 +  Mutators = DefaultMutators;
    if (EF->LLVMFuzzerCustomMutator)
      Mutators.push_back({&MutationDispatcher::Mutate_Custom, "Custom"});
 -  else
 -    Mutators = DefaultMutators;
 
    if (EF->LLVMFuzzerCustomCrossOver)
      Mutators.push_back(
 EOF
+
+# [https://llvm.org/bugs/show_bug.cgi?id=31318]
+# This prevents a known buffer overrun that won't be fixed as the affected code
+# will go away in the near future. Until that is we have to patch it as we seem
+# to constantly run into it.
+cat <<EOF | patch -p0 -d $d
+diff --git libFuzzer/FuzzerLoop.cpp libFuzzer/FuzzerLoop.cpp
+--- libFuzzer/FuzzerLoop.cpp
++++ libFuzzer/FuzzerLoop.cpp
+@@ -472,6 +472,9 @@
+   uint8_t dummy;
+   ExecuteCallback(&dummy, 0);
+
++  // Number of counters might have changed.
++  PrepareCounters(&MaxCoverage);
++
+   for (const auto &U : *InitialCorpus) {
+     if (size_t NumFeatures = RunOne(U)) {
+       CheckExitOnSrcPosOrItem();
+EOF
+
+# Latest Libfuzzer uses __sanitizer_dump_coverage(), a symbol to be introduced
+# with LLVM 4.0. To keep our code working with LLVM 3.x to simplify development
+# of fuzzers we'll just provide it ourselves.
+cat <<EOF | patch -p0 -d $d
+diff --git libFuzzer/FuzzerTracePC.cpp libFuzzer/FuzzerTracePC.cpp
+--- libFuzzer/FuzzerTracePC.cpp
++++ libFuzzer/FuzzerTracePC.cpp
+@@ -24,6 +24,12 @@
+ #include <set>
+ #include <sstream>
+
++#if defined(__clang_major__) && (__clang_major__ == 3)
++void __sanitizer_dump_coverage(const uintptr_t *pcs, uintptr_t len) {
++  // SanCov in LLVM 4.x will provide this symbol. Make 3.x work.
++}
++#endif
++
+ namespace fuzzer {
+
+ TracePC TPC;
+EOF
--- a/fuzz/fuzz.gyp
+++ b/fuzz/fuzz.gyp
@@ -10,45 +10,86 @@
     {
       'target_name': 'libFuzzer',
       'type': 'static_library',
       'sources': [
         'libFuzzer/FuzzerCrossOver.cpp',
         'libFuzzer/FuzzerDriver.cpp',
         'libFuzzer/FuzzerExtFunctionsDlsym.cpp',
         'libFuzzer/FuzzerExtFunctionsWeak.cpp',
+        'libFuzzer/FuzzerExtFunctionsWeakAlias.cpp',
         'libFuzzer/FuzzerIO.cpp',
+        'libFuzzer/FuzzerIOPosix.cpp',
+        'libFuzzer/FuzzerIOWindows.cpp',
         'libFuzzer/FuzzerLoop.cpp',
+        'libFuzzer/FuzzerMain.cpp',
+        'libFuzzer/FuzzerMerge.cpp',
         'libFuzzer/FuzzerMutate.cpp',
         'libFuzzer/FuzzerSHA1.cpp',
         'libFuzzer/FuzzerTracePC.cpp',
         'libFuzzer/FuzzerTraceState.cpp',
         'libFuzzer/FuzzerUtil.cpp',
         'libFuzzer/FuzzerUtilDarwin.cpp',
         'libFuzzer/FuzzerUtilLinux.cpp',
+        'libFuzzer/FuzzerUtilPosix.cpp',
+        'libFuzzer/FuzzerUtilWindows.cpp',
       ],
       'direct_dependent_settings': {
         'include_dirs': [
           'libFuzzer',
         ],
       }
     },
     {
-      'target_name': 'nssfuzz',
+      'target_name': 'nssfuzz-cert',
       'type': 'executable',
       'sources': [
         'asn1_mutators.cc',
-        'nssfuzz.cc',
-        'pkcs8_target.cc',
-        'quickder_targets.cc',
+        'cert_target.cc',
+        'initialize.cc',
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         'libFuzzer',
       ],
+    },
+    {
+      'target_name': 'nssfuzz-pkcs8',
+      'type': 'executable',
+      'sources': [
+        'asn1_mutators.cc',
+        'initialize.cc',
+        'pkcs8_target.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+        'libFuzzer',
+      ],
+    },
+    {
+      'target_name': 'nssfuzz-spki',
+      'type': 'executable',
+      'sources': [
+        'asn1_mutators.cc',
+        'spki_target.cc',
+        'initialize.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+        'libFuzzer',
+      ],
+    },
+    {
+      'target_name': 'nssfuzz',
+      'type': 'none',
+      'dependencies': [
+        'nssfuzz-cert',
+        'nssfuzz-pkcs8',
+        'nssfuzz-spki',
+      ]
     }
   ],
   'target_defaults': {
     'variables': {
       'debug_optimization_level': '2',
     },
     'cflags/': [
       ['exclude', '-fsanitize-coverage'],
new file mode 100644
--- /dev/null
+++ b/fuzz/initialize.cc
@@ -0,0 +1,54 @@
+/* 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 <string.h>
+#include <algorithm>
+#include <iostream>
+#include <vector>
+
+#include "assert.h"
+
+extern const uint16_t DEFAULT_MAX_LENGTH;
+
+const uint16_t MERGE_MAX_LENGTH = 50000U;
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
+  std::vector<std::string> args(*argv, *argv + *argc);
+
+  auto hasMaxLenArg = [](std::string &a) { return a.find("-max_len=") == 0; };
+
+  // Nothing to do if a max_len argument is given.
+  if (any_of(args.begin(), args.end(), hasMaxLenArg)) {
+    return 0;
+  }
+
+  auto hasMergeArg = [](std::string &a) { return a.find("-merge=1") == 0; };
+
+  uint16_t max_length = DEFAULT_MAX_LENGTH;
+
+  // Set specific max_len when merging.
+  if (any_of(args.begin(), args.end(), hasMergeArg)) {
+    max_length = MERGE_MAX_LENGTH;
+  }
+
+  std::cerr << "INFO: MaxLen: " << max_length << std::endl;
+  std::string param = "-max_len=" + std::to_string(max_length);
+
+  // Copy original arguments.
+  char **new_args = new char *[*argc + 1];
+  for (int i = 0; i < *argc; i++) {
+    new_args[i] = (*argv)[i];
+  }
+
+  // Append corpus max length.
+  size_t param_len = param.size() + 1;
+  new_args[*argc] = new char[param_len];
+  memcpy(new_args[*argc], param.c_str(), param_len);
+
+  // Update arguments.
+  (*argc)++;
+  *argv = new_args;
+
+  return 0;
+}
deleted file mode 100644
--- a/fuzz/nssfuzz.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*- 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 "FuzzerInternal.h"
-#include "FuzzerMutate.h"
-#include "FuzzerRandom.h"
-#include "registry.h"
-#include "shared.h"
-
-using namespace std;
-
-static vector<Mutator> gMutators;
-
-class Args {
- public:
-  Args(int argc, char **argv) : args_(argv, argv + argc) {}
-
-  string &operator[](const int idx) { return args_[idx]; }
-
-  bool Has(const string &arg) {
-    return any_of(args_.begin(), args_.end(),
-                  [&arg](string &a) { return a.find(arg) == 0; });
-  }
-
-  void Append(const string &arg) { args_.push_back(arg); }
-
-  void Remove(const int index) {
-    assert(index < count());
-    args_.erase(args_.begin() + index);
-  }
-
-  vector<char *> argv() {
-    vector<char *> out;
-    out.resize(count());
-
-    transform(args_.begin(), args_.end(), out.begin(),
-              [](string &a) { return const_cast<char *>(a.c_str()); });
-
-    return out;
-  }
-
-  size_t count() { return args_.size(); }
-
- private:
-  vector<string> args_;
-};
-
-void printUsage(Args &args) {
-  size_t sep = args[0].rfind("/") + 1;
-  string progName = args[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) {
-  Args args(argc, argv);
-
-  if (args.count() < 2 || !Registry::Has(args[1])) {
-    printUsage(args);
-    return 1;
-  }
-
-  string targetName(args[1]);
-
-  // Add target mutators.
-  auto mutators = Registry::Mutators(targetName);
-  gMutators.insert(gMutators.end(), mutators.begin(), mutators.end());
-
-  // Remove the target argument when -workers=x or -jobs=y is NOT given.
-  // If both are given, libFuzzer will spawn multiple processes for the target.
-  if (!args.Has("-workers=") || !args.Has("-jobs=")) {
-    args.Remove(1);
-  }
-
-  // Set default max_len arg, if none given and we're not merging.
-  if (!args.Has("-max_len=") && !args.Has("-merge=1")) {
-    uint16_t maxLen = Registry::MaxLen(targetName);
-    args.Append("-max_len=" + to_string(maxLen));
-  }
-
-  // Hand control to the libFuzzer driver.
-  vector<char *> args_new(args.argv());
-  argc = args_new.size();
-  argv = args_new.data();
-
-  return fuzzer::FuzzerDriver(&argc, &argv, Registry::Func(targetName));
-}
-
-extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
-                                          size_t MaxSize, unsigned int Seed) {
-  if (gMutators.empty()) {
-    return 0;
-  }
-
-  // Forward to a pseudorandom mutator.
-  fuzzer::Random R(Seed);
-  return gMutators.at(R(gMutators.size()))(Data, Size, MaxSize, Seed);
-}
--- a/fuzz/pkcs8_target.cc
+++ b/fuzz/pkcs8_target.cc
@@ -1,25 +1,27 @@
-/* -*- 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 <vector>
 
 #include "keyhi.h"
 #include "pk11pub.h"
 
-#include "registry.h"
+#include "FuzzerInternal.h"
+#include "FuzzerRandom.h"
+#include "asn1_mutators.h"
+#include "assert.h"
 #include "shared.h"
 
-extern "C" int pkcs8_fuzzing_target(const uint8_t *Data, size_t Size) {
+extern const uint16_t DEFAULT_MAX_LENGTH = 2048U;
+
+extern "C" int LLVMFuzzerTestOneInput(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);
 
@@ -29,10 +31,9 @@ extern "C" int pkcs8_fuzzing_target(cons
                                                nullptr) == SECSuccess) {
     SECKEY_DestroyPrivateKey(key);
   }
 
   PK11_FreeSlot(slot);
   return 0;
 }
 
-REGISTER_FUZZING_TARGET("pkcs8", pkcs8_fuzzing_target, 2048, "PKCS#8 Import",
-                        {})
+ADD_CUSTOM_MUTATORS({&ASN1MutatorFlipConstructed, &ASN1MutatorChangeType})
deleted file mode 100644
--- a/fuzz/quickder_targets.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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 <stdint.h>
-
-#include "asn1_mutators.h"
-#include "cert.h"
-#include "registry.h"
-
-void QuickDERDecode(void *dst, const SEC_ASN1Template *tpl, const uint8_t *buf,
-                    size_t len) {
-  PORTCheapArenaPool pool;
-  SECItem data = {siBuffer, const_cast<unsigned char *>(buf),
-                  static_cast<unsigned int>(len)};
-
-  PORT_InitCheapArena(&pool, DER_DEFAULT_CHUNKSIZE);
-  (void)SEC_QuickDERDecodeItem(&pool.arena, dst, tpl, &data);
-  PORT_DestroyCheapArena(&pool);
-}
-
-extern "C" int cert_fuzzing_target(const uint8_t *Data, size_t Size) {
-  CERTCertificate cert;
-  QuickDERDecode(&cert, SEC_SignedCertificateTemplate, Data, Size);
-  return 0;
-}
-
-REGISTER_FUZZING_TARGET("cert", cert_fuzzing_target, 3072, "Certificate Import",
-                        {&ASN1MutatorFlipConstructed, &ASN1MutatorChangeType})
-
-extern "C" int spki_fuzzing_target(const uint8_t *Data, size_t Size) {
-  CERTSubjectPublicKeyInfo spki;
-  QuickDERDecode(&spki, CERT_SubjectPublicKeyInfoTemplate, Data, Size);
-  return 0;
-}
-
-REGISTER_FUZZING_TARGET("spki", spki_fuzzing_target, 1024, "SPKI Import",
-                        {&ASN1MutatorFlipConstructed, &ASN1MutatorChangeType})
deleted file mode 100644
--- a/fuzz/registry.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- 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 "FuzzerInternal.h"
-#include "nss.h"
-
-using namespace fuzzer;
-using namespace std;
-
-typedef decltype(LLVMFuzzerCustomMutator)* Mutator;
-
-class Registry {
- public:
-  static void Add(string name, UserCallback func, uint16_t max_len, string desc,
-                  vector<Mutator> mutators = {}) {
-    assert(!Has(name));
-    GetInstance().targets_[name] = TargetData(func, max_len, desc, mutators);
-  }
-
-  static bool Has(string name) {
-    return GetInstance().targets_.count(name) > 0;
-  }
-
-  static UserCallback Func(string name) {
-    assert(Has(name));
-    return get<0>(Get(name));
-  }
-
-  static uint16_t MaxLen(string name) {
-    assert(Has(name));
-    return get<1>(Get(name));
-  }
-
-  static string& Desc(string name) {
-    assert(Has(name));
-    return get<2>(Get(name));
-  }
-
-  static vector<Mutator>& Mutators(string name) {
-    assert(Has(name));
-    return get<3>(Get(name));
-  }
-
-  static vector<string> Names() {
-    vector<string> names;
-    for (auto& it : GetInstance().targets_) {
-      names.push_back(it.first);
-    }
-    return names;
-  }
-
- private:
-  typedef tuple<UserCallback, uint16_t, string, vector<Mutator>> TargetData;
-
-  static Registry& GetInstance() {
-    static Registry registry;
-    return registry;
-  }
-
-  static TargetData& Get(string name) { return GetInstance().targets_[name]; }
-
-  Registry() {}
-
-  map<string, TargetData> targets_;
-};
-
-#define REGISTER_FUZZING_TARGET(name, func, max_len, desc, ...) \
-  static void __attribute__((constructor)) Register_##func() {  \
-    Registry::Add(name, func, max_len, desc, __VA_ARGS__);      \
-  }
-
-#endif  // registry_h__
--- a/fuzz/shared.h
+++ b/fuzz/shared.h
@@ -2,17 +2,38 @@
 /* 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 "cert.h"
 #include "nss.h"
 
 class NSSDatabase {
  public:
   NSSDatabase() { NSS_NoDB_Init(nullptr); }
   ~NSSDatabase() { NSS_Shutdown(); }
 };
 
+void QuickDERDecode(void *dst, const SEC_ASN1Template *tpl, const uint8_t *buf,
+                    size_t len) {
+  PORTCheapArenaPool pool;
+  SECItem data = {siBuffer, const_cast<unsigned char *>(buf),
+                  static_cast<unsigned int>(len)};
+
+  PORT_InitCheapArena(&pool, DER_DEFAULT_CHUNKSIZE);
+  (void)SEC_QuickDERDecodeItem(&pool.arena, dst, tpl, &data);
+  PORT_DestroyCheapArena(&pool);
+}
+
+#define ADD_CUSTOM_MUTATORS(...)                                             \
+  extern "C" size_t LLVMFuzzerCustomMutator(                                 \
+      uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed) {       \
+    std::vector<decltype(LLVMFuzzerCustomMutator) *> mutators = __VA_ARGS__; \
+    fuzzer::Random R(Seed);                                                  \
+    auto idx = R(mutators.size());                                           \
+    return mutators.at(idx)(Data, Size, MaxSize, Seed);                      \
+  }
+
 #endif  // shared_h__
new file mode 100644
--- /dev/null
+++ b/fuzz/spki_target.cc
@@ -0,0 +1,18 @@
+/* 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 "FuzzerInternal.h"
+#include "FuzzerRandom.h"
+#include "asn1_mutators.h"
+#include "shared.h"
+
+extern const uint16_t DEFAULT_MAX_LENGTH = 1024U;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+  CERTSubjectPublicKeyInfo spki;
+  QuickDERDecode(&spki, CERT_SubjectPublicKeyInfoTemplate, Data, Size);
+  return 0;
+}
+
+ADD_CUSTOM_MUTATORS({&ASN1MutatorFlipConstructed, &ASN1MutatorChangeType})