Clean up s23_srvr.c.

ssl23_get_client_hello has lots of remnants of SSLv2 support and remnants of an
even older SSL_OP_NON_EXPORT_FIRST option (see upstream's
d92f0bb6e9ed94ac0c3aa0c939f2565f2ed95935) which complicates the logic.

Split it into three states and move V2ClientHello parsing into its own
function. Port it to CBS and CBB to give bounds checks on the V2ClientHello
parse.

This fixes a minor bug where, if the SSL_accept call in ssl23_get_client_hello
failed, cb would not be NULL'd and SSL_CB_ACCEPT_LOOP would get reported an
extra time.

It also unbreaks the invariant between s->packet, s->packet_length,
s->s3->rbuf.buf, and s->s3->rbuf.offset at the point the switch, although this
was of no consequence because the first ssl3_read_n call passes extend = 0
which resets s->packet and s->packet_length.

It also makes us tolerant to major version bumps in the ClientHello. Add tests
for TLS tolerance of both minor and major version bumps as well as the HTTP
request error codes.

Change-Id: I948337f4dc483f4ebe1742d3eba53b045b260257
Reviewed-on: https://boringssl-review.googlesource.com/1455
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bytestring/cbb.c b/crypto/bytestring/cbb.c
index 41770df..6767b9d 100644
--- a/crypto/bytestring/cbb.c
+++ b/crypto/bytestring/cbb.c
@@ -138,8 +138,17 @@
     return 0;
   }
 
-  *out_data = cbb->base->buf;
-  *out_len = cbb->base->len;
+  if (cbb->base->can_resize && (out_data == NULL || out_len == NULL)) {
+    /* |out_data| and |out_len| can only be NULL if the CBB is fixed. */
+    return 0;
+  }
+
+  if (out_data != NULL) {
+    *out_data = cbb->base->buf;
+  }
+  if (out_len != NULL) {
+    *out_len = cbb->base->len;
+  }
   cbb->base->buf = NULL;
   CBB_cleanup(cbb);
   return 1;
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index d1734a8..c042256 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2496,6 +2496,7 @@
 #define SSL_F_tls1_aead_ctx_init 280
 #define SSL_F_tls1_check_duplicate_extensions 281
 #define SSL_F_ssl3_expect_change_cipher_spec 282
+#define SSL_F_ssl23_get_v2_client_hello 283
 #define SSL_R_UNABLE_TO_FIND_ECDH_PARAMETERS 100
 #define SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC 101
 #define SSL_R_INVALID_NULL_CMD_NAME 102
diff --git a/include/openssl/ssl23.h b/include/openssl/ssl23.h
index d322898..0095937 100644
--- a/include/openssl/ssl23.h
+++ b/include/openssl/ssl23.h
@@ -73,8 +73,9 @@
 
 /* server */
 /* read from client */
-#define SSL23_ST_SR_CLNT_HELLO_A	(0x210|SSL_ST_ACCEPT)
-#define SSL23_ST_SR_CLNT_HELLO_B	(0x211|SSL_ST_ACCEPT)
+#define SSL23_ST_SR_CLNT_HELLO		(0x210|SSL_ST_ACCEPT)
+#define SSL23_ST_SR_V2_CLNT_HELLO	(0x220|SSL_ST_ACCEPT)
+#define SSL23_ST_SR_SWITCH_VERSION	(0x230|SSL_ST_ACCEPT)
 
 #ifdef  __cplusplus
 }
diff --git a/ssl/s23_srvr.c b/ssl/s23_srvr.c
index ecf999d..f46e4e2 100644
--- a/ssl/s23_srvr.c
+++ b/ssl/s23_srvr.c
@@ -106,6 +106,7 @@
  * (eay@cryptsoft.com).  This product includes software written by Tim
  * Hudson (tjh@cryptsoft.com). */
 
+#include <assert.h>
 #include <stdio.h>
 
 #include <openssl/buf.h>
@@ -118,7 +119,9 @@
 #include "ssl_locl.h"
 
 static const SSL_METHOD *ssl23_get_server_method(int ver);
