Bug 1330557 - Add basic TLS client fuzzer r=mt,franziskus
authorTim Taubert <ttaubert@mozilla.com>
Fri, 10 Feb 2017 16:23:22 +0100
changeset 13119 ea20484bb75de6837887bb4d6fc35d50aadf66c4
parent 13118 cd62d563112ad99927c9a291d15b97f77f5e794d
child 13120 f0c85797eb5498c12669320f22f04aceedcda9b1
push id2004
push userttaubert@mozilla.com
push dateFri, 10 Feb 2017 15:27:14 +0000
reviewersmt, franziskus
bugs1330557
Bug 1330557 - Add basic TLS client fuzzer r=mt,franziskus Differential Revision: https://nss-review.dev.mozaws.net/D145
automation/taskcluster/graph/src/extend.js
automation/taskcluster/scripts/run_clang_format.sh
cpputil/.clang-format
cpputil/README
cpputil/cpputil.gyp
cpputil/dummy_io.cc
cpputil/dummy_io.h
cpputil/dummy_io_fwd.cc
cpputil/scoped_ptrs.h
fuzz/fuzz.gyp
fuzz/tls-client.options
fuzz/tls_client_socket.cc
fuzz/tls_client_socket.h
fuzz/tls_client_target.cc
gtests/common/gtest.gypi
gtests/common/manifest.mn
gtests/common/scoped_ptrs.h
gtests/der_gtest/der_gtest.gyp
gtests/der_gtest/manifest.mn
gtests/freebl_gtest/freebl_gtest.gyp
gtests/google_test/google_test.gyp
gtests/pk11_gtest/manifest.mn
gtests/pk11_gtest/pk11_gtest.gyp
gtests/ssl_gtest/manifest.mn
gtests/ssl_gtest/ssl_gtest.gyp
gtests/ssl_gtest/test_io.cc
gtests/ssl_gtest/test_io.h
gtests/util_gtest/manifest.mn
gtests/util_gtest/util_gtest.gyp
nss-tool/common/scoped_ptrs.h
nss-tool/nss_tool.gyp
--- a/automation/taskcluster/graph/src/extend.js
+++ b/automation/taskcluster/graph/src/extend.js
@@ -321,26 +321,32 @@ async function scheduleFuzzing() {
     tests: "ssl_gtests gtests",
     cycle: "standard",
     symbol: "Gtest",
     kind: "test"
   }));
 
   // Schedule fuzzing runs.
   let run_base = merge(base, {parent: task_build, kind: "test"});
-  let mpi_base = merge(run_base, {group: "MPI"});
   scheduleFuzzingRun(run_base, "CertDN", "certDN", 4096);
   scheduleFuzzingRun(run_base, "Hash", "hash", 4096);
   scheduleFuzzingRun(run_base, "QuickDER", "quickder", 10000);
