Fix DTLS unprocessed records bug
authorMatt Caswell <matt@openssl.org>
Tue, 5 Jul 2016 10:46:26 +0000 (11:46 +0100)
committerMatt Caswell <matt@openssl.org>
Fri, 19 Aug 2016 12:50:27 +0000 (13:50 +0100)
During a DTLS handshake we may get records destined for the next epoch
arrive before we have processed the CCS. In that case we can't decrypt or
verify the record yet, so we buffer it for later use. When we do receive
the CCS we work through the queue of unprocessed records and process them.

Unfortunately the act of processing wipes out any existing packet data
that we were still working through. This includes any records from the new
epoch that were in the same packet as the CCS. We should only process the
buffered records if we've not got any data left.

Reviewed-by: Richard Levitte <levitte@openssl.org>
ssl/d1_pkt.c

index fe30ec7d004235d74145d38af12cfde3fb0e2ffd..1fb119da512d1919ef5c10889f4327193e0acbec 100644 (file)
@@ -319,6 +319,7 @@ static int dtls1_retrieve_buffered_record(SSL *s, record_pqueue *queue)
 static int dtls1_process_buffered_records(SSL *s)
 {
     pitem *item;
 static int dtls1_process_buffered_records(SSL *s)
 {
     pitem *item;
+    SSL3_BUFFER *rb;
 
     item = pqueue_peek(s->d1->unprocessed_rcds.q);
     if (item) {
 
     item = pqueue_peek(s->d1->unprocessed_rcds.q);
     if (item) {
@@ -326,6 +327,19 @@ static int dtls1_process_buffered_records(SSL *s)
         if (s->d1->unprocessed_rcds.epoch != s->d1->r_epoch)
             return (1);         /* Nothing to do. */
 
         if (s->d1->unprocessed_rcds.epoch != s->d1->r_epoch)
             return (1);         /* Nothing to do. */
 
+        rb = &s->s3->rbuf;
+
+        if (rb->left > 0) {
+            /*
+             * We've still got data from the current packet to read. There could
+             * be a record from the new epoch in it - so don't overwrite it
+             * with the unprocessed records yet (we'll do it when we've
+             * finished reading the current packet).
+             */
+            return 1;
+        }
+
+
         /* Process all the records. */
         while (pqueue_peek(s->d1->unprocessed_rcds.q)) {
             dtls1_get_unprocessed_record(s);
         /* Process all the records. */
         while (pqueue_peek(s->d1->unprocessed_rcds.q)) {
             dtls1_get_unprocessed_record(s);
@@ -581,6 +595,7 @@ int dtls1_get_record(SSL *s)
 
     rr = &(s->s3->rrec);
 
 
     rr = &(s->s3->rrec);
 
+ again:
     /*
      * The epoch may have changed.  If so, process all the pending records.
      * This is a non-blocking operation.
     /*
      * The epoch may have changed.  If so, process all the pending records.
      * This is a non-blocking operation.
@@ -593,7 +608,6 @@ int dtls1_get_record(SSL *s)
         return 1;
 
     /* get something from the wire */
         return 1;
 
     /* get something from the wire */
- again:
     /* check if we have the header */
     if ((s->rstate != SSL_ST_READ_BODY) ||
         (s->packet_length < DTLS1_RT_HEADER_LENGTH)) {
     /* check if we have the header */
     if ((s->rstate != SSL_ST_READ_BODY) ||
         (s->packet_length < DTLS1_RT_HEADER_LENGTH)) {
@@ -1830,8 +1844,13 @@ static DTLS1_BITMAP *dtls1_get_bitmap(SSL *s, SSL3_RECORD *rr,
     if (rr->epoch == s->d1->r_epoch)
         return &s->d1->bitmap;
 
     if (rr->epoch == s->d1->r_epoch)
         return &s->d1->bitmap;
 
-    /* Only HM and ALERT messages can be from the next epoch */
+    /*
+     * Only HM and ALERT messages can be from the next epoch and only if we
+     * have already processed all of the unprocessed records from the last
+     * epoch
+     */
     else if (rr->epoch == (unsigned long)(s->d1->r_epoch + 1) &&
     else if (rr->epoch == (unsigned long)(s->d1->r_epoch + 1) &&
+             s->d1->unprocessed_rcds.epoch != s->d1->r_epoch &&
              (rr->type == SSL3_RT_HANDSHAKE || rr->type == SSL3_RT_ALERT)) {
         *is_next_epoch = 1;
         return &s->d1->next_bitmap;
              (rr->type == SSL3_RT_HANDSHAKE || rr->type == SSL3_RT_ALERT)) {
         *is_next_epoch = 1;
         return &s->d1->next_bitmap;