-int ssl23_get_client_hello(SSL *s);
+static int ssl23_get_client_hello(SSL *s);
+static int ssl23_get_v2_client_hello(SSL *s);
+
 static const SSL_METHOD *ssl23_get_server_method(int ver)
 	{
 	if (ver == SSL3_VERSION)
@@ -190,17 +193,38 @@
 
 			ssl3_init_finished_mac(s);
 
-			s->state=SSL23_ST_SR_CLNT_HELLO_A;
+			s->state=SSL23_ST_SR_CLNT_HELLO;
 			s->ctx->stats.sess_accept++;
 			s->init_num=0;
 			break;
 
-		case SSL23_ST_SR_CLNT_HELLO_A:
-		case SSL23_ST_SR_CLNT_HELLO_B:
+		case SSL23_ST_SR_CLNT_HELLO:
+			s->shutdown = 0;
+			ret = ssl23_get_client_hello(s);
+			if (ret <= 0) goto end;
+			break;
 
-			s->shutdown=0;
-			ret=ssl23_get_client_hello(s);
-			if (ret >= 0) cb=NULL;
+		case SSL23_ST_SR_V2_CLNT_HELLO:
+			ret = ssl23_get_v2_client_hello(s);
+			if (ret <= 0) goto end;
+			break;
+
+		case SSL23_ST_SR_SWITCH_VERSION:
+			if (!ssl_init_wbio_buffer(s, 1))
+				{
+				ret = -1;
+				goto end;
+				}
+
+			s->state = SSL3_ST_SR_CLNT_HELLO_A;
+			s->method = ssl23_get_server_method(s->version);
+			assert(s->method != NULL);
+			s->handshake_func = s->method->ssl_accept;
+			s->init_num = 0;
+
+			/* NULL the callback; SSL_accept will call it instead. */
+			cb = NULL;
+			ret = SSL_accept(s);
 			goto end;
 			/* break; */
 
@@ -226,191 +250,137 @@
 	return(ret);
 	}
 
-
-int ssl23_get_client_hello(SSL *s)
+/* ssl23_get_mutual_version determines the highest supported version for a
+ * client which reports a highest version of |client_version|. On success, it
+ * returns 1 and sets |*out_version| to the negotiated version. Otherwise, it
+ * returns 0. */
+static int ssl23_get_mutual_version(SSL *s, int *out_version, uint16_t client_version)
 	{
-	char buf_space[11]; /* Request this many bytes in initial read.
-	                     * We can detect SSL 3.0/TLS 1.0 Client Hellos
-	                     * ('type == 3') correctly only when the following
-	                     * is in a single record, which is not guaranteed by
-	                     * the protocol specification:
-	                     * Byte  Content
-	                     *  0     type            \
-	                     *  1/2   version          > record header
-	                     *  3/4   length          /
-	                     *  5     msg_type        \
-	                     *  6-8   length           > Client Hello message
-	                     *  9/10  client_version  /
-	                     */
-	char *buf= &(buf_space[0]);
-	unsigned char *p,*d,*d_len,*dd;
-	unsigned int i;
-	unsigned int csl,sil,cl;
-	int n=0,j;
-	int type=0;
-	int v[2];
-
-	if (s->state ==	SSL23_ST_SR_CLNT_HELLO_A)
+	if (client_version >= TLS1_2_VERSION && !(s->options & SSL_OP_NO_TLSv1_2))
 		{
-		/* read the initial header */
-		v[0]=v[1]=0;
+		*out_version = TLS1_2_VERSION;
+		return 1;
+		}
+	if (client_version >= TLS1_1_VERSION && !(s->options & SSL_OP_NO_TLSv1_1))
+		{
+		*out_version = TLS1_1_VERSION;
+		return 1;
+		}
+	if (client_version >= TLS1_VERSION && !(s->options & SSL_OP_NO_TLSv1))
+		{
+		*out_version = TLS1_VERSION;
+		return 1;
+		}
+	if (client_version >= SSL3_VERSION && !(s->options & SSL_OP_NO_SSLv3))
+		{
+		*out_version = SSL3_VERSION;
+		return 1;
+		}
+	return 0;
+	}
 
