Get rid of the RSMBLY macros.
Turn them into static functions that take in an hm_fragment. It's not
immediately obvious that the frag_off/frag_len bounds checks and the msg_len
consistency check are critical to avoiding an out-of-bounds write. Better to
have dtls1_hm_fragment_mark also check internally.
Also rework the bitmask logic to be clearer and avoid a table.
Change-Id: Ica54e98f66295efb323e033cb6c67ab21e7d6cbc
Reviewed-on: https://boringssl-review.googlesource.com/3765
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index a591573..1fd7e2c 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -60,6 +60,7 @@
SSL,function,158,dtls1_get_hello_verify
SSL,function,159,dtls1_get_message
SSL,function,160,dtls1_get_message_fragment
+SSL,function,265,dtls1_hm_fragment_new
SSL,function,161,dtls1_preprocess_fragment
SSL,function,264,dtls1_process_fragment
SSL,function,162,dtls1_process_record
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 8c4f6d7..19ed07b 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2430,6 +2430,7 @@
#define SSL_F_tls1_setup_key_block 262
#define SSL_F_dtls1_get_buffered_message 263
#define SSL_F_dtls1_process_fragment 264
+#define SSL_F_dtls1_hm_fragment_new 265
#define SSL_R_APP_DATA_IN_HANDSHAKE 100
#define SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT 101
#define SSL_R_BAD_ALERT 102
diff --git a/ssl/d1_both.c b/ssl/d1_both.c
index 29f4780..a19ee3d 100644
--- a/ssl/d1_both.c
+++ b/ssl/d1_both.c
@@ -126,42 +126,6 @@
#include "ssl_locl.h"
-#define RSMBLY_BITMASK_SIZE(msg_len) (((msg_len) + 7) / 8)
-
-#define RSMBLY_BITMASK_MARK(bitmask, start, end) \
- { \
- if ((end) - (start) <= 8) { \
- long ii; \
- for (ii = (start); ii < (end); ii++) \
- bitmask[((ii) >> 3)] |= (1 << ((ii)&7)); \
- } else { \
- long ii; \
- bitmask[((start) >> 3)] |= bitmask_start_values[((start)&7)]; \
- for (ii = (((start) >> 3) + 1); ii < ((((end)-1)) >> 3); ii++) \
- bitmask[ii] = 0xff; \
- bitmask[(((end)-1) >> 3)] |= bitmask_end_values[((end)&7)]; \
- } \
- }
-
-#define RSMBLY_BITMASK_IS_COMPLETE(bitmask, msg_len, is_complete) \
- { \
- long ii; \
- assert((msg_len) > 0); \
- is_complete = 1; \
- if (bitmask[(((msg_len)-1) >> 3)] != bitmask_end_values[((msg_len)&7)]) \
- is_complete = 0; \
- if (is_complete) \
- for (ii = (((msg_len)-1) >> 3) - 1; ii >= 0; ii--) \
- if (bitmask[ii] != 0xff) { \
- is_complete = 0; \
- break; \
- } \
- }
-
-static const uint8_t bitmask_start_values[] = {0xff, 0xfe, 0xfc, 0xf8,
- 0xf0, 0xe0, 0xc0, 0x80};
-static const uint8_t bitmask_end_values[] = {0xff, 0x01, 0x03, 0x07,
- 0x0f, 0x1f, 0x3f, 0x7f};
/* TODO(davidben): 28 comes from the size of IP + UDP header. Is this reasonable
* for these values? Notably, why is kMinMTU a function of the transport
@@ -191,12 +155,14 @@
frag = (hm_fragment *)OPENSSL_malloc(sizeof(hm_fragment));
if (frag == NULL) {
+ OPENSSL_PUT_ERROR(SSL, dtls1_hm_fragment_new, ERR_R_MALLOC_FAILURE);
return NULL;
}
if (frag_len) {
buf = (uint8_t *)OPENSSL_malloc(frag_len);
if (buf == NULL) {
+ OPENSSL_PUT_ERROR(SSL, dtls1_hm_fragment_new, ERR_R_MALLOC_FAILURE);
OPENSSL_free(frag);
return NULL;
}
@@ -207,15 +173,21 @@
/* Initialize reassembly bitmask if necessary */
if (reassembly && frag_len > 0) {
- bitmask = (uint8_t *)OPENSSL_malloc(RSMBLY_BITMASK_SIZE(frag_len));
+ if (frag_len + 7 < frag_len) {
+ OPENSSL_PUT_ERROR(SSL, dtls1_hm_fragment_new, ERR_R_OVERFLOW);
+ return NULL;
+ }
+ size_t bitmask_len = (frag_len + 7) / 8;
+ bitmask = (uint8_t *)OPENSSL_malloc(bitmask_len);
if (bitmask == NULL) {
+ OPENSSL_PUT_ERROR(SSL, dtls1_hm_fragment_new, ERR_R_MALLOC_FAILURE);
if (buf != NULL) {
OPENSSL_free(buf);
}
OPENSSL_free(frag);
return NULL;
}
- memset(bitmask, 0, RSMBLY_BITMASK_SIZE(frag_len));
+ memset(bitmask, 0, bitmask_len);
}
frag->reassembly = bitmask;
@@ -233,6 +205,59 @@
OPENSSL_free(frag);
}
+#if !defined(inline)
+#define inline __inline
+#endif
+
+/* bit_range returns a |uint8_t| with bits |start|, inclusive, to |end|,
+ * exclusive, set. */
+static inline uint8_t bit_range(size_t start, size_t end) {
+ return (uint8_t)(~((1u << start) - 1) & ((1u << end) - 1));
+}
+
+/* dtls1_hm_fragment_mark marks bytes |start|, inclusive, to |end|, exclusive,
+ * as received in |frag|. If |frag| becomes complete, it clears
+ * |frag->reassembly|. The range must be within the bounds of |frag|'s message
+ * and |frag->reassembly| must not be NULL. */
+static void dtls1_hm_fragment_mark(hm_fragment *frag, size_t start,
+ size_t end) {
+ size_t i;
+ size_t msg_len = frag->msg_header.msg_len;
+
+ if (frag->reassembly == NULL || start > end || end > msg_len) {
+ assert(0);
+ return;
+ }
+ /* A zero-length message will never have a pending reassembly. */
+ assert(msg_len > 0);
+
+ if ((start >> 3) == (end >> 3)) {
+ frag->reassembly[start >> 3] |= bit_range(start & 7, end & 7);
+ } else {
+ frag->reassembly[start >> 3] |= bit_range(start & 7, 8);
+ for (i = (start >> 3) + 1; i < (end >> 3); i++) {
+ frag->reassembly[i] = 0xff;
+ }
+ if ((end & 7) != 0) {
+ frag->reassembly[end >> 3] |= bit_range(0, end & 7);
+ }
+ }
+
+ /* Check if the fragment is complete. */
+ for (i = 0; i < (msg_len >> 3); i++) {
+ if (frag->reassembly[i] != 0xff) {
+ return;
+ }
+ }
+ if ((msg_len & 7) != 0 &&
+ frag->reassembly[msg_len >> 3] != bit_range(0, msg_len & 7)) {
+ return;
+ }
+
+ OPENSSL_free(frag->reassembly);
+ frag->reassembly = NULL;
+}
+
/* send s->init_buf in records of type 'type' (SSL3_RT_HANDSHAKE or
* SSL3_RT_CHANGE_CIPHER_SPEC) */
int dtls1_do_write(SSL *s, int type) {
@@ -522,15 +547,8 @@
ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
return -1;
}
+ dtls1_hm_fragment_mark(frag, frag_off, frag_off + frag_len);
- /* Update whether the message is complete. */
- int is_complete;
- RSMBLY_BITMASK_MARK(frag->reassembly, frag_off, frag_off + frag_len);
- RSMBLY_BITMASK_IS_COMPLETE(frag->reassembly, msg_len, is_complete);
- if (is_complete) {
- OPENSSL_free(frag->reassembly);
- frag->reassembly = NULL;
- }
return 1;
}