Correctly handle a retransmitted ClientHello
authorMatt Caswell <matt@openssl.org>
Thu, 23 Jun 2022 10:39:38 +0000 (11:39 +0100)
committerHugo Landau <hlandau@openssl.org>
Thu, 22 Sep 2022 11:24:02 +0000 (12:24 +0100)
If we receive a ClientHello and send back a HelloVerifyRequest, we need
to be able to handle the scenario where the HelloVerifyRequest gets lost
and we receive another ClientHello with the message sequence number set to
0.

Fixes #18635

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18654)

(cherry picked from commit 81926c91567cd5d11eec38b9980438f45b276d72)

ssl/statem/statem_dtls.c

index 2838d51bdb25d141a9b59fec5c967c13476be1bc..788d0eff656ba3f25b921b287464f6e5122d51c8 100644 (file)
@@ -491,23 +491,64 @@ static int dtls1_retrieve_buffered_fragment(SSL *s, size_t *len)
      * (2) update s->init_num
      */
     pitem *item;
+    piterator iter;
     hm_fragment *frag;
     int ret;
+    int chretran = 0;
 
+    iter = pqueue_iterator(s->d1->buffered_messages);
     do {
-        item = pqueue_peek(s->d1->buffered_messages);
+        item = pqueue_next(&iter);
         if (item == NULL)
             return 0;
 
         frag = (hm_fragment *)item->data;
 
         if (frag->msg_header.seq < s->d1->handshake_read_seq) {
-            /* This is a stale message that has been buffered so clear it */
-            pqueue_pop(s->d1->buffered_messages);
-            dtls1_hm_fragment_free(frag);
-            pitem_free(item);
-            item = NULL;
-            frag = NULL;
+            pitem *next;
+            hm_fragment *nextfrag;
+
+            if (!s->server
+                    || frag->msg_header.seq != 0
+                    || s->d1->handshake_read_seq != 1
+                    || s->statem.hand_state != DTLS_ST_SW_HELLO_VERIFY_REQUEST) {
+                /*
+                 * This is a stale message that has been buffered so clear it.
+                 * It is safe to pop this message from the queue even though
+                 * we have an active iterator
+                 */
+                pqueue_pop(s->d1->buffered_messages);
+                dtls1_hm_fragment_free(frag);
+                pitem_free(item);
+                item = NULL;
+                frag = NULL;
+            } else {
+                /*
+                 * We have fragments for a ClientHello without a cookie,
+                 * even though we have sent a HelloVerifyRequest. It is possible
+                 * that the HelloVerifyRequest got lost and this is a
+                 * retransmission of the original ClientHello
+                 */
+                next = pqueue_next(&iter);
+                if (next != NULL) {
+                    nextfrag = (hm_fragment *)next->data;
+                    if (nextfrag->msg_header.seq == s->d1->handshake_read_seq) {
+                        /*
+                        * We have fragments for both a ClientHello without
+                        * cookie and one with. Ditch the one without.
+                        */
+                        pqueue_pop(s->d1->buffered_messages);
+                        dtls1_hm_fragment_free(frag);
+                        pitem_free(item);
+                        item = next;
+                        frag = nextfrag;
+                    } else {
+                        chretran = 1;
+                    }
+                } else {
+                    chretran = 1;
+                }
+            }
         }
     } while (item == NULL);
 
@@ -515,7 +556,7 @@ static int dtls1_retrieve_buffered_fragment(SSL *s, size_t *len)
     if (frag->reassembly != NULL)
         return 0;
 
-    if (s->d1->handshake_read_seq == frag->msg_header.seq) {
+    if (s->d1->handshake_read_seq == frag->msg_header.seq || chretran) {
         size_t frag_len = frag->msg_header.frag_len;
         pqueue_pop(s->d1->buffered_messages);
 
@@ -533,6 +574,16 @@ static int dtls1_retrieve_buffered_fragment(SSL *s, size_t *len)
         pitem_free(item);
 
         if (ret) {
+            if (chretran) {
+                /*
+                 * We got a new ClientHello with a message sequence of 0.
+                 * Reset the read/write sequences back to the beginning.
+                 * We process it like this is the first time we've seen a
+                 * ClientHello from the client.
+                 */
+                s->d1->handshake_read_seq = 0;
+                s->d1->next_handshake_write_seq = 0;
+            }
             *len = frag_len;
             return 1;
         }
@@ -759,6 +810,7 @@ static int dtls_get_reassembled_message(SSL *s, int *errtype, size_t *len)
     int i, ret, recvd_type;
     struct hm_header_st msg_hdr;
     size_t readbytes;
+    int chretran = 0;
 
     *errtype = 0;
 
@@ -828,8 +880,20 @@ static int dtls_get_reassembled_message(SSL *s, int *errtype, size_t *len)
      * although we're still expecting seq 0 (ClientHello)
      */
     if (msg_hdr.seq != s->d1->handshake_read_seq) {
-        *errtype = dtls1_process_out_of_seq_message(s, &msg_hdr);
-        return 0;
+        if (!s->server
+                || msg_hdr.seq != 0
+                || s->d1->handshake_read_seq != 1
+                || wire[0] != SSL3_MT_CLIENT_HELLO
+                || s->statem.hand_state != DTLS_ST_SW_HELLO_VERIFY_REQUEST) {
+            *errtype = dtls1_process_out_of_seq_message(s, &msg_hdr);
+            return 0;
+        }
+        /*
+         * We received a ClientHello and sent back a HelloVerifyRequest. We
+         * now seem to have received a retransmitted initial ClientHello. That
+         * is allowed (possibly our HelloVerifyRequest got lost).
+         */
+        chretran = 1;
     }
 
     if (frag_len && frag_len < mlen) {
@@ -895,6 +959,17 @@ static int dtls_get_reassembled_message(SSL *s, int *errtype, size_t *len)
         goto f_err;
     }
 
+    if (chretran) {
+        /*
+         * We got a new ClientHello with a message sequence of 0.
+         * Reset the read/write sequences back to the beginning.
+         * We process it like this is the first time we've seen a ClientHello
+         * from the client.
+         */
+        s->d1->handshake_read_seq = 0;
+        s->d1->next_handshake_write_seq = 0;
+    }
+
     /*
      * Note that s->init_num is *not* used as current offset in
      * s->init_buf->data, but as a counter summing up fragments' lengths: as