-		if (!ssl3_setup_buffers(s)) goto err;
+static int ssl23_get_client_hello(SSL *s)
+	{
+	uint8_t *p;
+	int n = 0;
 
-		n=ssl23_read_bytes(s, sizeof buf_space);
-		if (n != sizeof buf_space) return(n); /* n == -1 || n == 0 */
+	/* Sniff enough of the input to determine ClientHello type and the
+	 * client version. */
+	if (!ssl3_setup_buffers(s)) goto err;
 
-		p=s->packet;
+	/* Read the initial 11 bytes of the input. This is sufficient to
+	 * determine the client version for a ClientHello or a
+	 * V2ClientHello.
+	 *
+	 * ClientHello (assuming client_version is unfragmented):
+	 * Byte  Content
+	 *  0     type            \
+	 *  1-2   version          > record header
+	 *  3-4   length          /
+	 *  5     msg_type        \
+	 *  6-8   length           > Client Hello message
+	 *  9-10  client_version  /
+	 *
+	 * V2ClientHello:
+	 * Byte  Content
+	 *  0-1   msg_length
+	 *  2     msg_type
+	 *  3-4   version
+	 *  5-6   cipher_spec_length
+	 *  7-8   session_id_length
+	 *  9-10  challenge_length
+	 */
+	n = ssl23_read_bytes(s, 11);
+	if (n <= 0)
+		return n;
+	assert(n == 11);
 
-		memcpy(buf,p,n);
+	p = s->packet;
 
-		if ((p[0] & 0x80) && (p[2] == SSL2_MT_CLIENT_HELLO))
+	/* Some dedicated error codes for protocol mixups should the application
+	 * wish to interpret them differently. (These do not overlap with
+	 * ClientHello or V2ClientHello.) */
+	if ((strncmp("GET ", (char *)p, 4) == 0) ||
+		(strncmp("POST ",(char *)p, 5) == 0) ||
+		(strncmp("HEAD ",(char *)p, 5) == 0) ||
+		(strncmp("PUT ", (char *)p, 4) == 0))
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_HTTP_REQUEST);
+		goto err;
+		}
+	if (strncmp("CONNECT",(char *)p, 7) == 0)
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_HTTPS_PROXY_REQUEST);
+		goto err;
+		}
+
+	/* Determine if this is a ClientHello or V2ClientHello. */
+	if ((p[0] & 0x80) && (p[2] == SSL2_MT_CLIENT_HELLO))
+		{
+		/* This is a V2ClientHello. Determine the version to
+		 * use. */
+		uint16_t client_version = (p[3] << 8) | p[4];
+		if (!ssl23_get_mutual_version(s, &s->version, client_version))
 			{
-			/*
-			 * SSLv2 header
-			 */
-			if ((p[3] == 0x00) && (p[4] == 0x02))
-				{
-				v[0]=p[3]; v[1]=p[4];
-				/* SSLv2 */
-				if (!(s->options & SSL_OP_NO_SSLv2))
-					type=1;
-				}
-			else if (p[3] == SSL3_VERSION_MAJOR)
-				{
-				v[0]=p[3]; v[1]=p[4];
-				/* SSLv3/TLSv1 */
-				if (p[4] >= TLS1_VERSION_MINOR)
-					{
-					if (p[4] >= TLS1_2_VERSION_MINOR &&
-					   !(s->options & SSL_OP_NO_TLSv1_2))
-						{
-						s->version=TLS1_2_VERSION;
-						s->state=SSL23_ST_SR_CLNT_HELLO_B;
-						}
-					else if (p[4] >= TLS1_1_VERSION_MINOR &&
-					   !(s->options & SSL_OP_NO_TLSv1_1))
-						{
-						s->version=TLS1_1_VERSION;
-						/* type=2; */ /* done later to survive restarts */
-						s->state=SSL23_ST_SR_CLNT_HELLO_B;
-						}
-					else if (!(s->options & SSL_OP_NO_TLSv1))
-						{
-						s->version=TLS1_VERSION;
-						/* type=2; */ /* done later to survive restarts */
-						s->state=SSL23_ST_SR_CLNT_HELLO_B;
-						}
-					else if (!(s->options & SSL_OP_NO_SSLv3))
-						{
-						s->version=SSL3_VERSION;
-						/* type=2; */
-						s->state=SSL23_ST_SR_CLNT_HELLO_B;
-						}
-					else if (!(s->options & SSL_OP_NO_SSLv2))
-						{
-						type=1;
-						}
-					}
-				else if (!(s->options & SSL_OP_NO_SSLv3))
-					{
-					s->version=SSL3_VERSION;
-					/* type=2; */
-					s->state=SSL23_ST_SR_CLNT_HELLO_B;
-					}
-				else if (!(s->options & SSL_OP_NO_SSLv2))
-					type=1;
-
-				}
-			}
-		else if ((p[0] == SSL3_RT_HANDSHAKE) &&
-			 (p[1] == SSL3_VERSION_MAJOR) &&
-			 (p[5] == SSL3_MT_CLIENT_HELLO) &&
-			 ((p[3] == 0 && p[4] < 5 /* silly record length? */)
-				|| (p[9] >= p[1])))
-			{
-			/*
-			 * SSLv3 or tls1 header
-			 */
-			
-			v[0]=p[1]; /* major version (= SSL3_VERSION_MAJOR) */
-			/* We must look at client_version inside the Client Hello message
-			 * to get the correct minor version.
-			 * However if we have only a pathologically small fragment of the
-			 * Client Hello message, this would be difficult, and we'd have
-			 * to read more records to find out.
-			 * No known SSL 3.0 client fragments ClientHello like this,
-			 * so we simply reject such connections to avoid
-			 * protocol version downgrade attacks. */
-			if (p[3] == 0 && p[4] < 6)
-				{
-				OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_TOO_SMALL);
-				goto err;
-				}
-			/* if major version number > 3 set minor to a value
-			 * which will use the highest version 3 we support.
-			 * If TLS 2.0 ever appears we will need to revise
-			 * this....
-			 */
-			if (p[9] > SSL3_VERSION_MAJOR)
-				v[1]=0xff;
-			else
-				v[1]=p[10]; /* minor version according to client_version */
-			if (v[1] >= TLS1_VERSION_MINOR)
-				{
-				if (v[1] >= TLS1_2_VERSION_MINOR &&
-					!(s->options & SSL_OP_NO_TLSv1_2))
-					{
-					s->version=TLS1_2_VERSION;
-					type=3;
-					}
-				else if (v[1] >= TLS1_1_VERSION_MINOR &&
-					!(s->options & SSL_OP_NO_TLSv1_1))
-					{
-					s->version=TLS1_1_VERSION;
-					type=3;
-					}
-				else if (!(s->options & SSL_OP_NO_TLSv1))
-					{
-					s->version=TLS1_VERSION;
-					type=3;
-					}
-				else if (!(s->options & SSL_OP_NO_SSLv3))
-					{
-					s->version=SSL3_VERSION;
-					type=3;
-					}
-				}
-			else
-				{
-				/* client requests SSL 3.0 */
-				if (!(s->options & SSL_OP_NO_SSLv3))
-					{
-					s->version=SSL3_VERSION;
-					type=3;
-					}
-				else if (!(s->options & SSL_OP_NO_TLSv1))
-					{
-					/* we won't be able to use TLS of course,
-					 * but this will send an appropriate alert */
-					s->version=TLS1_VERSION;
-					type=3;
-					}
-				}
-			}
-		else if ((strncmp("GET ", (char *)p,4) == 0) ||
-			 (strncmp("POST ",(char *)p,5) == 0) ||
-			 (strncmp("HEAD ",(char *)p,5) == 0) ||
-			 (strncmp("PUT ", (char *)p,4) == 0))
-			{
-			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_HTTP_REQUEST);
+			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNSUPPORTED_PROTOCOL);
 			goto err;
 			}
