-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Incremental TLS handshake defragmentation #10011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2e5a7ea
9bdc8aa
0702772
7a17696
235eae9
e85ece6
90a9593
e4a3fc2
0851ec9
15c072f
afb254c
b8f1e4b
dab1cb5
e34ec86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3049,64 +3049,122 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) | |
| } | ||
| } else | ||
| #endif /* MBEDTLS_SSL_PROTO_DTLS */ | ||
| if (ssl->in_hsfraglen <= ssl->in_hslen) { | ||
| int ret; | ||
| const size_t hs_remain = ssl->in_hslen - ssl->in_hsfraglen; | ||
| MBEDTLS_SSL_DEBUG_MSG(3, | ||
| ("handshake fragment: %" MBEDTLS_PRINTF_SIZET | ||
| ", %" MBEDTLS_PRINTF_SIZET | ||
| "..%" MBEDTLS_PRINTF_SIZET | ||
| " of %" MBEDTLS_PRINTF_SIZET, | ||
| ssl->in_msglen, | ||
| ssl->in_hsfraglen, | ||
| ssl->in_hsfraglen + ssl->in_msglen, | ||
| ssl->in_hslen)); | ||
| if (ssl->in_msglen < hs_remain) { | ||
| ssl->in_hsfraglen += ssl->in_msglen; | ||
| ssl->in_hdr = ssl->in_msg + ssl->in_msglen; | ||
| ssl->in_msglen = 0; | ||
| mbedtls_ssl_update_in_pointers(ssl); | ||
| { | ||
| unsigned char *const reassembled_record_start = | ||
| ssl->in_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN; | ||
| unsigned char *const payload_start = | ||
| reassembled_record_start + mbedtls_ssl_in_hdr_len(ssl); | ||
| unsigned char *payload_end = payload_start + ssl->in_hsfraglen; | ||
|
|
||
| if (ssl->in_hsfraglen != 0) { | ||
| /* We already had a handshake fragment. Prepare to append | ||
| * to the initial segment. */ | ||
| MBEDTLS_SSL_DEBUG_MSG(3, | ||
| ("subsequent handshake fragment: %" MBEDTLS_PRINTF_SIZET | ||
| ", %" MBEDTLS_PRINTF_SIZET | ||
| "..%" MBEDTLS_PRINTF_SIZET | ||
| " of %" MBEDTLS_PRINTF_SIZET, | ||
| ssl->in_msglen, | ||
| ssl->in_hsfraglen, | ||
| ssl->in_hsfraglen + ssl->in_msglen, | ||
| ssl->in_hslen)); | ||
|
|
||
| const size_t hs_remain = ssl->in_hslen - ssl->in_hsfraglen; | ||
| if (ssl->in_msglen > hs_remain) { | ||
| MBEDTLS_SSL_DEBUG_MSG(1, ("Handshake fragment too long: %" | ||
| MBEDTLS_PRINTF_SIZET " but only %" | ||
| MBEDTLS_PRINTF_SIZET " of %" | ||
| MBEDTLS_PRINTF_SIZET " remain", | ||
| ssl->in_msglen, | ||
| hs_remain, | ||
| ssl->in_hslen)); | ||
| return MBEDTLS_ERR_SSL_INVALID_RECORD; | ||
| } | ||
| } else if (ssl->in_msglen == ssl->in_hslen) { | ||
| /* This is the sole fragment. */ | ||
| /* Emit a log message in the same format as when there are | ||
| * multiple fragments, for ease of matching. */ | ||
| MBEDTLS_SSL_DEBUG_MSG(3, | ||
| ("sole handshake fragment: %" MBEDTLS_PRINTF_SIZET | ||
| ", %" MBEDTLS_PRINTF_SIZET | ||
| "..%" MBEDTLS_PRINTF_SIZET | ||
| " of %" MBEDTLS_PRINTF_SIZET, | ||
| ssl->in_msglen, | ||
| ssl->in_hsfraglen, | ||
| ssl->in_hsfraglen + ssl->in_msglen, | ||
| ssl->in_hslen)); | ||
| } else { | ||
| /* This is the first fragment of many. */ | ||
| MBEDTLS_SSL_DEBUG_MSG(3, | ||
| ("initial handshake fragment: %" MBEDTLS_PRINTF_SIZET | ||
| ", %" MBEDTLS_PRINTF_SIZET | ||
| "..%" MBEDTLS_PRINTF_SIZET | ||
| " of %" MBEDTLS_PRINTF_SIZET, | ||
| ssl->in_msglen, | ||
| ssl->in_hsfraglen, | ||
| ssl->in_hsfraglen + ssl->in_msglen, | ||
| ssl->in_hslen)); | ||
| } | ||
|
|
||
| /* Move the received handshake fragment to have the whole message | ||
| * (at least the part received so far) in a single segment at a | ||
| * known offset in the input buffer. | ||
| * - When receiving a non-initial handshake fragment, append it to | ||
| * the initial segment. | ||
| * - Even the initial handshake fragment is moved, if it was | ||
mpg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * encrypted with an explicit IV: decryption leaves the payload | ||
| * after the explicit IV, but here we move it to start where the | ||
| * IV was. | ||
| */ | ||
| #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) | ||
| size_t const in_buf_len = ssl->in_buf_len; | ||
| #else | ||
| size_t const in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; | ||
| #endif | ||
| if (payload_end + ssl->in_hsfraglen > ssl->in_buf + in_buf_len) { | ||
|
||
| MBEDTLS_SSL_DEBUG_MSG(1, | ||
| ("Shouldn't happen: no room to move handshake fragment %" | ||
| MBEDTLS_PRINTF_SIZET " from %p to %p (buf=%p len=%" | ||
| MBEDTLS_PRINTF_SIZET ")", | ||
| ssl->in_msglen, | ||
| ssl->in_msg, payload_end, | ||
| ssl->in_buf, in_buf_len)); | ||
| return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; | ||
| } | ||
| memmove(payload_end, ssl->in_msg, ssl->in_msglen); | ||
|
|
||
| ssl->in_hsfraglen += ssl->in_msglen; | ||
minosgalanakis marked this conversation as resolved.
Show resolved
Hide resolved
mpg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| payload_end += ssl->in_msglen; | ||
|
|
||
| if (ssl->in_hsfraglen < ssl->in_hslen) { | ||
| MBEDTLS_SSL_DEBUG_MSG(3, ("Prepare: waiting for more handshake fragments %" | ||
| MBEDTLS_PRINTF_SIZET "/%" MBEDTLS_PRINTF_SIZET, | ||
| MBEDTLS_PRINTF_SIZET "/%" | ||
| MBEDTLS_PRINTF_SIZET, | ||
| ssl->in_hsfraglen, ssl->in_hslen)); | ||
| return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING; | ||
| } | ||
| if (ssl->in_hsfraglen > 0) { | ||
| /* | ||
| * At in_first_hdr we have a sequence of records that cover the next handshake | ||
| * record, each with its own record header that we need to remove. | ||
| * Note that the reassembled record size may not equal the size of the message, | ||
| * there may be more messages after it, complete or partial. | ||
| */ | ||
| unsigned char *in_first_hdr = ssl->in_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN; | ||
| unsigned char *p = in_first_hdr, *q = NULL; | ||
| size_t merged_rec_len = 0; | ||
| do { | ||
| mbedtls_record rec; | ||
| ret = ssl_parse_record_header(ssl, p, mbedtls_ssl_in_hdr_len(ssl), &rec); | ||
| if (ret != 0) { | ||
| return ret; | ||
| } | ||
| merged_rec_len += rec.data_len; | ||
| p = rec.buf + rec.buf_len; | ||
| if (q != NULL) { | ||
| memmove(q, rec.buf + rec.data_offset, rec.data_len); | ||
| q += rec.data_len; | ||
| } else { | ||
| q = p; | ||
| } | ||
| } while (merged_rec_len < ssl->in_hslen); | ||
| ssl->in_hdr = in_first_hdr; | ||
| ssl->in_hdr = payload_end; | ||
| ssl->in_msglen = 0; | ||
| mbedtls_ssl_update_in_pointers(ssl); | ||
| ssl->in_msglen = merged_rec_len; | ||
| /* Adjust message length. */ | ||
| MBEDTLS_PUT_UINT16_BE(merged_rec_len, ssl->in_len, 0); | ||
| return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING; | ||
| } else { | ||
| ssl->in_msglen = ssl->in_hsfraglen; | ||
| ssl->in_hsfraglen = 0; | ||
| ssl->in_hdr = reassembled_record_start; | ||
| mbedtls_ssl_update_in_pointers(ssl); | ||
|
|
||
| /* Update the record length in the fully reassembled record */ | ||
| if (ssl->in_msglen > 0xffff) { | ||
mpg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| MBEDTLS_SSL_DEBUG_MSG(1, | ||
| ("Shouldn't happen: in_msglen=%" | ||
| MBEDTLS_PRINTF_SIZET " > 0xffff", | ||
| ssl->in_msglen)); | ||
| return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; | ||
| } | ||
| MBEDTLS_PUT_UINT16_BE(ssl->in_msglen, ssl->in_len, 0); | ||
|
|
||
| MBEDTLS_SSL_DEBUG_BUF(4, "reassembled record", | ||
| ssl->in_hdr, mbedtls_ssl_in_hdr_len(ssl) + merged_rec_len); | ||
| ssl->in_hdr, | ||
| mbedtls_ssl_in_hdr_len(ssl) + ssl->in_msglen); | ||
| } | ||
| } else { | ||
| return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; | ||
| } | ||
|
|
||
| return 0; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC we're being stricter than the protocol here: this condition means that there's more data in the record after the rest of this handshake message, presumably the (beginning of) the next handshake message, which is allowed by the standard unless I'm mistaken.
I don't have an issue with rejecting it anyway, but I'd like a comment explaining that we are rejecting something the standard allows and why we're doing that. Also, that's a limitation we should document. And I find the current debug message a bit confusing.
(Unless I'm mistaken about what's happening here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
I added this check because I believed that the old code silently ignored subsequent handshake messages if there is more than one in the same record. However, revisiting this, I'm not sure that was the case: here we do nothing, but
ssl_consume_current_messagehandles it. So now I'm worried that I've broken some existing untested behavior. But if I remove the check here, I'm worried that the defragmentation code may be creating an unexpected state. I'll keep digging today.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leveraging #9976, I've crafted a test case with ServerHello and ServerCertificate in the same message, and that test case passes before incremental defragmentation. So this check removes functionality, and I'm going to remove it.