-  for (let mpi_name of ["add", "addmod", "div", "expmod", "mod", "mulmod",
-                        "sqr", "sqrmod", "sub", "submod"]) {
-    scheduleFuzzingRun(mpi_base, `MPI (${mpi_name})`, `mpi-${mpi_name}`,
-                       4096, mpi_name);
+
+  // Schedule MPI fuzzing runs.
+  let mpi_base = merge(run_base, {group: "MPI"});
+  let mpi_names = ["add", "addmod", "div", "expmod", "mod", "mulmod", "sqr",
+                   "sqrmod", "sub", "submod"];
+  for (let name of mpi_names) {
+    scheduleFuzzingRun(mpi_base, `MPI (${name})`, `mpi-${name}`, 4096, name);
   }
 
+  // Schedule TLS fuzzing runs.
+  let tls_base = merge(run_base, {group: "TLS"});
+  scheduleFuzzingRun(tls_base, "TLS Client", "tls-client", 20000, "client");
+
   return queue.submit();
 }
 
 /*****************************************************************************/
 
 async function scheduleTestBuilds() {
   let base = {
     platform: "linux64",
--- a/automation/taskcluster/scripts/run_clang_format.sh
+++ b/automation/taskcluster/scripts/run_clang_format.sh
@@ -37,16 +37,17 @@ else
          "$top/lib/util" \
          "$top/gtests/common" \
          "$top/gtests/der_gtest" \
          "$top/gtests/freebl_gtest" \
          "$top/gtests/pk11_gtest" \
          "$top/gtests/ssl_gtest" \
          "$top/gtests/util_gtest" \
          "$top/nss-tool" \
+         "$top/cpputil" \
     )
 fi
 
 for dir in "${dirs[@]}"; do
     find "$dir" -type f \( -name '*.[ch]' -o -name '*.cc' \) -exec clang-format -i {} \+
 done
 
 TMPFILE=$(mktemp /tmp/$(basename $0).XXXXXX)
new file mode 100644
--- /dev/null
+++ b/cpputil/.clang-format
@@ -0,0 +1,4 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+...
new file mode 100644
--- /dev/null
+++ b/cpputil/README
@@ -0,0 +1,11 @@
+######################################
+## PLEASE READ BEFORE USING CPPUTIL ##
+######################################
+
+This is a static library supposed to be mainly used by NSS internally. We use
+it for testing, fuzzing, and a few new tools written in C++ that we're
+experimenting with.
+
+You might find it handy to use for your own projects but please be aware that
+we will make no promises your application won't break in the future. We will
+provide no support if you decide to link against it.
new file mode 100644
--- /dev/null
+++ b/cpputil/cpputil.gyp
@@ -0,0 +1,27 @@
+# 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/.
+{
+  'includes': [
+    '../coreconf/config.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'cpputil',
+      'type': 'static_library',
+      'sources': [
+        'dummy_io.cc',
+        'dummy_io_fwd.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/exports.gyp:nss_exports',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(DEPTH)/cpputil',
+        ],
+      },
+    },
+  ],
+}
+
new file mode 100644
--- /dev/null
+++ b/cpputil/dummy_io.cc
@@ -0,0 +1,221 @@
+/* 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 <iostream>
+
+#include "prerror.h"
+#include "prio.h"
+
+#include "dummy_io.h"
+
+#define UNIMPLEMENTED()                                        \
+  std::cerr << "Unimplemented: " << __FUNCTION__ << std::endl; \
+  assert(false);
+
+extern const struct PRIOMethods DummyMethodsForward;
+
+ScopedPRFileDesc DummyIOLayerMethods::CreateFD(PRDescIdentity id,
+                                               DummyIOLayerMethods *methods) {
+  ScopedPRFileDesc fd(PR_CreateIOLayerStub(id, &DummyMethodsForward));
+  fd->secret = reinterpret_cast<PRFilePrivate *>(methods);
+  return fd;
+}
+
+PRStatus DummyIOLayerMethods::Close(PRFileDesc *f) {
+  f->secret = nullptr;
+  f->dtor(f);
+  return PR_SUCCESS;
+}
+
+int32_t DummyIOLayerMethods::Read(PRFileDesc *f, void *buf, int32_t length) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int32_t DummyIOLayerMethods::Write(PRFileDesc *f, const void *buf,
+                                   int32_t length) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int32_t DummyIOLayerMethods::Available(PRFileDesc *f) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int64_t DummyIOLayerMethods::Available64(PRFileDesc *f) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+PRStatus DummyIOLayerMethods::Sync(PRFileDesc *f) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+int32_t DummyIOLayerMethods::Seek(PRFileDesc *f, int32_t offset,
+                                  PRSeekWhence how) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int64_t DummyIOLayerMethods::Seek64(PRFileDesc *f, int64_t offset,
+                                    PRSeekWhence how) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+PRStatus DummyIOLayerMethods::FileInfo(PRFileDesc *f, PRFileInfo *info) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+PRStatus DummyIOLayerMethods::FileInfo64(PRFileDesc *f, PRFileInfo64 *info) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+int32_t DummyIOLayerMethods::Writev(PRFileDesc *f, const PRIOVec *iov,
+                                    int32_t iov_size, PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+PRStatus DummyIOLayerMethods::Connect(PRFileDesc *f, const PRNetAddr *addr,
+                                      PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+PRFileDesc *DummyIOLayerMethods::Accept(PRFileDesc *sd, PRNetAddr *addr,
+                                        PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return nullptr;
+}
+
+PRStatus DummyIOLayerMethods::Bind(PRFileDesc *f, const PRNetAddr *addr) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+PRStatus DummyIOLayerMethods::Listen(PRFileDesc *f, int32_t depth) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+PRStatus DummyIOLayerMethods::Shutdown(PRFileDesc *f, int32_t how) {
+  return PR_SUCCESS;
+}
+
+int32_t DummyIOLayerMethods::Recv(PRFileDesc *f, void *buf, int32_t buflen,
+                                  int32_t flags, PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+// Note: this is always nonblocking and assumes a zero timeout.
+int32_t DummyIOLayerMethods::Send(PRFileDesc *f, const void *buf,
+                                  int32_t amount, int32_t flags,
+                                  PRIntervalTime to) {
+  return Write(f, buf, amount);
+}
+
+int32_t DummyIOLayerMethods::Recvfrom(PRFileDesc *f, void *buf, int32_t amount,
+                                      int32_t flags, PRNetAddr *addr,
+                                      PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int32_t DummyIOLayerMethods::Sendto(PRFileDesc *f, const void *buf,
+                                    int32_t amount, int32_t flags,
+                                    const PRNetAddr *addr, PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int16_t DummyIOLayerMethods::Poll(PRFileDesc *f, int16_t in_flags,
+                                  int16_t *out_flags) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int32_t DummyIOLayerMethods::AcceptRead(PRFileDesc *sd, PRFileDesc **nd,
+                                        PRNetAddr **raddr, void *buf,
+                                        int32_t amount, PRIntervalTime t) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+int32_t DummyIOLayerMethods::TransmitFile(PRFileDesc *sd, PRFileDesc *f,
+                                          const void *headers, int32_t hlen,
+                                          PRTransmitFileFlags flags,
+                                          PRIntervalTime t) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+// TODO: Modify to return unique names for each channel
+// somehow, as opposed to always the same static address. The current
+// implementation messes up the session cache, which is why it's off
+// elsewhere
+PRStatus DummyIOLayerMethods::Getpeername(PRFileDesc *f, PRNetAddr *addr) {
+  addr->inet.family = PR_AF_INET;
+  addr->inet.port = 0;
+  addr->inet.ip = 0;
+
+  return PR_SUCCESS;
+}
+
+PRStatus DummyIOLayerMethods::Getsockname(PRFileDesc *f, PRNetAddr *addr) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+PRStatus DummyIOLayerMethods::Getsockoption(PRFileDesc *f,
+                                            PRSocketOptionData *opt) {
+  switch (opt->option) {
+    case PR_SockOpt_Nonblocking:
+      opt->value.non_blocking = PR_TRUE;
+      return PR_SUCCESS;
+    default:
+      UNIMPLEMENTED();
+      break;
+  }
+
+  return PR_FAILURE;
+}
+
+PRStatus DummyIOLayerMethods::Setsockoption(PRFileDesc *f,
+                                            const PRSocketOptionData *opt) {
+  switch (opt->option) {
+    case PR_SockOpt_Nonblocking:
+      return PR_SUCCESS;
+    case PR_SockOpt_NoDelay:
+      return PR_SUCCESS;
+    default:
+      UNIMPLEMENTED();
+      break;
+  }
+
+  return PR_FAILURE;
+}
+
+int32_t DummyIOLayerMethods::Sendfile(PRFileDesc *out, PRSendFileData *in,
+                                      PRTransmitFileFlags flags,
+                                      PRIntervalTime to) {
+  UNIMPLEMENTED();
+  return -1;
+}
+
+PRStatus DummyIOLayerMethods::ConnectContinue(PRFileDesc *f, int16_t flags) {
+  UNIMPLEMENTED();
+  return PR_FAILURE;
+}
+
+int32_t DummyIOLayerMethods::Reserved(PRFileDesc *f) {
+  UNIMPLEMENTED();
+  return -1;
+}
new file mode 100644
--- /dev/null
+++ b/cpputil/dummy_io.h
@@ -0,0 +1,62 @@
+/* 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 dummy_io_h__
+#define dummy_io_h__
+
+#include "prerror.h"
+#include "prio.h"
+
+#include "scoped_ptrs.h"
+
+class DummyIOLayerMethods {
+ public:
+  static ScopedPRFileDesc CreateFD(PRDescIdentity id,
+                                   DummyIOLayerMethods *methods);
+
+  virtual PRStatus Close(PRFileDesc *f);
+  virtual int32_t Read(PRFileDesc *f, void *buf, int32_t length);
+  virtual int32_t Write(PRFileDesc *f, const void *buf, int32_t length);
+  virtual int32_t Available(PRFileDesc *f);
+  virtual int64_t Available64(PRFileDesc *f);
+  virtual PRStatus Sync(PRFileDesc *f);
+  virtual int32_t Seek(PRFileDesc *f, int32_t offset, PRSeekWhence how);
+  virtual int64_t Seek64(PRFileDesc *f, int64_t offset, PRSeekWhence how);
+  virtual PRStatus FileInfo(PRFileDesc *f, PRFileInfo *info);
+  virtual PRStatus FileInfo64(PRFileDesc *f, PRFileInfo64 *info);
+  virtual int32_t Writev(PRFileDesc *f, const PRIOVec *iov, int32_t iov_size,
+                         PRIntervalTime to);
+  virtual PRStatus Connect(PRFileDesc *f, const PRNetAddr *addr,
+                           PRIntervalTime to);
+  virtual PRFileDesc *Accept(PRFileDesc *sd, PRNetAddr *addr,
+                             PRIntervalTime to);
+  virtual PRStatus Bind(PRFileDesc *f, const PRNetAddr *addr);
+  virtual PRStatus Listen(PRFileDesc *f, int32_t depth);
+  virtual PRStatus Shutdown(PRFileDesc *f, int32_t how);
+  virtual int32_t Recv(PRFileDesc *f, void *buf, int32_t buflen, int32_t flags,
+                       PRIntervalTime to);
+  virtual int32_t Send(PRFileDesc *f, const void *buf, int32_t amount,
+                       int32_t flags, PRIntervalTime to);
+  virtual int32_t Recvfrom(PRFileDesc *f, void *buf, int32_t amount,
+                           int32_t flags, PRNetAddr *addr, PRIntervalTime to);
+  virtual int32_t Sendto(PRFileDesc *f, const void *buf, int32_t amount,
+                         int32_t flags, const PRNetAddr *addr,
+                         PRIntervalTime to);
+  virtual int16_t Poll(PRFileDesc *f, int16_t in_flags, int16_t *out_flags);
+  virtual int32_t AcceptRead(PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr,
+                             void *buf, int32_t amount, PRIntervalTime t);
+  virtual int32_t TransmitFile(PRFileDesc *sd, PRFileDesc *f,
+                               const void *headers, int32_t hlen,
+                               PRTransmitFileFlags flags, PRIntervalTime t);
+  virtual PRStatus Getpeername(PRFileDesc *f, PRNetAddr *addr);
+  virtual PRStatus Getsockname(PRFileDesc *f, PRNetAddr *addr);
+  virtual PRStatus Getsockoption(PRFileDesc *f, PRSocketOptionData *opt);
+  virtual PRStatus Setsockoption(PRFileDesc *f, const PRSocketOptionData *opt);
+  virtual int32_t Sendfile(PRFileDesc *out, PRSendFileData *in,
+                           PRTransmitFileFlags flags, PRIntervalTime to);
+  virtual PRStatus ConnectContinue(PRFileDesc *f, int16_t flags);
+  virtual int32_t Reserved(PRFileDesc *f);
+};
+
+#endif  // dummy_io_h__
new file mode 100644
--- /dev/null
+++ b/cpputil/dummy_io_fwd.cc
@@ -0,0 +1,162 @@
+/* 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 "prio.h"
+
+#include "dummy_io.h"
+
+static DummyIOLayerMethods *ToMethods(PRFileDesc *f) {
+  return reinterpret_cast<DummyIOLayerMethods *>(f->secret);
+}
+
+static PRStatus DummyClose(PRFileDesc *f) { return ToMethods(f)->Close(f); }
+
+static int32_t DummyRead(PRFileDesc *f, void *buf, int32_t length) {
+  return ToMethods(f)->Read(f, buf, length);
+}
+
+static int32_t DummyWrite(PRFileDesc *f, const void *buf, int32_t length) {
+  return ToMethods(f)->Write(f, buf, length);
+}
+
+static int32_t DummyAvailable(PRFileDesc *f) {
+  return ToMethods(f)->Available(f);
+}
+
+static int64_t DummyAvailable64(PRFileDesc *f) {
+  return ToMethods(f)->Available64(f);
+}
+
+static PRStatus DummySync(PRFileDesc *f) { return ToMethods(f)->Sync(f); }
+
+static int32_t DummySeek(PRFileDesc *f, int32_t offset, PRSeekWhence how) {
+  return ToMethods(f)->Seek(f, offset, how);
+}
+
+static int64_t DummySeek64(PRFileDesc *f, int64_t offset, PRSeekWhence how) {
+  return ToMethods(f)->Seek64(f, offset, how);
+}
+
+static PRStatus DummyFileInfo(PRFileDesc *f, PRFileInfo *info) {
+  return ToMethods(f)->FileInfo(f, info);
+}
+
+static PRStatus DummyFileInfo64(PRFileDesc *f, PRFileInfo64 *info) {
+  return ToMethods(f)->FileInfo64(f, info);
+}
+
+static int32_t DummyWritev(PRFileDesc *f, const PRIOVec *iov, int32_t iov_size,
+                           PRIntervalTime to) {
+  return ToMethods(f)->Writev(f, iov, iov_size, to);
+}
+
+static PRStatus DummyConnect(PRFileDesc *f, const PRNetAddr *addr,
+                             PRIntervalTime to) {
+  return ToMethods(f)->Connect(f, addr, to);
+}
+
+static PRFileDesc *DummyAccept(PRFileDesc *f, PRNetAddr *addr,
+                               PRIntervalTime to) {
+  return ToMethods(f)->Accept(f, addr, to);
+}
+
+static PRStatus DummyBind(PRFileDesc *f, const PRNetAddr *addr) {
+  return ToMethods(f)->Bind(f, addr);
+}
+
+static PRStatus DummyListen(PRFileDesc *f, int32_t depth) {
+  return ToMethods(f)->Listen(f, depth);
+}
+
+static PRStatus DummyShutdown(PRFileDesc *f, int32_t how) {
+  return ToMethods(f)->Shutdown(f, how);
+}
+
+static int32_t DummyRecv(PRFileDesc *f, void *buf, int32_t buflen,
+                         int32_t flags, PRIntervalTime to) {
+  return ToMethods(f)->Recv(f, buf, buflen, flags, to);
+}
+
+static int32_t DummySend(PRFileDesc *f, const void *buf, int32_t amount,
+                         int32_t flags, PRIntervalTime to) {
+  return ToMethods(f)->Send(f, buf, amount, flags, to);
+}
+
+static int32_t DummyRecvfrom(PRFileDesc *f, void *buf, int32_t amount,
+                             int32_t flags, PRNetAddr *addr,
+                             PRIntervalTime to) {
+  return ToMethods(f)->Recvfrom(f, buf, amount, flags, addr, to);
+}
+
+static int32_t DummySendto(PRFileDesc *f, const void *buf, int32_t amount,
+                           int32_t flags, const PRNetAddr *addr,
+                           PRIntervalTime to) {
+  return ToMethods(f)->Sendto(f, buf, amount, flags, addr, to);
+}
+
+static int16_t DummyPoll(PRFileDesc *f, int16_t in_flags, int16_t *out_flags) {
+  return ToMethods(f)->Poll(f, in_flags, out_flags);
+}
+
+static int32_t DummyAcceptRead(PRFileDesc *f, PRFileDesc **nd,
+                               PRNetAddr **raddr, void *buf, int32_t amount,
+                               PRIntervalTime t) {
+  return ToMethods(f)->AcceptRead(f, nd, raddr, buf, amount, t);
+}
+
+static int32_t DummyTransmitFile(PRFileDesc *sd, PRFileDesc *f,
+                                 const void *headers, int32_t hlen,
+                                 PRTransmitFileFlags flags, PRIntervalTime t) {
+  return ToMethods(f)->TransmitFile(sd, f, headers, hlen, flags, t);
+}
+
+static PRStatus DummyGetpeername(PRFileDesc *f, PRNetAddr *addr) {
+  return ToMethods(f)->Getpeername(f, addr);
+}
+
+static PRStatus DummyGetsockname(PRFileDesc *f, PRNetAddr *addr) {
+  return ToMethods(f)->Getsockname(f, addr);
+}
+
+static PRStatus DummyGetsockoption(PRFileDesc *f, PRSocketOptionData *opt) {
+  return ToMethods(f)->Getsockoption(f, opt);
+}
+
+static PRStatus DummySetsockoption(PRFileDesc *f,
+                                   const PRSocketOptionData *opt) {
+  return ToMethods(f)->Setsockoption(f, opt);
+}
+
+static int32_t DummySendfile(PRFileDesc *f, PRSendFileData *in,
+                             PRTransmitFileFlags flags, PRIntervalTime to) {
+  return ToMethods(f)->Sendfile(f, in, flags, to);
+}
+
+static PRStatus DummyConnectContinue(PRFileDesc *f, int16_t flags) {
+  return ToMethods(f)->ConnectContinue(f, flags);
+}
+
+static int32_t DummyReserved(PRFileDesc *f) {
+  return ToMethods(f)->Reserved(f);
+}
+
+extern const struct PRIOMethods DummyMethodsForward = {
+    PR_DESC_LAYERED,    DummyClose,
+    DummyRead,          DummyWrite,
+    DummyAvailable,     DummyAvailable64,
+    DummySync,          DummySeek,
+    DummySeek64,        DummyFileInfo,
+    DummyFileInfo64,    DummyWritev,
+    DummyConnect,       DummyAccept,
+    DummyBind,          DummyListen,
+    DummyShutdown,      DummyRecv,
+    DummySend,          DummyRecvfrom,
+    DummySendto,        DummyPoll,
+    DummyAcceptRead,    DummyTransmitFile,
+    DummyGetsockname,   DummyGetpeername,
+    DummyReserved,      DummyReserved,
+    DummyGetsockoption, DummySetsockoption,
+    DummySendfile,      DummyConnectContinue,
+    DummyReserved,      DummyReserved,
+    DummyReserved,      DummyReserved};
rename from gtests/common/scoped_ptrs.h
rename to cpputil/scoped_ptrs.h
--- a/gtests/common/scoped_ptrs.h
+++ b/cpputil/scoped_ptrs.h
@@ -7,18 +7,16 @@
 #ifndef scoped_ptrs_h__
 #define scoped_ptrs_h__
 
 #include <memory>
 #include "cert.h"
 #include "keyhi.h"
 #include "pk11pub.h"
 
-namespace nss_test {
-
 struct ScopedDelete {
   void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); }
   void operator()(CERTCertificateList* list) {
     CERT_DestroyCertificateList(list);
   }
   void operator()(CERTCertList* list) { CERT_DestroyCertList(list); }
   void operator()(CERTSubjectPublicKeyInfo* spki) {
     SECKEY_DestroySubjectPublicKeyInfo(spki);
@@ -53,11 +51,9 @@ SCOPED(PK11SymKey);
 SCOPED(PRFileDesc);
 SCOPED(SECAlgorithmID);
 SCOPED(SECItem);
 SCOPED(SECKEYPublicKey);
 SCOPED(SECKEYPrivateKey);
 
 #undef SCOPED
 
-}  // namespace nss_test
-
-#endif
+#endif  // scoped_ptrs_h__
--- a/fuzz/fuzz.gyp
+++ b/fuzz/fuzz.gyp
@@ -24,16 +24,17 @@
   },
   'targets': [
     {
       'target_name': 'fuzz_base',
       'dependencies': [
         '<(DEPTH)/lib/certdb/certdb.gyp:certdb',
         '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi',
         '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi',
+        '<(DEPTH)/lib/ssl/ssl.gyp:ssl',
         '<(DEPTH)/lib/base/base.gyp:nssb',
         '<(DEPTH)/lib/dev/dev.gyp:nssdev',
         '<(DEPTH)/lib/pki/pki.gyp:nsspki',
         '<(DEPTH)/lib/util/util.gyp:nssutil',
         '<(DEPTH)/lib/nss/nss.gyp:nss_static',
         '<(DEPTH)/lib/pkcs7/pkcs7.gyp:pkcs7',
         # This is a static build of pk11wrap, softoken, and freebl.
         '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
@@ -239,23 +240,40 @@
         'mpi_expmod_target.cc',
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         'nssfuzz-mpi-base',
       ],
     },
     {
+      'target_name': 'nssfuzz-tls-client',
+      'type': 'executable',
+      'sources': [
+        'tls_client_socket.cc',
+        'tls_client_target.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cpputil/cpputil.gyp:cpputil',
+        '<(DEPTH)/exports.gyp:nss_exports',
+        'fuzz_base',
+      ],
+      'include_dirs': [
+        '<(DEPTH)/lib/freebl',
+      ],
+    },
+    {
       'target_name': 'nssfuzz',
       'type': 'none',
       'dependencies': [
         'nssfuzz-certDN',
         'nssfuzz-hash',
         'nssfuzz-pkcs8',
         'nssfuzz-quickder',
+        'nssfuzz-tls-client',
       ],
       'conditions': [
         ['OS=="linux"', {
           'dependencies': [
             'nssfuzz-mpi-add',
             'nssfuzz-mpi-addmod',
             'nssfuzz-mpi-div',
             'nssfuzz-mpi-expmod',
new file mode 100644
--- /dev/null
+++ b/fuzz/tls-client.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 20000
+
new file mode 100644
--- /dev/null
+++ b/fuzz/tls_client_socket.cc
@@ -0,0 +1,34 @@
+/* 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 <string.h>
+#include <algorithm>
+
+#include "prerror.h"
+#include "prio.h"
+
+#include "tls_client_socket.h"
+
+int32_t DummyPrSocket::Read(PRFileDesc *f, void *data, int32_t len) {
+  assert(data && len > 0);
+
+  int32_t amount = std::min(len, static_cast<int32_t>(len_));
+  memcpy(data, buf_, amount);
+
+  buf_ += amount;
+  len_ -= amount;
+
+  return amount;
+}
+
+int32_t DummyPrSocket::Write(PRFileDesc *f, const void *buf, int32_t length) {
+  return length;
+}
+
+int32_t DummyPrSocket::Recv(PRFileDesc *f, void *buf, int32_t buflen,
+                            int32_t flags, PRIntervalTime to) {
+  assert(flags == 0);
+  return Read(f, buf, buflen);
+}
new file mode 100644
--- /dev/null
+++ b/fuzz/tls_client_socket.h
@@ -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/. */
+
+#ifndef tls_client_socket_h__
+#define tls_client_socket_h__
+
+#include "dummy_io.h"
+
+class DummyPrSocket : public DummyIOLayerMethods {
+ public:
+  DummyPrSocket(const uint8_t *buf, size_t len) : buf_(buf), len_(len) {}
+
+  int32_t Read(PRFileDesc *f, void *data, int32_t len) override;
+  int32_t Write(PRFileDesc *f, const void *buf, int32_t length) override;
+  int32_t Recv(PRFileDesc *f, void *buf, int32_t buflen, int32_t flags,
+               PRIntervalTime to) override;
+
+ private:
+  const uint8_t *buf_;
+  size_t len_;
+};
+
+#endif  // tls_client_socket_h__
new file mode 100644
--- /dev/null
+++ b/fuzz/tls_client_target.cc
@@ -0,0 +1,113 @@
+/* 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 "blapi.h"
+#include "prinit.h"
+#include "ssl.h"
+
+#include "shared.h"
+#include "tls_client_socket.h"
+
+static PRStatus EnableAllProtocolVersions() {
+  SSLVersionRange supported;
+
+  SECStatus rv = SSL_VersionRangeGetSupported(ssl_variant_stream, &supported);
+  assert(rv == SECSuccess);
+
+  rv = SSL_VersionRangeSetDefault(ssl_variant_stream, &supported);
+  assert(rv == SECSuccess);
+
+  return PR_SUCCESS;
+}
+
+static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig,
+                                     PRBool isServer) {
+  return SECSuccess;
+}
+
+static void SetSocketOptions(PRFileDesc* fd) {
+  // Disable session cache for now.
+  SECStatus rv = SSL_OptionSet(fd, SSL_NO_CACHE, true);
+  assert(rv == SECSuccess);
+
+  rv = SSL_OptionSet(fd, SSL_ENABLE_EXTENDED_MASTER_SECRET, true);
+  assert(rv == SECSuccess);
+
+  rv = SSL_OptionSet(fd, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, true);
+  assert(rv == SECSuccess);
+
+  rv = SSL_OptionSet(fd, SSL_ENABLE_FALLBACK_SCSV, true);
+  assert(rv == SECSuccess);
+
+  rv = SSL_OptionSet(fd, SSL_ENABLE_ALPN, true);
+  assert(rv == SECSuccess);
+
+  rv =
+      SSL_OptionSet(fd, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_UNRESTRICTED);
+  assert(rv == SECSuccess);
+}
+
+static void EnableAllCipherSuites(PRFileDesc* fd) {
+  for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
+    SECStatus rv = SSL_CipherPrefSet(fd, SSL_ImplementedCiphers[i], true);
+    assert(rv == SECSuccess);
+  }
+}
+
+static void SetupAuthCertificateHook(PRFileDesc* fd) {
+  SECStatus rv = SSL_AuthCertificateHook(fd, AuthCertificateHook, nullptr);
+  assert(rv == SECSuccess);
+}
+
+static void DoHandshake(PRFileDesc* fd) {
+  SECStatus rv = SSL_ResetHandshake(fd, false /* asServer */);
+  assert(rv == SECSuccess);
+
+  do {
+    rv = SSL_ForceHandshake(fd);
+  } while (rv != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+  // If the handshake succeeds, let's read some data from the server, if any.
+  if (rv == SECSuccess) {
+    uint8_t block[1024];
+    int32_t nb;
+
+    // Read application data and echo it back.
+    while ((nb = PR_Read(fd, block, sizeof(block))) > 0) {
+      PR_Write(fd, block, nb);
+    }
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) {
+  static std::unique_ptr<NSSDatabase> db(new NSSDatabase());
+  assert(db != nullptr);
+
+  EnableAllProtocolVersions();
+
+  // Reset the RNG state.
+  SECStatus rv = RNG_ResetForFuzzing();
+  assert(rv == SECSuccess);
+
+  // Create and import dummy socket.
+  std::unique_ptr<DummyPrSocket> socket(new DummyPrSocket(data, len));
+  static PRDescIdentity id = PR_GetUniqueIdentity("fuzz-client");
+  ScopedPRFileDesc fd(DummyIOLayerMethods::CreateFD(id, socket.get()));
+  PRFileDesc* ssl_fd = SSL_ImportFD(nullptr, fd.get());
+  assert(ssl_fd == fd.get());
+
+  // Probably not too important for clients.
+  SSL_SetURL(ssl_fd, "server");
+
+  SetSocketOptions(ssl_fd);
+  EnableAllCipherSuites(ssl_fd);
+  SetupAuthCertificateHook(ssl_fd);
+  DoHandshake(ssl_fd);
+
+  return 0;
+}
--- a/gtests/common/gtest.gypi
+++ b/gtests/common/gtest.gypi
@@ -1,13 +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/.
 {
   'target_defaults': {
+    'include_dirs': [
+      '<(DEPTH)/gtests/google_test/gtest/include',
+      '<(DEPTH)/gtests/common',
+      '<(DEPTH)/cpputil',
+    ],
     'cflags': [
       '-Wsign-compare',
     ],
     'xcode_settings': {
       'OTHER_CFLAGS': [
         '-Wsign-compare',
       ],
     },
--- a/gtests/common/manifest.mn
+++ b/gtests/common/manifest.mn
@@ -6,16 +6,17 @@ CORE_DEPTH = ../..
 DEPTH      = ../..
 MODULE = nss
 
 CPPSRCS = \
       gtests.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
-            -I$(CORE_DEPTH)/gtests/common
+            -I$(CORE_DEPTH)/gtests/common \
+            -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = gtest
 
 EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX)
 
 # NOTE: this is not actually used but required to build gtests.o
 PROGRAM = gtests
--- a/gtests/der_gtest/der_gtest.gyp
+++ b/gtests/der_gtest/der_gtest.gyp
@@ -19,18 +19,12 @@
         '<(DEPTH)/exports.gyp:nss_exports',
         '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
         '<(DEPTH)/lib/util/util.gyp:nssutil3',
         '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
         '<(DEPTH)/lib/nss/nss.gyp:nss3',
       ]
     }
   ],
