Fuzz SSL_serialize_handoff() and SSL_serialize_handback().

This is done by adding two new tagged data types to the shim's
transcript: one for the serialized handoff, and another for the
serialized handback.

Then, the handshake driver in |TLSFuzzer| is modified to be able to
drive a handoff+handback sequence in the same way as was done for
testing: by swapping |BIO|s into additional |SSL| objects.  (If a
particular transcript does not contain a serialized handoff, this is a
no-op.)

Change-Id: Iab23e4dc27959ffd3d444adc41d40a4274e83653
Reviewed-on: https://boringssl-review.googlesource.com/27204
Commit-Queue: Matt Braithwaite <mab@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 03a89fb..5fe394d 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4544,6 +4544,7 @@
 // WARNING: |SSL_apply_handoff| may trigger “msg” callback calls.
 
 OPENSSL_EXPORT void SSL_CTX_set_handoff_mode(SSL_CTX *ctx, bool on);
+OPENSSL_EXPORT void SSL_set_handoff_mode(SSL *SSL, bool on);
 OPENSSL_EXPORT bool SSL_serialize_handoff(const SSL *ssl, CBB *out);
 OPENSSL_EXPORT bool SSL_decline_handoff(SSL *ssl);
 OPENSSL_EXPORT bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff);
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index f67961d..8fb9ada 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -503,6 +503,13 @@
   ssl->config = nullptr;
 }
 
+void SSL_set_handoff_mode(SSL *ssl, bool on) {
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->handoff = on;
+}
+
 }  // namespace bssl
 
 using namespace bssl;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 1b37a8d..ae0d2a1 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -163,6 +163,7 @@
   return true;
 }
 
+// MoveBIOs moves the |BIO|s of |src| to |dst|.  It is used for handoff.
 static void MoveBIOs(SSL *dest, SSL *src) {
   BIO *rbio = SSL_get_rbio(src);
   BIO_up_ref(rbio);
@@ -1924,6 +1925,36 @@
     return fwrite(settings, settings_len, 1, file.get()) == 1;
   }
 
+  bool WriteHandoff(const bssl::Array<uint8_t> &handoff) {
+    if (path_.empty()) {
+      return true;
+    }
+
+    CBB child;
+    if (!CBB_add_u16(cbb_.get(), kHandoffTag) ||
+        !CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
+        !CBB_add_bytes(&child, handoff.data(), handoff.size()) ||
+        !CBB_flush(cbb_.get())) {
+      return false;
+    }
+    return true;
+  }
+
+  bool WriteHandback(const bssl::Array<uint8_t> &handback) {
+    if (path_.empty()) {
+      return true;
+    }
+
+    CBB child;
+    if (!CBB_add_u16(cbb_.get(), kHandbackTag) ||
+        !CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
+        !CBB_add_bytes(&child, handback.data(), handback.size()) ||
+        !CBB_flush(cbb_.get())) {
+      return false;
+    }
+    return true;
+  }
+
  private:
   std::string path_;
   bssl::ScopedCBB cbb_;
@@ -2147,7 +2178,8 @@
 
 static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
                        bssl::UniquePtr<SSL> *ssl_uniqueptr,
-                       const TestConfig *config, bool is_resume, bool is_retry);
+                       const TestConfig *config, bool is_resume, bool is_retry,
+                       SettingsWriter *writer);
 
 // DoConnection tests an SSL connection against the peer. On success, it returns
 // true and sets |*out_session| to the negotiated SSL session. If the test is a
