Add DTLS fuzzers.

Bug: 124
Change-Id: Iff02be9df2806572e6d3f860b448f598f85778c3
Reviewed-on: https://boringssl-review.googlesource.com/20107
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
index 556514f..5eff3d9 100644
--- a/fuzz/CMakeLists.txt
+++ b/fuzz/CMakeLists.txt
@@ -59,6 +59,26 @@
 target_link_libraries(client ssl)
 
 add_executable(
+  dtls_server
+
+  dtls_server.cc
+)
+
+target_link_libraries(dtls_server Fuzzer)
+target_link_libraries(dtls_server crypto)
+target_link_libraries(dtls_server ssl)
+
+add_executable(
+  dtls_client
+
+  dtls_client.cc
+)
+
+target_link_libraries(dtls_client Fuzzer)
+target_link_libraries(dtls_client crypto)
+target_link_libraries(dtls_client ssl)
+
+add_executable(
   read_pem
 
   read_pem.cc
diff --git a/fuzz/client.cc b/fuzz/client.cc
index 5f930b6..ad15486 100644
--- a/fuzz/client.cc
+++ b/fuzz/client.cc
@@ -15,7 +15,7 @@
 #include "../ssl/test/fuzzer.h"
 
 
-static TLSFuzzer g_fuzzer(TLSFuzzer::kClient);
+static TLSFuzzer g_fuzzer(TLSFuzzer::kTLS, TLSFuzzer::kClient);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
   return g_fuzzer.TestOneInput(buf, len);
diff --git a/fuzz/dtls_client.cc b/fuzz/dtls_client.cc
new file mode 100644
index 0000000..5fb6b3b
--- /dev/null
+++ b/fuzz/dtls_client.cc
@@ -0,0 +1,22 @@
+/* Copyright (c) 2017, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "../ssl/test/fuzzer.h"
+
+
+static TLSFuzzer g_fuzzer(TLSFuzzer::kDTLS, TLSFuzzer::kClient);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+  return g_fuzzer.TestOneInput(buf, len);
+}
diff --git a/fuzz/dtls_server.cc b/fuzz/dtls_server.cc
new file mode 100644
index 0000000..5a27915
--- /dev/null
+++ b/fuzz/dtls_server.cc
@@ -0,0 +1,22 @@
+/* Copyright (c) 2017, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "../ssl/test/fuzzer.h"
+
+
+static TLSFuzzer g_fuzzer(TLSFuzzer::kDTLS, TLSFuzzer::kServer);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+  return g_fuzzer.TestOneInput(buf, len);
+}
diff --git a/fuzz/refresh_ssl_corpora.sh b/fuzz/refresh_ssl_corpora.sh
index bded442..6db5562 100755
--- a/fuzz/refresh_ssl_corpora.sh
+++ b/fuzz/refresh_ssl_corpora.sh
@@ -60,6 +60,8 @@
 assert_directory client_corpus_no_fuzzer_mode
 assert_directory server_corpus
 assert_directory server_corpus_no_fuzzer_mode
+assert_directory dtls_client_corpus
+assert_directory dtls_server_corpus
 
 
 # Gather new transcripts. Ignore errors in running the tests.
@@ -102,6 +104,8 @@
 minimize_corpus "$fuzzer_mode_build_dir/fuzz/server" server_corpus
 minimize_corpus "$no_fuzzer_mode_build_dir/fuzz/client" client_corpus_no_fuzzer_mode
 minimize_corpus "$no_fuzzer_mode_build_dir/fuzz/server" server_corpus_no_fuzzer_mode
+minimize_corpus "$fuzzer_mode_build_dir/fuzz/dtls_client" dtls_client_corpus
+minimize_corpus "$fuzzer_mode_build_dir/fuzz/dtls_server" dtls_server_corpus
 
 
 # Incorporate the new transcripts.
@@ -110,3 +114,5 @@
 "$fuzzer_mode_build_dir/fuzz/server" -max_len=50000 -merge=1 server_corpus "${fuzzer_mode_transcripts}/tls/server"
 "$no_fuzzer_mode_build_dir/fuzz/client" -max_len=50000 -merge=1 client_corpus_no_fuzzer_mode "${no_fuzzer_mode_transcripts}/tls/client"
 "$no_fuzzer_mode_build_dir/fuzz/server" -max_len=50000 -merge=1 server_corpus_no_fuzzer_mode "${no_fuzzer_mode_transcripts}/tls/server"
+"$fuzzer_mode_build_dir/fuzz/dtls_client" -max_len=50000 -merge=1 dtls_client_corpus "${fuzzer_mode_transcripts}/dtls/client"
+"$fuzzer_mode_build_dir/fuzz/dtls_server" -max_len=50000 -merge=1 dtls_server_corpus "${fuzzer_mode_transcripts}/dtls/server"
diff --git a/fuzz/server.cc b/fuzz/server.cc
index 1d5c7b9..9f8cee2 100644
--- a/fuzz/server.cc
+++ b/fuzz/server.cc
@@ -15,7 +15,7 @@
 #include "../ssl/test/fuzzer.h"
 
 
-static TLSFuzzer g_fuzzer(TLSFuzzer::kServer);
+static TLSFuzzer g_fuzzer(TLSFuzzer::kTLS, TLSFuzzer::kServer);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
   return g_fuzzer.TestOneInput(buf, len);
diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h
index 76d52de..c794c4c 100644
--- a/ssl/test/fuzzer.h
+++ b/ssl/test/fuzzer.h
@@ -15,7 +15,11 @@
 #ifndef HEADER_SSL_TEST_FUZZER
 #define HEADER_SSL_TEST_FUZZER
 
+#include <assert.h>
 #include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
 
 #include <openssl/bio.h>
 #include <openssl/bytestring.h>
@@ -253,13 +257,19 @@
 
 class TLSFuzzer {
  public:
+  enum Protocol {
+    kTLS,
+    kDTLS,
+  };
+
   enum Role {
     kClient,
     kServer,
   };
 
-  explicit TLSFuzzer(Role role)
+  TLSFuzzer(Protocol protocol, Role role)
       : debug_(getenv("BORINGSSL_FUZZER_DEBUG") != nullptr),
+        protocol_(protocol),
         role_(role) {
     if (!Init()) {
       abort();
@@ -284,11 +294,9 @@
       SSL_set_tlsext_host_name(ssl.get(), "hostname");
     }
 
-    BIO *in = BIO_new(BIO_s_mem());
-    BIO *out = BIO_new(BIO_s_mem());
-    SSL_set_bio(ssl.get(), in, out);  // Takes ownership of |in| and |out|.
+    SSL_set0_rbio(ssl.get(), MakeBIO(CBS_data(&cbs), CBS_len(&cbs)).release());
+    SSL_set0_wbio(ssl.get(), BIO_new(BIO_s_mem()));
 
-    BIO_write(in, CBS_data(&cbs), CBS_len(&cbs));
     if (SSL_do_handshake(ssl.get()) == 1) {
       // Keep reading application data until error or EOF.
       uint8_t tmp[1024];
@@ -311,7 +319,7 @@
  private:
   // Init initializes |ctx_| with settings common to all inputs.
   bool Init() {
-    ctx_.reset(SSL_CTX_new(TLS_method()));
+    ctx_.reset(SSL_CTX_new(protocol_ == kDTLS ? DTLS_method() : TLS_method()));
     bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
     bssl::UniquePtr<RSA> privkey(RSA_private_key_from_bytes(
         kRSAPrivateKeyDER, sizeof(kRSAPrivateKeyDER)));
@@ -341,11 +349,15 @@
     SSL_CTX_enable_ocsp_stapling(ctx_.get());
 
     // Enable versions and ciphers that are off by default.
-    if (!SSL_CTX_set_strict_cipher_list(ctx_.get(), "ALL:NULL-SHA") ||
-        !SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION) ||
-        !SSL_CTX_set_min_proto_version(ctx_.get(), SSL3_VERSION)) {
+    if (!SSL_CTX_set_strict_cipher_list(ctx_.get(), "ALL:NULL-SHA")) {
       return false;
     }
+    if (protocol_ == kTLS) {
+      if (!SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION) ||
+          !SSL_CTX_set_min_proto_version(ctx_.get(), SSL3_VERSION)) {
+        return false;
+      }
+    }
 
     SSL_CTX_set_early_data_enabled(ctx_.get(), 1);
 
@@ -436,11 +448,70 @@
     }
   }
 
+  struct BIOData {
+    Protocol protocol;
+    CBS cbs;
+  };
+
+  bssl::UniquePtr<BIO> MakeBIO(const uint8_t *in, size_t len) {
+    BIOData *b = new BIOData;
+    b->protocol = protocol_;
+    CBS_init(&b->cbs, in, len);
+
+    bssl::UniquePtr<BIO> bio(BIO_new(&kBIOMethod));
+    bio->init = 1;
+    bio->ptr = b;
+    return bio;
+  }
+
+  static int BIORead(BIO *bio, char *out, int len) {
+    assert(bio->method == &kBIOMethod);
+    BIOData *b = reinterpret_cast<BIOData *>(bio->ptr);
+    if (b->protocol == kTLS) {
+      len = std::min(static_cast<size_t>(len), CBS_len(&b->cbs));
+      memcpy(out, CBS_data(&b->cbs), len);
+      CBS_skip(&b->cbs, len);
+      return len;
+    }
+
+    // Preserve packet boundaries for DTLS.
+    CBS packet;
+    if (!CBS_get_u24_length_prefixed(&b->cbs, &packet)) {
+      return -1;
+    }
+    len = std::min(static_cast<size_t>(len), CBS_len(&packet));
+    memcpy(out, CBS_data(&packet), len);
+    return len;
+  }
+
+  static int BIODestroy(BIO *bio) {
+    assert(bio->method == &kBIOMethod);
+    BIOData *b = reinterpret_cast<BIOData *>(bio->ptr);
+    delete b;
+    return 1;
+  }
+
+  static const BIO_METHOD kBIOMethod;
+
   bool debug_;
+  Protocol protocol_;
   Role role_;
   bssl::UniquePtr<SSL_CTX> ctx_;
 };
 
+const BIO_METHOD TLSFuzzer::kBIOMethod = {
+    0,        // type
+    nullptr,  // name
+    nullptr,  // bwrite
+    TLSFuzzer::BIORead,
+    nullptr,  // bputs
+    nullptr,  // bgets
+    nullptr,  // ctrl
+    nullptr,  // create
+    TLSFuzzer::BIODestroy,
+    nullptr,  // callback_ctrl
+};
+
 }  // namespace
 
 
diff --git a/ssl/test/runner/recordingconn.go b/ssl/test/runner/recordingconn.go
index 4dae435..427b236 100644
--- a/ssl/test/runner/recordingconn.go
+++ b/ssl/test/runner/recordingconn.go
@@ -112,6 +112,10 @@
 		if flow.flowType != writeFlow {
 			continue
 		}
+		if r.isDatagram {
+			// Prepend a length prefix to preserve packet boundaries.
+			ret = append(ret, byte(len(flow.data)>>16), byte(len(flow.data)>>8), byte(len(flow.data)))
+		}
 		ret = append(ret, flow.data...)
 	}
 	return ret