-  'target_defaults': {
-    'include_dirs': [
-      '../../gtests/google_test/gtest/include',
-      '../../gtests/common'
-    ]
-  },
   'variables': {
     'module': 'nss'
   }
 }
--- a/gtests/der_gtest/manifest.mn
+++ b/gtests/der_gtest/manifest.mn
@@ -7,16 +7,17 @@ DEPTH      = ../..
 MODULE = nss
 
 CPPSRCS = \
       der_getint_unittest.cc \
       der_private_key_import_unittest.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
-            -I$(CORE_DEPTH)/gtests/common
+            -I$(CORE_DEPTH)/gtests/common \
+            -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = nspr nss libdbm gtest
 
 PROGRAM = der_gtest
 
 EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) $(EXTRA_OBJS) \
              ../common/$(OBJDIR)/gtests$(OBJ_SUFFIX)
--- a/gtests/freebl_gtest/freebl_gtest.gyp
+++ b/gtests/freebl_gtest/freebl_gtest.gyp
@@ -34,17 +34,15 @@
             'CT_VERIF',
           ],
         }],
       ],
     }
   ],
   'target_defaults': {
     'include_dirs': [
-      '<(DEPTH)/gtests/google_test/gtest/include',
-      '<(DEPTH)/gtests/common',
       '<(DEPTH)/lib/freebl/mpi',
     ]
   },
   'variables': {
     'module': 'nss'
   }
 }