-		else if (strncmp("CONNECT",(char *)p,7) == 0)
+		/* Parse the entire V2ClientHello. */
+		s->state = SSL23_ST_SR_V2_CLNT_HELLO;
+		}
+	else if ((p[0] == SSL3_RT_HANDSHAKE) &&
+		 (p[1] >= SSL3_VERSION_MAJOR) &&
+		 (p[5] == SSL3_MT_CLIENT_HELLO))
+		{
+		/* This is a fragment of a ClientHello. We look at the
+		 * client_hello to negotiate the version. However, this
+		 * is difficult if we have only a pathologically small
+		 * fragment. No known client fragments ClientHello like
+		 * this, so we simply reject such connections to avoid
+		 * protocol version downgrade attacks. */
+		uint16_t record_length = (p[3] << 8) | p[4];
+		uint16_t client_version;
+		if (record_length < 6)
 			{
-			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_HTTPS_PROXY_REQUEST);
+			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_TOO_SMALL);
 			goto err;
 			}
+
+		client_version = (p[9] << 8) | p[10];
+		if (!ssl23_get_mutual_version(s, &s->version, client_version))
+			{
+			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNSUPPORTED_PROTOCOL);
+			goto err;
+			}
+
+                /* Reset the record-layer state for SSL3. */
+                assert(s->rstate == SSL_ST_READ_HEADER);
+                s->s3->rbuf.left = s->packet_length;
+                s->s3->rbuf.offset = 0;
+                s->packet_length = 0;
+
+		/* Ready to switch versions. */
+		s->state = SSL23_ST_SR_SWITCH_VERSION;
 		}
 
 	if (s->version < TLS1_2_VERSION && tls1_suiteb(s))
@@ -419,183 +389,142 @@
 		goto err;
 		}
 