@@ -2156,7 +2188,7 @@
 static bool DoConnection(bssl::UniquePtr<SSL_SESSION> *out_session,
                          SSL_CTX *ssl_ctx, const TestConfig *config,
                          const TestConfig *retry_config, bool is_resume,
-                         SSL_SESSION *session) {
+                         SSL_SESSION *session, SettingsWriter *writer) {
   bssl::UniquePtr<SSL> ssl = NewSSL(ssl_ctx, config, session, is_resume,
                                     std::unique_ptr<TestState>(new TestState));
   if (!ssl) {
@@ -2201,7 +2233,7 @@
   SSL_set_bio(ssl.get(), bio.get(), bio.get());
   bio.release();  // SSL_set_bio takes ownership.
 
-  bool ret = DoExchange(out_session, &ssl, config, is_resume, false);
+  bool ret = DoExchange(out_session, &ssl, config, is_resume, false, writer);
   if (!config->is_server && is_resume && config->expect_reject_early_data) {
     // We must have failed due to an early data rejection.
     if (ret) {
@@ -2236,7 +2268,7 @@
     }
 
     assert(!config->handoff);
-    ret = DoExchange(out_session, &ssl, retry_config, is_resume, true);
+    ret = DoExchange(out_session, &ssl, retry_config, is_resume, true, writer);
   }
 
   if (!ret) {
@@ -2269,8 +2301,8 @@
 
 static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
                        bssl::UniquePtr<SSL> *ssl_uniqueptr,
-                       const TestConfig *config, bool is_resume,
-                       bool is_retry) {
+                       const TestConfig *config, bool is_resume, bool is_retry,
+                       SettingsWriter *writer) {
   int ret;
   SSL *ssl = ssl_uniqueptr->get();
   SSL_CTX *session_ctx = ssl->ctx;
@@ -2312,7 +2344,8 @@
       bssl::Array<uint8_t> handoff;
       if (!CBB_init(cbb.get(), 512) ||
           !SSL_serialize_handoff(ssl_handoff.get(), cbb.get()) ||
-          !CBBFinishArray(cbb.get(), &handoff)) {
+          !CBBFinishArray(cbb.get(), &handoff) ||
+          !writer->WriteHandoff(handoff)) {
         fprintf(stderr, "Handoff serialisation failed.\n");
         return false;
       }
@@ -2344,7 +2377,8 @@
       bssl::Array<uint8_t> handback;
       if (!CBB_init(cbb.get(), 512) ||
           !SSL_serialize_handback(ssl, cbb.get()) ||
-          !CBBFinishArray(cbb.get(), &handback)) {
+          !CBBFinishArray(cbb.get(), &handback) ||
+          !writer->WriteHandback(handback)) {
         fprintf(stderr, "Handback serialisation failed.\n");
         return false;
       }
@@ -2740,7 +2774,7 @@
       return 1;
     }
     bool ok = DoConnection(&session, ssl_ctx.get(), config, &retry_config,
-                           is_resume, offer_session.get());
+                           is_resume, offer_session.get(), &writer);
     if (!writer.Commit()) {
       fprintf(stderr, "Error writing settings.\n");
       return 1;
diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h
index c794c4c..b240630 100644
--- a/ssl/test/fuzzer.h
+++ b/ssl/test/fuzzer.h
@@ -30,9 +30,9 @@
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
 
+#include "../internal.h"
 #include "./fuzzer_tags.h"
 
-
 namespace {
 
 const uint8_t kP256KeyPKCS8[] = {
@@ -276,6 +276,19 @@
     }
   }
 
+  static void MoveBIOs(SSL *dest, SSL *src) {
+    BIO *rbio = SSL_get_rbio(src);
+    BIO_up_ref(rbio);
+    SSL_set0_rbio(dest, rbio);
+
+    BIO *wbio = SSL_get_wbio(src);
+    BIO_up_ref(wbio);
+    SSL_set0_wbio(dest, wbio);
+
+    SSL_set0_rbio(src, nullptr);
+    SSL_set0_wbio(src, nullptr);
+  }
+
   int TestOneInput(const uint8_t *buf, size_t len) {
     RAND_reset_for_fuzzing();
 
@@ -294,19 +307,63 @@
       SSL_set_tlsext_host_name(ssl.get(), "hostname");
     }
 
+    // ssl_handoff may or may not be used.
+    bssl::UniquePtr<SSL> ssl_handoff(SSL_new(ctx_.get()));
+    bssl::UniquePtr<SSL> ssl_handback(SSL_new(ctx_.get()));
+    SSL_set_accept_state(ssl_handoff.get());
+
     SSL_set0_rbio(ssl.get(), MakeBIO(CBS_data(&cbs), CBS_len(&cbs)).release());
     SSL_set0_wbio(ssl.get(), BIO_new(BIO_s_mem()));
 
-    if (SSL_do_handshake(ssl.get()) == 1) {
+    SSL *ssl_handshake = ssl.get();
+    bool handshake_successful = false;
+    bool handback_successful = false;
+    for (;;) {
+      int ret = SSL_do_handshake(ssl_handshake);
+      if (ret < 0 && SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDOFF) {
+        MoveBIOs(ssl_handoff.get(), ssl.get());
+        // Ordinarily we would call SSL_serialize_handoff(ssl.get().  But for
+        // fuzzing, use the serialized handoff that's getting fuzzed.
+        if (!SSL_apply_handoff(ssl_handoff.get(), handoff_)) {
+          if (debug_) {
+            fprintf(stderr, "Handoff failed.\n");
+          }
+          break;
+        }
+        ssl_handshake = ssl_handoff.get();
+      } else if (ret < 0 &&
+                 SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDBACK) {
+        MoveBIOs(ssl_handback.get(), ssl_handoff.get());
+        if (!SSL_apply_handback(ssl_handback.get(), handback_)) {
+          if (debug_) {
+            fprintf(stderr, "Handback failed.\n");
+          }
+          break;
+        }
+        handback_successful = true;
+        ssl_handshake = ssl_handback.get();
+      } else {
+        handshake_successful = ret == 1;
+        break;
+      }
+    }
+
+    if (debug_) {
+      if (!handshake_successful) {
+        fprintf(stderr, "Handshake failed.\n");
+      } else if (handback_successful) {
+        fprintf(stderr, "Handback successful.\n");
+      }
+    }
+
+    if (handshake_successful) {
       // Keep reading application data until error or EOF.
       uint8_t tmp[1024];
       for (;;) {
-        if (SSL_read(ssl.get(), tmp, sizeof(tmp)) <= 0) {
+        if (SSL_read(ssl_handshake, tmp, sizeof(tmp)) <= 0) {
           break;
         }
       }
-    } else if (debug_) {
-      fprintf(stderr, "Handshake failed.\n");
     }
 
     if (debug_) {
@@ -389,6 +446,8 @@
     // |ctx| is shared between runs, so we must clear any modifications to it
     // made later on in this function.
     SSL_CTX_flush_sessions(ctx_.get(), 0);
+    handoff_ = {};
+    handback_ = {};
 
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx_.get()));
     if (role_ == kServer) {
@@ -442,6 +501,26 @@
           break;
         }
 
+        case kHandoffTag: {
+          CBS handoff;
+          if (!CBS_get_u24_length_prefixed(cbs, &handoff)) {
+            return nullptr;
+          }
+          handoff_.CopyFrom(handoff);
+          bssl::SSL_set_handoff_mode(ssl.get(), 1);
+          break;
+        }
+
+        case kHandbackTag: {
+          CBS handback;
+          if (!CBS_get_u24_length_prefixed(cbs, &handback)) {
+            return nullptr;
+          }
+          handback_.CopyFrom(handback);
+          bssl::SSL_set_handoff_mode(ssl.get(), 1);
+          break;
+        }
+
         default:
           return nullptr;
       }
@@ -497,6 +576,7 @@
   Protocol protocol_;
   Role role_;
   bssl::UniquePtr<SSL_CTX> ctx_;
+  bssl::Array<uint8_t> handoff_, handback_;
 };
 
 const BIO_METHOD TLSFuzzer::kBIOMethod = {
diff --git a/ssl/test/fuzzer_tags.h b/ssl/test/fuzzer_tags.h
index b161d80..c21aca3 100644
--- a/ssl/test/fuzzer_tags.h
+++ b/ssl/test/fuzzer_tags.h
@@ -42,4 +42,10 @@
 // kTLS13Variant is followed by a u8 denoting the TLS 1.3 variant to configure.
 static const uint16_t kTLS13Variant = 3;
 
+// kHandoffTag is followed by the output of |SSL_serialize_handoff|.
+static const uint16_t kHandoffTag = 4;
+
+// kHandbackTag is followed by te output of |SSL_serialize_handback|.
+static const uint16_t kHandbackTag = 5;
+
 #endif  // HEADER_SSL_TEST_FUZZER_TAGS