--- a/gtests/google_test/google_test.gyp
+++ b/gtests/google_test/google_test.gyp
@@ -12,16 +12,15 @@
       'type': 'static_library',
       'sources': [
         'gtest/src/gtest-all.cc'
       ],
     },
   ],
   'target_defaults': {
     'include_dirs': [
-      'gtest/include/',
       'gtest'
     ],
   },
   'variables': {
     'module': 'gtest'
   }
 }
--- a/gtests/pk11_gtest/manifest.mn
+++ b/gtests/pk11_gtest/manifest.mn
@@ -13,17 +13,18 @@ CPPSRCS = \
       pk11_export_unittest.cc \
       pk11_pbkdf2_unittest.cc \
       pk11_prf_unittest.cc \
       pk11_prng_unittest.cc \
       pk11_rsapss_unittest.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
-            -I$(CORE_DEPTH)/gtests/common
+            -I$(CORE_DEPTH)/gtests/common \
+            -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = nspr nss libdbm gtest
 
 PROGRAM = pk11_gtest
 
 EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) $(EXTRA_OBJS) \
              ../common/$(OBJDIR)/gtests$(OBJ_SUFFIX)
 
--- a/gtests/pk11_gtest/pk11_gtest.gyp
+++ b/gtests/pk11_gtest/pk11_gtest.gyp
@@ -42,18 +42,12 @@
           'dependencies': [
             '<(DEPTH)/lib/nss/nss.gyp:nss3',
             '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
           ],
         }],
       ],
     }
   ],