-	if (s->state == SSL23_ST_SR_CLNT_HELLO_B)
-		{
-		/* we have SSLv3/TLSv1 in an SSLv2 header
-		 * (other cases skip this state) */
-
-		type=2;
-		p=s->packet;
-		v[0] = p[3]; /* == SSL3_VERSION_MAJOR */
-		v[1] = p[4];
-
-		/* An SSLv3/TLSv1 backwards-compatible CLIENT-HELLO in an SSLv2
-		 * header is sent directly on the wire, not wrapped as a TLS
-		 * record. It's format is:
-		 * Byte  Content
-		 * 0-1   msg_length
-		 * 2     msg_type
-		 * 3-4   version
-		 * 5-6   cipher_spec_length
-		 * 7-8   session_id_length
-		 * 9-10  challenge_length
-		 * ...   ...
-		 */
-		n=((p[0]&0x7f)<<8)|p[1];
-		if (n > (1024*4))
-			{
-			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_TOO_LARGE);
-			goto err;
-			}
-		if (n < 9)
-			{
-			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_LENGTH_MISMATCH);
-			goto err;
-			}
-
-		j=ssl23_read_bytes(s,n+2);
-		/* We previously read 11 bytes, so if j > 0, we must have
-		 * j == n+2 == s->packet_length. We have at least 11 valid
-		 * packet bytes. */
-		if (j <= 0) return(j);
-
-		ssl3_finish_mac(s, s->packet+2, s->packet_length-2);
-		if (s->msg_callback)
-			s->msg_callback(0, SSL2_VERSION, 0, s->packet+2, s->packet_length-2, s, s->msg_callback_arg); /* CLIENT-HELLO */
-
-		p=s->packet;
-		p+=5;
-		n2s(p,csl);
-		n2s(p,sil);
-		n2s(p,cl);
-		d=(unsigned char *)s->init_buf->data;
-		if ((csl+sil+cl+11) != s->packet_length) /* We can't have TLS extensions in SSL 2.0 format
-		                                          * Client Hello, can we? Error condition should be
-		                                          * '>' otherweise */
-			{
-			OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_RECORD_LENGTH_MISMATCH);
-			goto err;
-			}
-
-		/* record header: msg_type ... */
-		*(d++) = SSL3_MT_CLIENT_HELLO;
-		/* ... and length (actual value will be written later) */
-		d_len = d;
-		d += 3;
-
-		/* client_version */
-		*(d++) = SSL3_VERSION_MAJOR; /* == v[0] */
-		*(d++) = v[1];
-
-		/* lets populate the random area */
-		/* get the challenge_length */
-		i=(cl > SSL3_RANDOM_SIZE)?SSL3_RANDOM_SIZE:cl;
-		memset(d,0,SSL3_RANDOM_SIZE);
-		memcpy(&(d[SSL3_RANDOM_SIZE-i]),&(p[csl+sil]),i);
-		d+=SSL3_RANDOM_SIZE;
-
-		/* no session-id reuse */
-		*(d++)=0;
-
-		/* ciphers */
-		j=0;
-		dd=d;
-		d+=2;
-		for (i=0; i<csl; i+=3)
-			{
-			if (p[i] != 0) continue;
-			*(d++)=p[i+1];
-			*(d++)=p[i+2];
-			j+=2;
-			}
-		s2n(j,dd);
-
-		/* COMPRESSION */
-		*(d++)=1;
-		*(d++)=0;
-		
-#if 0
-                /* copy any remaining data with may be extensions */
-	        p = p+csl+sil+cl;
-		while (p <  s->packet+s->packet_length)
-			{
-			*(d++)=*(p++);
-			}
-#endif
-
-		i = (d-(unsigned char *)s->init_buf->data) - 4;
-		l2n3((long)i, d_len);
-
-		/* get the data reused from the init_buf */
-		s->s3->tmp.reuse_message=1;
-		s->s3->tmp.message_type=SSL3_MT_CLIENT_HELLO;
-		s->s3->tmp.message_size=i;
-		}
-
-	/* imaginary new state (for program structure): */
-	/* s->state = SSL23_SR_CLNT_HELLO_C */
-
-	if (type == 1)
-		{
-		OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNSUPPORTED_PROTOCOL);
-		goto err;
-		}
-
-	if ((type == 2) || (type == 3))
-		{
-		/* we have SSLv3/TLSv1 (type 2: SSL2 style, type 3: SSL3/TLS style) */
-
-		if (!ssl_init_wbio_buffer(s,1)) goto err;
-
-		/* we are in this state */
-		s->state=SSL3_ST_SR_CLNT_HELLO_A;
-
-		if (type == 3)
-			{
-			/* put the 'n' bytes we have read into the input buffer
-			 * for SSLv3 */
-			s->rstate=SSL_ST_READ_HEADER;
-			s->packet_length=n;
-			if (s->s3->rbuf.buf == NULL)
-				if (!ssl3_setup_read_buffer(s))
-					goto err;
-
-			s->packet= &(s->s3->rbuf.buf[0]);
-			memcpy(s->packet,buf,n);
-			s->s3->rbuf.left=n;
-			s->s3->rbuf.offset=0;
-			}
-		else
-			{
-			s->packet_length=0;
-			s->s3->rbuf.left=0;
-			s->s3->rbuf.offset=0;
-			}
-		if (s->version == TLS1_2_VERSION)
-			s->method = TLSv1_2_server_method();
-		else if (s->version == TLS1_1_VERSION)
-			s->method = TLSv1_1_server_method();
-		else if (s->version == TLS1_VERSION)
-			s->method = TLSv1_server_method();
-		else
-			s->method = SSLv3_server_method();
-#if 0 /* ssl3_get_client_hello does this */
-		s->client_version=(v[0]<<8)|v[1];
-#endif
-		s->handshake_func=s->method->ssl_accept;
-		}
-	
-	if ((type < 1) || (type > 3))
-		{
-		/* bad, very bad */
-		OPENSSL_PUT_ERROR(SSL, ssl23_get_client_hello, SSL_R_UNKNOWN_PROTOCOL);
-		goto err;
-		}
-	s->init_num=0;
-
-	if (buf != buf_space) OPENSSL_free(buf);
-	return(SSL_accept(s));
+	return 1;
 err:
