# HG changeset patch
# Parent  c153bf56aa5677ec35ff3b1c8a34570eaf8909d7
MFSA 2016-35/CVE-2016-1950
(bmo#1245528)
Buffer overflow during parsing of ASN.1 structures

Backport of changeset b9a31471759d751a56bf261b24c138c8f5d3925f

diff --git a/lib/util/secasn1d.c b/lib/util/secasn1d.c
--- a/lib/util/secasn1d.c
+++ b/lib/util/secasn1d.c
@@ -9,16 +9,18 @@
 
 /* #define DEBUG_ASN1D_STATES 1 */
 
 #ifdef DEBUG_ASN1D_STATES
 #include <stdio.h>
 #define PR_Assert sec_asn1d_Assert
 #endif
 
+#include <limits.h>
+
 #include "secasn1.h"
 #include "secerr.h"
 
 typedef enum {
     beforeIdentifier,
     duringIdentifier,
     afterIdentifier,
     beforeLength,
@@ -1588,28 +1590,63 @@ sec_asn1d_parse_leaf (sec_asn1d_state *s
 
     if (state->pending < len)
 	len = state->pending;
 
     bufLen = len;
 
     item = (SECItem *)(state->dest);
     if (item != NULL && item->data != NULL) {
+	unsigned long offset;
 	/* Strip leading zeroes when target is unsigned integer */
 	if (state->underlying_kind == SEC_ASN1_INTEGER && /* INTEGER   */
 	    item->len == 0 &&                             /* MSB       */
 	    item->type == siUnsignedInteger)              /* unsigned  */
 	{
 	    while (len > 1 && buf[0] == 0) {              /* leading 0 */
 		buf++;
 		len--;
 	    }
 	}
-	PORT_Memcpy (item->data + item->len, buf, len);
-	item->len += len;
+        offset = item->len;
+        if (state->underlying_kind == SEC_ASN1_BIT_STRING) {
+            // The previous bit string must have no unused bits.
+            if (item->len & 0x7) {
+                PORT_SetError (SEC_ERROR_BAD_DER);
+                state->top->status = decodeError;
+                return 0;
+            }
+            // If this is a bit string, the length is bits, not bytes.
+            offset = item->len >> 3;
+        }
+        if (state->underlying_kind == SEC_ASN1_BIT_STRING) {
+            unsigned long len_in_bits;
+            // Protect against overflow during the bytes-to-bits conversion.
+            if (len >= (ULONG_MAX >> 3) + 1) {
+                PORT_SetError (SEC_ERROR_BAD_DER);
+                state->top->status = decodeError;
+                return 0;
+            }
+            len_in_bits = (len << 3) - state->bit_string_unused_bits;
+            // Protect against overflow when computing the total length in bits.
+            if (UINT_MAX - item->len < len_in_bits) {
+                PORT_SetError (SEC_ERROR_BAD_DER);
+                state->top->status = decodeError;
+                return 0;
+            }
+            item->len += len_in_bits;
+        } else {
+            if (UINT_MAX - item->len < len) {
+                PORT_SetError (SEC_ERROR_BAD_DER);
+                state->top->status = decodeError;
+                return 0;
+            }
+            item->len += len;
+        }
+        PORT_Memcpy (item->data + offset, buf, len);
     }
     state->pending -= bufLen;
     if (state->pending == 0)
 	state->place = beforeEndOfContents;
 
     return bufLen;
 }
 
@@ -1666,24 +1703,16 @@ sec_asn1d_parse_more_bit_string (sec_asn
 	} else {
 	    /* An empty bit string with no unused bits is OK. */
 	    state->place = beforeEndOfContents;
 	}
 	return 0;
     }
 
     len = sec_asn1d_parse_leaf (state, buf, len);
-    if (state->place == beforeEndOfContents && state->dest != NULL) {
-	SECItem *item;
-
-	item = (SECItem *)(state->dest);
-	if (item->len)
-	    item->len = (item->len << 3) - state->bit_string_unused_bits;
-    }
-
     return len;
 }
 
 
 /*
  * XXX All callers should be looking at return value to detect
  * out-of-memory errors (and stop!).
  */
@@ -2203,17 +2232,17 @@ sec_asn1d_concat_substrings (sec_asn1d_s
 			? PR_TRUE : PR_FALSE;
 
 	substring = state->subitems_head;
 	while (substring != NULL) {
 	    /*
 	     * All bit-string substrings except the last one should be
 	     * a clean multiple of 8 bits.
 	     */
-	    if (is_bit_string && (substring->next == NULL)
+	    if (is_bit_string && (substring->next != NULL)
 			      && (substring->len & 0x7)) {
 		PORT_SetError (SEC_ERROR_BAD_DER);
 		state->top->status = decodeError;
 		return;
 	    }
 	    item_len += substring->len;
 	    substring = substring->next;
 	}