-  'target_defaults': {
-    'include_dirs': [
-      '../../gtests/google_test/gtest/include',
-      '../../gtests/common'
-    ]
-  },
   'variables': {
     'module': 'nss'
   }
 }
--- a/gtests/ssl_gtest/manifest.mn
+++ b/gtests/ssl_gtest/manifest.mn
@@ -7,16 +7,18 @@ DEPTH      = ../..
 MODULE = nss
 
 # These sources have access to libssl internals
 CSRCS = \
       libssl_internals.c \
       $(NULL)
 
 CPPSRCS = \
+      $(CORE_DEPTH)/cpputil/dummy_io.cc \
+      $(CORE_DEPTH)/cpputil/dummy_io_fwd.cc \
       ssl_0rtt_unittest.cc \
       ssl_agent_unittest.cc \
       ssl_auth_unittest.cc \
       ssl_cert_ext_unittest.cc \
       ssl_ciphersuite_unittest.cc \
       ssl_damage_unittest.cc \
       ssl_dhe_unittest.cc \
       ssl_drop_unittest.cc \
@@ -41,16 +43,17 @@ CPPSRCS = \
       tls_connect.cc \
       tls_hkdf_unittest.cc \
       tls_filter.cc \
       tls_parser.cc \
       tls_protect.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