-	if (buf != buf_space) OPENSSL_free(buf);
-	return(-1);
+	return -1;
+	}
+
+static int ssl23_get_v2_client_hello(SSL *s)
+	{
+	uint8_t *p;
+	size_t i;
+	int n = 0;
+
+	CBS v2_client_hello, cipher_specs, session_id, challenge;
+	size_t msg_length, len;
+	uint8_t msg_type;
+	uint16_t version, cipher_spec_length, session_id_length, challenge_length;
+	CBB client_hello, hello_body, cipher_suites;
+	uint8_t random[SSL3_RANDOM_SIZE];
+
+	/* Read the remainder of the V2ClientHello. We have previously read 11
+	 * bytes in ssl23_get_client_hello. */
+	p = s->packet;
+	msg_length = ((p[0] & 0x7f) << 8) | p[1];
+	if (msg_length > (1024 * 4))
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, SSL_R_RECORD_TOO_LARGE);
+		goto err;
+		}
+	if (msg_length < 11 - 2)
+		{
+		/* Reject lengths that are too short early. We have already read
+		 * 11 bytes, so we should not attempt to process an (invalid)
+		 * V2ClientHello which would be shorter than that. */
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, SSL_R_RECORD_LENGTH_MISMATCH);
+		goto err;
+		}
+	n = ssl23_read_bytes(s, msg_length + 2);
+	if (n <= 0)
+		return n;
+	assert(n == s->packet_length);
+
+	/* The V2ClientHello without the length is incorporated into the
+	 * Finished hash. */
+	ssl3_finish_mac(s, s->packet + 2, s->packet_length - 2);
+	if (s->msg_callback)
+		s->msg_callback(0, SSL2_VERSION, 0, s->packet+2, s->packet_length-2, s, s->msg_callback_arg); /* CLIENT-HELLO */
+
+	CBS_init(&v2_client_hello, s->packet + 2, s->packet_length - 2);
+	if (!CBS_get_u8(&v2_client_hello, &msg_type) ||
+		!CBS_get_u16(&v2_client_hello, &version) ||
+		!CBS_get_u16(&v2_client_hello, &cipher_spec_length) ||
+		!CBS_get_u16(&v2_client_hello, &session_id_length) ||
+		!CBS_get_u16(&v2_client_hello, &challenge_length) ||
+		!CBS_get_bytes(&v2_client_hello, &cipher_specs, cipher_spec_length) ||
+		!CBS_get_bytes(&v2_client_hello, &session_id, session_id_length) ||
+		!CBS_get_bytes(&v2_client_hello, &challenge, challenge_length) ||
+		CBS_len(&v2_client_hello) != 0)
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, SSL_R_DECODE_ERROR);
+		goto err;
+		}
+
+	/* msg_type has already been checked. */
+	assert(msg_type == SSL2_MT_CLIENT_HELLO);
+
+	/* The client_random is the V2ClientHello challenge. Truncate or
+	 * left-pad with zeros as needed. */
+	memset(random, 0, SSL3_RANDOM_SIZE);
+	i = (CBS_len(&challenge) > SSL3_RANDOM_SIZE) ? SSL3_RANDOM_SIZE : CBS_len(&challenge);
+	memcpy(random, CBS_data(&challenge), i);
+
+	/* Write out an equivalent SSLv3 ClientHello. */
+	if (!CBB_init_fixed(&client_hello, (uint8_t *)s->init_buf->data, s->init_buf->max))
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, ERR_R_MALLOC_FAILURE);
+		goto err;
+		}
+	if (!CBB_add_u8(&client_hello, SSL3_MT_CLIENT_HELLO) ||
+		!CBB_add_u24_length_prefixed(&client_hello, &hello_body) ||
+		!CBB_add_u16(&hello_body, version) ||
+		!CBB_add_bytes(&hello_body, random, SSL3_RANDOM_SIZE) ||
+		/* No session id. */
+		!CBB_add_u8(&hello_body, 0) ||
+		!CBB_add_u16_length_prefixed(&hello_body, &cipher_suites))
+		{
+		CBB_cleanup(&client_hello);
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, ERR_R_INTERNAL_ERROR);
+		goto err;
+		}
+
+	/* Copy the cipher suites. */
+	while (CBS_len(&cipher_specs) > 0)
+		{
+		uint32_t cipher_spec;
+		if (!CBS_get_u24(&cipher_specs, &cipher_spec))
+			{
+			CBB_cleanup(&client_hello);
+			OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, SSL_R_DECODE_ERROR);
+			goto err;
+			}
+
+		/* Skip SSLv2 ciphers. */
+		if ((cipher_spec & 0xff0000) != 0)
+			continue;
+		if (!CBB_add_u16(&cipher_suites, cipher_spec))
+			{
+			CBB_cleanup(&client_hello);
+			OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, ERR_R_INTERNAL_ERROR);
+			goto err;
+			}
+		}
+
+	/* Add the null compression scheme and finish. */
+	if (!CBB_add_u8(&hello_body, 1) ||
+		!CBB_add_u8(&hello_body, 0) ||
+		!CBB_finish(&client_hello, NULL, &len))
+		{
+		CBB_cleanup(&client_hello);
+		OPENSSL_PUT_ERROR(SSL, ssl23_get_v2_client_hello, ERR_R_INTERNAL_ERROR);
+		goto err;
+		}
+
+	/* Mark the message for "re"-use by the version-specific
+	 * method. */
+	s->s3->tmp.reuse_message = 1;
+	s->s3->tmp.message_type = SSL3_MT_CLIENT_HELLO;
+	/* The handshake message header is 4 bytes. */
+	s->s3->tmp.message_size = len - 4;
+
+	/* Reset the record layer for SSL3. */
+	assert(s->rstate == SSL_ST_READ_HEADER);
+	s->packet_length = 0;
+	s->s3->rbuf.left = 0;
+	s->s3->rbuf.offset = 0;
+
+	s->state = SSL23_ST_SR_SWITCH_VERSION;
+	return 1;
+err:
+	return -1;
 	}