-            -I$(CORE_DEPTH)/gtests/common
+            -I$(CORE_DEPTH)/gtests/common \
+            -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = nspr nss libdbm gtest
 
 PROGRAM = ssl_gtest
 EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX)
 
 USE_STATIC_LIBS = 1
--- a/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/gtests/ssl_gtest/ssl_gtest.gyp
@@ -54,17 +54,18 @@
         '<(DEPTH)/lib/pkcs12/pkcs12.gyp:pkcs12',
         '<(DEPTH)/lib/pkcs7/pkcs7.gyp:pkcs7',
         '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi',
         '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi',
         '<(DEPTH)/lib/certdb/certdb.gyp:certdb',
         '<(DEPTH)/lib/pki/pki.gyp:nsspki',
         '<(DEPTH)/lib/dev/dev.gyp:nssdev',
         '<(DEPTH)/lib/base/base.gyp:nssb',
-        '<(DEPTH)/lib/zlib/zlib.gyp:nss_zlib'
+        '<(DEPTH)/lib/zlib/zlib.gyp:nss_zlib',
+        '<(DEPTH)/cpputil/cpputil.gyp:cpputil',
       ],
       'conditions': [
         [ 'test_build==1', {
           'dependencies': [
             '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
           ],
         }, {
           'dependencies': [
@@ -94,18 +95,16 @@
             '<(DEPTH)/lib/libpkix/pkix_pl_nss/pki/pki.gyp:pkixpki',
           ],
         }],
       ],
     }
   ],
   'target_defaults': {
     'include_dirs': [
-      '../../gtests/google_test/gtest/include',
-      '../../gtests/common',
       '../../lib/ssl'
     ],
     'defines': [
       'NSS_USE_STATIC_LIBS'
     ],
   },
   'variables': {
     'module': 'nss',
--- a/gtests/ssl_gtest/test_io.cc
+++ b/gtests/ssl_gtest/test_io.cc
@@ -14,270 +14,37 @@
 #include "prerror.h"
 #include "prlog.h"
 #include "prthread.h"
 
 extern bool g_ssl_gtest_verbose;
 
 namespace nss_test {
 
-static PRDescIdentity test_fd_identity = PR_INVALID_IO_LAYER;
-
-#define UNIMPLEMENTED()                                                        \
-  std::cerr << "Call to unimplemented function " << __FUNCTION__ << std::endl; \
-  PR_ASSERT(PR_FALSE);                                                         \
-  PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0)
-
 #define LOG(a) std::cerr << name_ << ": " << a << std::endl
 #define LOGV(a)                      \
   do {                               \
     if (g_ssl_gtest_verbose) LOG(a); \
   } while (false)
 
-// Implementation of NSPR methods
-static PRStatus DummyClose(PRFileDesc *f) {
-  f->secret = nullptr;
-  f->dtor(f);
-  return PR_SUCCESS;
-}
-
-static int32_t DummyRead(PRFileDesc *f, void *buf, int32_t length) {
-  DummyPrSocket *io = reinterpret_cast<DummyPrSocket *>(f->secret);
-  return io->Read(buf, length);
-}
-
-static int32_t DummyWrite(PRFileDesc *f, const void *buf, int32_t length) {
-  DummyPrSocket *io = reinterpret_cast<DummyPrSocket *>(f->secret);
-  return io->Write(buf, length);
-}
-
-static int32_t DummyAvailable(PRFileDesc *f) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int64_t DummyAvailable64(PRFileDesc *f) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static PRStatus DummySync(PRFileDesc *f) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static int32_t DummySeek(PRFileDesc *f, int32_t offset, PRSeekWhence how) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int64_t DummySeek64(PRFileDesc *f, int64_t offset, PRSeekWhence how) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static PRStatus DummyFileInfo(PRFileDesc *f, PRFileInfo *info) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static PRStatus DummyFileInfo64(PRFileDesc *f, PRFileInfo64 *info) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static int32_t DummyWritev(PRFileDesc *f, const PRIOVec *iov, int32_t iov_size,
-                           PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static PRStatus DummyConnect(PRFileDesc *f, const PRNetAddr *addr,
-                             PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static PRFileDesc *DummyAccept(PRFileDesc *sd, PRNetAddr *addr,
-                               PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return nullptr;
-}
-
-static PRStatus DummyBind(PRFileDesc *f, const PRNetAddr *addr) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static PRStatus DummyListen(PRFileDesc *f, int32_t depth) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static PRStatus DummyShutdown(PRFileDesc *f, int32_t how) { return PR_SUCCESS; }
-
-// This function does not support peek.
-static int32_t DummyRecv(PRFileDesc *f, void *buf, int32_t buflen,
-                         int32_t flags, PRIntervalTime to) {
-  PR_ASSERT(flags == 0);
-  if (flags != 0) {
-    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
-    return -1;
-  }
-
-  DummyPrSocket *io = reinterpret_cast<DummyPrSocket *>(f->secret);
-
-  if (io->mode() == DGRAM) {
-    return io->Recv(buf, buflen);
-  } else {
-    return io->Read(buf, buflen);
-  }
-}
-
-// Note: this is always nonblocking and assumes a zero timeout.
-static int32_t DummySend(PRFileDesc *f, const void *buf, int32_t amount,
-                         int32_t flags, PRIntervalTime to) {
-  int32_t written = DummyWrite(f, buf, amount);
-  return written;
-}
-
-static int32_t DummyRecvfrom(PRFileDesc *f, void *buf, int32_t amount,
-                             int32_t flags, PRNetAddr *addr,
-                             PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int32_t DummySendto(PRFileDesc *f, const void *buf, int32_t amount,
-                           int32_t flags, const PRNetAddr *addr,
-                           PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int16_t DummyPoll(PRFileDesc *f, int16_t in_flags, int16_t *out_flags) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int32_t DummyAcceptRead(PRFileDesc *sd, PRFileDesc **nd,
-                               PRNetAddr **raddr, void *buf, int32_t amount,
-                               PRIntervalTime t) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static int32_t DummyTransmitFile(PRFileDesc *sd, PRFileDesc *f,
-                                 const void *headers, int32_t hlen,
-                                 PRTransmitFileFlags flags, PRIntervalTime t) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static PRStatus DummyGetpeername(PRFileDesc *f, PRNetAddr *addr) {
-  // TODO: Modify to return unique names for each channel
-  // somehow, as opposed to always the same static address. The current
-  // implementation messes up the session cache, which is why it's off
-  // elsewhere
-  addr->inet.family = PR_AF_INET;
-  addr->inet.port = 0;
-  addr->inet.ip = 0;
-
-  return PR_SUCCESS;
-}
-
-static PRStatus DummyGetsockname(PRFileDesc *f, PRNetAddr *addr) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static PRStatus DummyGetsockoption(PRFileDesc *f, PRSocketOptionData *opt) {
-  switch (opt->option) {
-    case PR_SockOpt_Nonblocking:
-      opt->value.non_blocking = PR_TRUE;
-      return PR_SUCCESS;
-    default:
-      UNIMPLEMENTED();
-      break;
-  }
-
-  return PR_FAILURE;
-}
-
-// Imitate setting socket options. These are mostly noops.
-static PRStatus DummySetsockoption(PRFileDesc *f,
-                                   const PRSocketOptionData *opt) {
-  switch (opt->option) {
-    case PR_SockOpt_Nonblocking:
-      return PR_SUCCESS;
-    case PR_SockOpt_NoDelay:
-      return PR_SUCCESS;
-    default:
-      UNIMPLEMENTED();
-      break;
-  }
-
-  return PR_FAILURE;
-}
-
-static int32_t DummySendfile(PRFileDesc *out, PRSendFileData *in,
-                             PRTransmitFileFlags flags, PRIntervalTime to) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
-static PRStatus DummyConnectContinue(PRFileDesc *f, int16_t flags) {
-  UNIMPLEMENTED();
-  return PR_FAILURE;
-}
-
-static int32_t DummyReserved(PRFileDesc *f) {
-  UNIMPLEMENTED();
-  return -1;
-}
-
 void DummyPrSocket::SetPacketFilter(std::shared_ptr<PacketFilter> filter) {
   filter_ = filter;
 }
 
-static const struct PRIOMethods DummyMethods = {
-    PR_DESC_LAYERED,    DummyClose,
-    DummyRead,          DummyWrite,
-    DummyAvailable,     DummyAvailable64,
-    DummySync,          DummySeek,
-    DummySeek64,        DummyFileInfo,
-    DummyFileInfo64,    DummyWritev,
-    DummyConnect,       DummyAccept,
-    DummyBind,          DummyListen,
-    DummyShutdown,      DummyRecv,
-    DummySend,          DummyRecvfrom,
-    DummySendto,        DummyPoll,
-    DummyAcceptRead,    DummyTransmitFile,
-    DummyGetsockname,   DummyGetpeername,
-    DummyReserved,      DummyReserved,
-    DummyGetsockoption, DummySetsockoption,
-    DummySendfile,      DummyConnectContinue,
-    DummyReserved,      DummyReserved,
-    DummyReserved,      DummyReserved};
-
 ScopedPRFileDesc DummyPrSocket::CreateFD() {
-  if (test_fd_identity == PR_INVALID_IO_LAYER) {
-    test_fd_identity = PR_GetUniqueIdentity("testtransportadapter");
-  }
-
-  ScopedPRFileDesc fd(PR_CreateIOLayerStub(test_fd_identity, &DummyMethods));
-  fd->secret = reinterpret_cast<PRFilePrivate *>(this);
-  return fd;
+  static PRDescIdentity test_fd_identity =
+      PR_GetUniqueIdentity("testtransportadapter");
+  return DummyIOLayerMethods::CreateFD(test_fd_identity, this);
 }
 
 void DummyPrSocket::PacketReceived(const DataBuffer &packet) {
   input_.push(Packet(packet));
 }
 
-int32_t DummyPrSocket::Read(void *data, int32_t len) {
+int32_t DummyPrSocket::Read(PRFileDesc *f, void *data, int32_t len) {
   PR_ASSERT(mode_ == STREAM);
 
   if (mode_ != STREAM) {
     PR_SetError(PR_INVALID_METHOD_ERROR, 0);
     return -1;
   }
 
   if (input_.empty()) {
@@ -295,17 +62,28 @@ int32_t DummyPrSocket::Read(void *data, 
 
   if (!front.remaining()) {
     input_.pop();
   }
 
   return static_cast<int32_t>(to_read);
 }
 
-int32_t DummyPrSocket::Recv(void *buf, int32_t buflen) {
+int32_t DummyPrSocket::Recv(PRFileDesc *f, void *buf, int32_t buflen,
+                            int32_t flags, PRIntervalTime to) {
+  PR_ASSERT(flags == 0);
+  if (flags != 0) {
+    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+    return -1;
+  }
+
+  if (mode() != DGRAM) {
+    return Read(f, buf, buflen);
+  }
+
   if (input_.empty()) {
     PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
     return -1;
   }
 
   auto &front = input_.front();
   if (static_cast<size_t>(buflen) < front.len()) {
     PR_ASSERT(false);
@@ -315,17 +93,17 @@ int32_t DummyPrSocket::Recv(void *buf, i
 
   size_t count = front.len();
   memcpy(buf, front.data(), count);
 
   input_.pop();
   return static_cast<int32_t>(count);
 }
 
-int32_t DummyPrSocket::Write(const void *buf, int32_t length) {
+int32_t DummyPrSocket::Write(PRFileDesc *f, const void *buf, int32_t length) {
   auto peer = peer_.lock();
   if (!peer || !writeable_) {
     PR_SetError(PR_IO_ERROR, 0);
     return -1;
   }
 
   DataBuffer packet(static_cast<const uint8_t *>(buf),
                     static_cast<size_t>(length));
--- a/gtests/ssl_gtest/test_io.h
+++ b/gtests/ssl_gtest/test_io.h
@@ -10,16 +10,17 @@
 #include <string.h>
 #include <map>
 #include <memory>
 #include <ostream>
 #include <queue>
 #include <string>
 
 #include "databuffer.h"
+#include "dummy_io.h"
 #include "prio.h"
 #include "scoped_ptrs.h"
 
 namespace nss_test {
 
 class DataBuffer;
 class DummyPrSocket;  // Fwd decl.
 
@@ -44,17 +45,17 @@ class PacketFilter {
 };
 
 enum Mode { STREAM, DGRAM };
 
 inline std::ostream& operator<<(std::ostream& os, Mode m) {
   return os << ((m == STREAM) ? "TLS" : "DTLS");
 }
 
-class DummyPrSocket {
+class DummyPrSocket : public DummyIOLayerMethods {
  public:
   DummyPrSocket(const std::string& name, Mode mode)
       : name_(name),
         mode_(mode),
         peer_(),
         input_(),
         filter_(nullptr),
         writeable_(true) {}
@@ -66,19 +67,20 @@ class DummyPrSocket {
 
   std::weak_ptr<DummyPrSocket>& peer() { return peer_; }
   void SetPeer(const std::shared_ptr<DummyPrSocket>& peer) { peer_ = peer; }
   void SetPacketFilter(std::shared_ptr<PacketFilter> filter);
   // Drops peer, packet filter and any outstanding packets.
   void Reset();
 
   void PacketReceived(const DataBuffer& data);
-  int32_t Read(void* data, int32_t len);
-  int32_t Recv(void* buf, int32_t buflen);
-  int32_t Write(const void* buf, int32_t length);
+  int32_t Read(PRFileDesc* f, void* data, int32_t len) override;
+  int32_t Recv(PRFileDesc* f, void* buf, int32_t buflen, int32_t flags,
+               PRIntervalTime to) override;
+  int32_t Write(PRFileDesc* f, const void* buf, int32_t length) override;
   void CloseWrites() { writeable_ = false; }
 
   Mode mode() const { return mode_; }
   bool readable() const { return !input_.empty(); }
 
  private:
   class Packet : public DataBuffer {
    public:
--- a/gtests/util_gtest/manifest.mn
+++ b/gtests/util_gtest/manifest.mn
@@ -8,16 +8,17 @@ MODULE = nss
 
 CPPSRCS = \
 	util_utf8_unittest.cc \
 	$(NULL)
 
 INCLUDES += \
 	-I$(CORE_DEPTH)/gtests/google_test/gtest/include \
 	-I$(CORE_DEPTH)/gtests/common \
+	-I$(CORE_DEPTH)/cpputil \
 	$(NULL)
 
 REQUIRES = nspr gtest
 
 PROGRAM = util_gtest
 
 EXTRA_LIBS = \
 	$(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) \
--- a/gtests/util_gtest/util_gtest.gyp
+++ b/gtests/util_gtest/util_gtest.gyp
@@ -27,17 +27,15 @@
         '<(DEPTH)/lib/dev/dev.gyp:nssdev',
         '<(DEPTH)/lib/pki/pki.gyp:nsspki',
         '<(DEPTH)/lib/ssl/ssl.gyp:ssl',
       ]
     }
   ],
   'target_defaults': {
     'include_dirs': [
-      '../../gtests/google_test/gtest/include',
-      '../../gtests/common',
       '../../lib/util'
     ]
   },
   'variables': {
     'module': 'nss'
   }
 }
deleted file mode 100644
--- a/nss-tool/common/scoped_ptrs.h
+++ /dev/null
@@ -1,57 +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/. */
-
-#ifndef scoped_ptrs_h__
-#define scoped_ptrs_h__
-
-#include <memory>
-#include "cert.h"
-#include "keyhi.h"
-#include "pk11pub.h"
-
-struct ScopedDelete {
-  void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); }
-  void operator()(CERTCertificateList* list) {
-    CERT_DestroyCertificateList(list);
-  }
-  void operator()(CERTSubjectPublicKeyInfo* spki) {
-    SECKEY_DestroySubjectPublicKeyInfo(spki);
-  }
-  void operator()(PK11SlotInfo* slot) { PK11_FreeSlot(slot); }
-  void operator()(PK11SymKey* key) { PK11_FreeSymKey(key); }
-  void operator()(SECAlgorithmID* id) { SECOID_DestroyAlgorithmID(id, true); }
-  void operator()(SECItem* item) { SECITEM_FreeItem(item, true); }
-  void operator()(SECKEYPublicKey* key) { SECKEY_DestroyPublicKey(key); }
-  void operator()(SECKEYPrivateKey* key) { SECKEY_DestroyPrivateKey(key); }
-
-  void operator()(CERTCertList* list) { CERT_DestroyCertList(list); }
-};
-
-template <class T>
-struct ScopedMaybeDelete {
-  void operator()(T* ptr) {
-    if (ptr) {
-      ScopedDelete del;
-      del(ptr);
-    }
-  }
-};
-
-#define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDelete<x> > Scoped##x
-
-SCOPED(CERTCertificate);
-SCOPED(CERTCertificateList);
-SCOPED(CERTSubjectPublicKeyInfo);
-SCOPED(PK11SlotInfo);
-SCOPED(PK11SymKey);
-SCOPED(SECAlgorithmID);
-SCOPED(SECItem);
-SCOPED(SECKEYPublicKey);
-SCOPED(SECKEYPrivateKey);
-
-SCOPED(CERTCertList);
-
-#undef SCOPED
-
-#endif
--- a/nss-tool/nss_tool.gyp
+++ b/nss-tool/nss_tool.gyp
@@ -14,14 +14,15 @@
         'nss_tool.cc',
         'common/argparse.cc',
         'db/dbtool.cc',
       ],
       'include_dirs': [
         'common',
       ],
       'dependencies' : [
+        '<(DEPTH)/cpputil/cpputil.gyp:cpputil',
         '<(DEPTH)/exports.gyp:dbm_exports',
-        '<(DEPTH)/exports.gyp:nss_exports'
+        '<(DEPTH)/exports.gyp:nss_exports',
       ],
     }
   ],
 }