diff --git a/ssl/ssl_error.c b/ssl/ssl_error.c
index c989c84..7a7a3ad 100644
--- a/ssl/ssl_error.c
+++ b/ssl/ssl_error.c
@@ -94,6 +94,7 @@
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_connect, 0), "ssl23_connect"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_get_client_hello, 0), "ssl23_get_client_hello"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_get_server_hello, 0), "ssl23_get_server_hello"},
+  {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_get_v2_client_hello, 0), "ssl23_get_v2_client_hello"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_peek, 0), "ssl23_peek"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_read, 0), "ssl23_read"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl23_write, 0), "ssl23_write"},
diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c
index 9a45b45..361a2fa 100644
--- a/ssl/ssl_stat.c
+++ b/ssl/ssl_stat.c
@@ -179,8 +179,9 @@
 case SSL23_ST_CR_SRVR_HELLO_A:	str="SSLv2/v3 read server hello A"; break;
 case SSL23_ST_CR_SRVR_HELLO_B:	str="SSLv2/v3 read server hello B"; break;
 /* server */
-case SSL23_ST_SR_CLNT_HELLO_A:	str="SSLv2/v3 read client hello A"; break;
-case SSL23_ST_SR_CLNT_HELLO_B:	str="SSLv2/v3 read client hello B"; break;
+case SSL23_ST_SR_CLNT_HELLO:	str="SSLv2/v3 read client hello"; break;
+case SSL23_ST_SR_V2_CLNT_HELLO:	str="SSLv2/v3 read v2 client hello"; break;
+case SSL23_ST_SR_SWITCH_VERSION: str="SSLv2/v3 switch version"; break;
 #endif
 
 /* DTLS */
@@ -292,8 +293,9 @@
 case SSL23_ST_CR_SRVR_HELLO_A:			str="23RSHA"; break;
 case SSL23_ST_CR_SRVR_HELLO_B:			str="23RSHA"; break;
 /* server */
-case SSL23_ST_SR_CLNT_HELLO_A:			str="23RCHA"; break;
-case SSL23_ST_SR_CLNT_HELLO_B:			str="23RCHB"; break;
+case SSL23_ST_SR_CLNT_HELLO:			str="23RCH_"; break;
+case SSL23_ST_SR_V2_CLNT_HELLO:			str="23R2CH"; break;
+case SSL23_ST_SR_SWITCH_VERSION:		str="23RSW_"; break;
 #endif
 /* DTLS */
 case DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A: str="DRCHVA"; break;
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 8859db8..ed60a3b 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -410,6 +410,10 @@
 	// RenewTicketOnResume causes the server to renew the session ticket and
 	// send a NewSessionTicket message during an abbreviated handshake.
 	RenewTicketOnResume bool
+
+	// SendClientVersion, if non-zero, causes the client to send a different
+	// TLS version in the ClientHello than the maximum supported version.
+	SendClientVersion uint16
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 9d2c1fa..fa84074 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -52,6 +52,10 @@
 		duplicateExtension:  c.config.Bugs.DuplicateExtension,
 	}
 
+	if c.config.Bugs.SendClientVersion != 0 {
+		hello.vers = c.config.Bugs.SendClientVersion
+	}
+
 	possibleCipherSuites := c.config.cipherSuites()
 	hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
 
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 2addab1..57bcf87 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -85,6 +85,9 @@
 	// resumeSession controls whether a second connection should be tested
 	// which resumes the first session.
 	resumeSession bool
+	// sendPrefix sends a prefix on the socket before actually performing a
+	// handshake.
+	sendPrefix string
 	// flags, if not empty, contains a list of command-line flags that will
 	// be passed to the shim program.
 	flags []string
@@ -382,9 +385,79 @@
 		shouldFail:    true,
 		expectedError: ":RECORD_TOO_SMALL:",
 	},
+	{
+		testType: serverTest,
+		name:     "MinorVersionTolerance",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendClientVersion: 0x03ff,
+			},
+		},
+		expectedVersion: VersionTLS12,
+	},
+	{
+		testType: serverTest,
+		name:     "MajorVersionTolerance",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendClientVersion: 0x0400,
+			},
+		},
+		expectedVersion: VersionTLS12,
+	},
+	{
+		testType: serverTest,
+		name:     "VersionTooLow",
+		config: Config{
+			Bugs: ProtocolBugs{
+				SendClientVersion: 0x0200,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":UNSUPPORTED_PROTOCOL:",
+	},
+	{
+		testType:      serverTest,
+		name:          "HttpGET",
+		sendPrefix:    "GET / HTTP/1.0\n",
+		shouldFail:    true,
+		expectedError: ":HTTP_REQUEST:",
+	},
+	{
+		testType:      serverTest,
+		name:          "HttpPOST",
+		sendPrefix:    "POST / HTTP/1.0\n",
+		shouldFail:    true,
+		expectedError: ":HTTP_REQUEST:",
+	},
+	{
+		testType:      serverTest,
+		name:          "HttpHEAD",
+		sendPrefix:    "HEAD / HTTP/1.0\n",
+		shouldFail:    true,
+		expectedError: ":HTTP_REQUEST:",
+	},
+	{
+		testType:      serverTest,
+		name:          "HttpPUT",
+		sendPrefix:    "PUT / HTTP/1.0\n",
+		shouldFail:    true,
+		expectedError: ":HTTP_REQUEST:",
+	},
+	{
+		testType:      serverTest,
+		name:          "HttpCONNECT",
+		sendPrefix:    "CONNECT www.google.com:443 HTTP/1.0\n",
+		shouldFail:    true,
+		expectedError: ":HTTPS_PROXY_REQUEST:",
+	},
 }
 
 func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
+	if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
+		return err
+	}
+
 	var tlsConn *Conn
 	if test.testType == clientTest {
 		tlsConn = Server(conn, config)
@@ -991,7 +1064,7 @@
 	// Client sends a V2ClientHello.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
-		name:     "SendV2ClientHello",
+		name:     "SendV2ClientHello" + suffix,
 		config: Config{
 			// Choose a cipher suite that does not involve
 			// elliptic curves, so no extensions are