Attaching simpler (in C) sample to reproduce the issue. 1. build the sample 2. run in console 3. it will first negotiate DTLS between server and client (both in the same process) 4. then it will try to renegotiate (immediately after negotiation finished) 5. client's flight containing Certificate, Key Exchange, Certificate Verify, Change Cipher Spec and Finished is dropped 6. then we wait for 1 second timeout and client tries to send the flight we failed to send at the previous step
Regards, Dmitry On Wed, Dec 18, 2013 at 9:12 PM, Dmitry Sobinov via RT <r...@openssl.org>wrote: > Attaching slightly modified sample which reproduces the problem (previous > one did not work sometimes). > > Can be built as > g++ -o dtlstest main.cpp -std=c++11 -lssl -lcrypto -lpthread -g > > > On Wed, Dec 18, 2013 at 3:06 PM, Dmitry Sobinov via RT <r...@openssl.org > >wrote: > > > Got some more info on this bug. It's a memory use after free. > > > > There's a problem with ssl_st::write_hash. It's cached > > in dtls1_buffer_message() function for each handshake message and got > freed > > and replaced by new hash context when forming Change Cipher Spec message > > (in ssl_replace_hash(), see stack trace below). So, when we want to > resend > > lost packet, old (already freed) hash context is used to create MACs for > > messages we want to resend, leading to crash on Win32 and undefined > > behavior on Linux. > > > > It's not a problem for initial handshake, because s->write_hash is zero > and > > nothing gets destroyed when forming Change Cipher Spec. > > > > Any ideas how to workaround/fix this are appreciated. > > > > Here is the report from clang AddressSanitizer: > > > > ==32258==ERROR: AddressSanitizer: heap-use-after-free on address > > 0x60400000b890 at pc 0xa541d0 bp 0x7f9225188c50 sp 0x7f9225188c48 > > READ of size 8 at 0x60400000b890 thread T1 > > #0 0xa541cf in EVP_MD_CTX_md evp_lib.c:285 > > #1 0x66e790 in dtls1_do_write d1_both.c:275 > > #2 0x682231 in dtls1_retransmit_message d1_both.c:1290 > > #3 0x67fe96 in dtls1_retransmit_buffered_messages d1_both.c:1145 > > #4 0x64f934 in dtls1_handle_timeout d1_lib.c:451 > > #5 0x65b6d4 in dtls1_read_bytes d1_pkt.c:832 > > #6 0x67640a in dtls1_get_message_fragment d1_both.c:789 > > #7 0x674459 in dtls1_get_message d1_both.c:437 > > #8 0x79835b in ssl3_get_new_session_ticket s3_clnt.c:2040 > > #9 0x63a5c1 in dtls1_connect d1_clnt.c:641 > > #10 0x6bef29 in SSL_do_handshake ssl_lib.c:2564 > > #11 0x4e11aa in _ZN17DtlsSrtpTransport18handshakeIterationEv > > test.cpp:348 > > #12 0x5070b2 in _ZN17DtlsSrtpTransport19receiveTimerExpiredEv > > test.cpp:423 > > #13 0x505128 in operator() test.cpp:392 > > #14 0x504951 in > > > > > _ZNSt3__18__invokeIRZN17DtlsSrtpTransport18handshakeIterationEvEUlvE_JEEEDTclclsr3std3__1E7forwardIT_Efp_Espclsr3std3__1E7forwardIT0_Efp0_EEEOS4_DpOS5_ > > __functional_base:341 > > #15 0x6069e9 in _ZNKSt3__18functionIFvvEEclEv functional:1436 > > #16 0x603c64 in _ZN15AsyncDispatcher3runEv test.cpp:170 > > #17 0x603928 in operator() test.cpp:88 > > #18 0x6021c5 in > > > > > _ZNSt3__17forwardIZN15AsyncDispatcherC1EvEUlvE_EEOT_RNS_16remove_referenceIS3_E4typeE > > type_traits:1341 > > #19 0x460083 in _ZN6__asan10AsanThread11ThreadStartEm ??:? > > #20 0x7f922847e0a1 in start_thread pthread_create.c:? > > #21 0x7f922788b49c in __clone ??:? > > 0x60400000b890 is located 0 bytes inside of 48-byte region > > [0x60400000b890,0x60400000b8c0) > > freed by thread T1 here: > > #0 0x459a04 in __interceptor_free ??:? > > #1 0x897bdf in CRYPTO_free mem.c:397 > > #2 0x9f97cf in EVP_MD_CTX_destroy digest.c:370 > > #3 0x693983 in ssl_clear_hash_ctx ssl_lib.c:3244 > > #4 0x6ca125 in ssl_replace_hash ssl_lib.c:3236 > > #5 0x83e213 in tls1_change_cipher_state t1_enc.c:425 > > #6 0x639971 in dtls1_connect d1_clnt.c:560 > > #7 0x6bef29 in SSL_do_handshake ssl_lib.c:2564 > > #8 0x4e11aa in _ZN17DtlsSrtpTransport18handshakeIterationEv > > test.cpp:348 > > #9 0x4d7d98 in operator() test.cpp:219 > > #10 0x4d52c1 in > > > > > _ZNSt3__18__invokeIRZN17DtlsSrtpTransport18handleIncomingDataERKNS_6vectorIhNS_9allocatorIhEEEEEUlvE_JEEEDTclclsr3std3__1E7forwardIT_Efp_Espclsr3std3__1E7forwardIT0_Efp0_EEEOSA_DpOSB_ > > __functional_base:341 > > #11 0x6069e9 in _ZNKSt3__18functionIFvvEEclEv functional:1436 > > #12 0x603c64 in _ZN15AsyncDispatcher3runEv test.cpp:170 > > #13 0x603928 in operator() test.cpp:88 > > #14 0x6021c5 in > > > > > _ZNSt3__17forwardIZN15AsyncDispatcherC1EvEUlvE_EEOT_RNS_16remove_referenceIS3_E4typeE > > type_traits:1341 > > #15 0x460083 in _ZN6__asan10AsanThread11ThreadStartEm ??:? > > previously allocated by thread T1 here: > > #0 0x459ae4 in __interceptor_malloc ??:? > > #1 0x89228c in default_malloc_ex mem.c:79 > > #2 0x895a5c in CRYPTO_malloc mem.c:308 > > #3 0x9f4515 in EVP_MD_CTX_create digest.c:131 > > #4 0x6ca12a in ssl_replace_hash ssl_lib.c:3237 > > #5 0x83e213 in tls1_change_cipher_state t1_enc.c:425 > > #6 0x639971 in dtls1_connect d1_clnt.c:560 > > #7 0x6bef29 in SSL_do_handshake ssl_lib.c:2564 > > #8 0x4e11aa in _ZN17DtlsSrtpTransport18handshakeIterationEv > > test.cpp:348 > > #9 0x4d7d98 in operator() test.cpp:219 > > #10 0x4d52c1 in > > > > > _ZNSt3__18__invokeIRZN17DtlsSrtpTransport18handleIncomingDataERKNS_6vectorIhNS_9allocatorIhEEEEEUlvE_JEEEDTclclsr3std3__1E7forwardIT_Efp_Espclsr3std3__1E7forwardIT0_Efp0_EEEOSA_DpOSB_ > > __functional_base:341 > > #11 0x6069e9 in _ZNKSt3__18functionIFvvEEclEv functional:1436 > > #12 0x603c64 in _ZN15AsyncDispatcher3runEv test.cpp:170 > > #13 0x603928 in operator() test.cpp:88 > > #14 0x6021c5 in > > > > > _ZNSt3__17forwardIZN15AsyncDispatcherC1EvEUlvE_EEOT_RNS_16remove_referenceIS3_E4typeE > > type_traits:1341 > > #15 0x460083 in _ZN6__asan10AsanThread11ThreadStartEm ??:? > > Thread T1 created by T0 here: > > #0 0x455a50 in __interceptor_pthread_create ??:? > > #1 0x5ff729 in thread<<lambda at test.cpp:88:31>, , void> thread:355 > > #2 0x5fd7b9 in thread<<lambda at test.cpp:88:31>, , void> thread:360 > > #3 0x5fd068 in AsyncDispatcher test.cpp:88 > > #4 0x4a3e3c in AsyncDispatcher test.cpp:89 > > #5 0x4688f7 in main test.cpp:516 > > #6 0x7f92277c7bc4 in __libc_start_main ??:? > > > > > > > > On Fri, Dec 13, 2013 at 5:55 PM, Dmitry Sobinov via RT <r...@openssl.org > > >wrote: > > > > > Hello > > > > > > While testing renegotiations for DTLS-SRTP, found a crash on Windows. > > > OpenSSL version is 1.0.1e, also tested on the latest 1.0.1 snapshot. > > There > > > were 2 possible stack traces: > > > > > > AddLiveService.dll!EVP_MD_size(const env_md_st * md) Line 273 C > > > > AddLiveService.dll!dtls1_do_write(ssl_st * s, int type) Line 275 C > > > AddLiveService.dll!dtls1_retransmit_message(ssl_st * s, unsigned > short > > > seq, unsigned long frag_off, int * found) Line 1293 C > > > AddLiveService.dll!dtls1_retransmit_buffered_messages(ssl_st * s) > Line > > > 1145 C > > > AddLiveService.dll!dtls1_handle_timeout(ssl_st * s) Line 450 C > > > AddLiveService.dll!dtls1_read_bytes(ssl_st * s, int type, unsigned > > char * > > > buf, int len, int peek) Line 832 C > > > AddLiveService.dll!dtls1_get_message_fragment(ssl_st * s, int st1, > int > > > stn, long max, int * ok) Line 789 C > > > AddLiveService.dll!dtls1_get_message(ssl_st * s, int st1, int stn, > int > > > mt, long max, int * ok) Line 436 C > > > AddLiveService.dll!ssl3_get_new_session_ticket(ssl_st * s) Line 2046 > C > > > AddLiveService.dll!dtls1_connect(ssl_st * s) Line 631 C > > > AddLiveService.dll!SSL_do_handshake(ssl_st * s) Line 2562 C > > > > > > and > > > > > > msvcr120d.dll!memcpy(unsigned char * dst, unsigned char * src, > unsigned > > > long count) Line 188 Unknown > > > > dtls_test.exe!dtls1_get_message_fragment(ssl_st * s, int st1, int > stn, > > > long max, int * ok) Line 789 C > > > dtls_test.exe!dtls1_get_message(ssl_st * s, int st1, int stn, int mt, > > > long max, int * ok) Line 436 C > > > dtls_test.exe!ssl3_get_new_session_ticket(ssl_st * s) Line 2046 C > > > dtls_test.exe!dtls1_connect(ssl_st * s) Line 631 C > > > dtls_test.exe!SSL_do_handshake(ssl_st * s) Line 2562 C > > > > > > Both are segfaults (access violations). On linux rehandshake doesn't > > finish > > > at all (failure after 1-2 minutes on timeout). > > > > > > You can find sample c++11 source file to reproduce this issue. > In-memory > > > BIO pair is used, client and server in the same process. When no > flights > > > are dropped, everything is fine. > > > > > > The sample can be compiled by MSVC 2013 on Windows and g++ 4.7+ (g++ -o > > > dtlstest main.cpp -std=c++11 -lssl -lcrypto -lpthread -g) or clang > 3.2+. > > > > > > > > > --- > > > Dmitry Sobinov > > > AddLive.com > > > Live video and voice for your application > > > > > > > > > > > > --- > > Dmitry Sobinov > > AddLive.com > > Live video and voice for your application > > > > ______________________________________________________________________ > > OpenSSL Project http://www.openssl.org > > Development Mailing List openssl-dev@openssl.org > > Automated List Manager majord...@openssl.org > > > > > > -- > --- > Dmitry Sobinov > AddLive.com > Live video and voice for your application > > > > -- > --- > Dmitry Sobinov > AddLive.com > Live video and voice for your application > > -- --- Dmitry Sobinov AddLive.com Live video and voice for your application
#include <stdint.h> #include <assert.h> #include <time.h> #include <stdio.h> #include <pthread.h> #include <stdbool.h> #include <openssl/ssl.h> #include <openssl/bio.h> #include <openssl/err.h> #include <openssl/x509.h> typedef struct DtlsIdentity_ { EVP_PKEY* key; X509* certificate; } DtlsIdentity; /** * Helper functions (defined in the bottom of the file) */ unsigned long idFunction(); void opensslLockingFunc(int mode, int n, const char* /*file*/, int /*line*/); void opensslInit(); void opensslCleanup(); EVP_PKEY* generateRsaKeyPair(); X509* generateCertificate(EVP_PKEY* pkey, const char* commonName); DtlsIdentity generateIdentity(); enum DtlsRole { DTLS_CLIENT = 0, DTLS_SERVER = 1 }; typedef struct PeerContext_ { SSL* ssl; SSL_CTX* ctx; BIO* inBio; BIO* outBio; X509* certificate; EVP_PKEY* key; enum DtlsRole role; char label[20]; bool handshakeCompleted; bool activeRenegotiation; int negotiationsCount; } PeerContext; void sslInfoCallbackInternal(PeerContext*, const SSL* s, int where, int ret); void sslInfoCallback(const SSL* s, int where, int ret) { sslInfoCallbackInternal((PeerContext*)SSL_get_app_data(s), s, where, ret); } int sslVerifyCallback(int ok, X509_STORE_CTX* store) { // we don't verify certificate here for simplicity return 1; } void initContext(PeerContext* ctx) { // set labels to distinguish client/server for logging: if (ctx->role == DTLS_CLIENT) sprintf(ctx->label, "[C] "); else sprintf(ctx->label, "[S] "); ctx->handshakeCompleted = false; ctx->activeRenegotiation = false; ctx->negotiationsCount = 0; // generate new certificate and private key: DtlsIdentity identity = generateIdentity(); ctx->ctx = (ctx->role == DTLS_CLIENT) ? SSL_CTX_new(DTLSv1_client_method()) : SSL_CTX_new(DTLSv1_server_method()); assert(ctx->ctx); SSL_CTX_use_certificate(ctx->ctx, identity.certificate); SSL_CTX_use_PrivateKey(ctx->ctx, identity.key); SSL_CTX_set_info_callback(ctx->ctx, &sslInfoCallback); SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, &sslVerifyCallback); SSL_CTX_set_verify_depth(ctx->ctx, 1); SSL_CTX_set_cipher_list(ctx->ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); SSL_CTX_set_read_ahead(ctx->ctx, 1); // "bad decompression" error fix for some linuxes: SSL_CTX_set_options(ctx->ctx, SSL_OP_NO_COMPRESSION); // disable tickets for simplicity: SSL_CTX_set_options(ctx->ctx, SSL_OP_NO_TICKET); ctx->ssl = SSL_new(ctx->ctx); assert(ctx->ssl); ctx->inBio = BIO_new(BIO_s_mem()); ctx->outBio = BIO_new(BIO_s_mem()); SSL_set_app_data(ctx->ssl, ctx); if (ctx->role == DTLS_CLIENT) SSL_set_connect_state(ctx->ssl); else SSL_set_accept_state(ctx->ssl); SSL_set_bio(ctx->ssl, ctx->inBio, ctx->outBio); //< the SSL object owns the bio now } void renegotiate(PeerContext* ctx) { printf("%s <<<<Renegotiation requested>>>>\n", ctx->label); assert(ctx->handshakeCompleted); assert(!ctx->activeRenegotiation); ctx->activeRenegotiation = true; (void)BIO_reset(ctx->inBio); (void)BIO_reset(ctx->outBio); SSL_renegotiate(ctx->ssl); } bool handshakeIteration(PeerContext* ctx, uint8_t** dataToSend, size_t* len, int* timeoutMs) { int wantRead = false; // we don't actually read data, but need this for SSL_read: uint8_t buf[4096]; // SSL_read after initial negotiation, SSL_do_handshake on client side // when renegotiation requested int res = (ctx->handshakeCompleted && !ctx->activeRenegotiation) ? SSL_read(ctx->ssl, buf, sizeof(buf)) : SSL_do_handshake(ctx->ssl); // get pointer to data written by handshake *len = BIO_get_mem_data(ctx->outBio, dataToSend); int err = SSL_get_error(ctx->ssl, res); struct timeval timeout; // check if remote side requested renegotiation if (!ctx->activeRenegotiation && ctx->handshakeCompleted && SSL_renegotiate_pending(ctx->ssl) == 1) { printf("%s Remote renegotiation detected\n", ctx->label); ctx->activeRenegotiation = true; } // check if renegotiation finished bool renegotiationFinished = ctx->activeRenegotiation && SSL_renegotiate_pending(ctx->ssl) == 0; // handle handshake errors switch (err) { case SSL_ERROR_NONE: if (!ctx->handshakeCompleted || renegotiationFinished) { ctx->handshakeCompleted = true; ctx->activeRenegotiation = false; ctx->negotiationsCount++; } break; case SSL_ERROR_WANT_READ: if (renegotiationFinished) { ctx->activeRenegotiation = false; ctx->negotiationsCount++; } else if (DTLSv1_get_timeout(ctx->ssl, &timeout)) { *timeoutMs = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; wantRead = true; printf("%s WANT_READ with timeout %d\n", ctx->label, *timeoutMs); } break; default: printf("Unexpected error while processing DTLS: %d\n", err); assert(false); } return wantRead; } void handleIncomingData(PeerContext* ctx, uint8_t* data, size_t size) { printf("%s INCOMING DATA of size %d\n", ctx->label, size); (void)BIO_reset(ctx->inBio); (void)BIO_reset(ctx->outBio); BIO_write(ctx->inBio, data, size); } void sslInfoCallbackInternal(PeerContext* ctx, const SSL* s, int where, int ret) { char method[100]; int w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) sprintf(method, "SSL_connect"); else if (w & SSL_ST_ACCEPT) sprintf(method, "SSL_accept"); if (where & SSL_CB_LOOP) { printf("%s %s: %s\n", ctx->label, method, SSL_state_string_long(s)); } else if (where & SSL_CB_ALERT) { const char* direction = (where & SSL_CB_READ) ? "read" : "write"; printf("%s SSL3 alert %s: %s : %s \n", ctx->label, direction, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) { printf("%s %s failed in %s \n", ctx->label, method, SSL_state_string_long(s)); } else if (ret < 0) { printf("%s %s failed in %s \n", ctx->label, method, SSL_state_string_long(s)); } } } int main() { opensslInit(); PeerContext clientCtx; clientCtx.role = DTLS_CLIENT; PeerContext serverCtx; serverCtx.role = DTLS_SERVER; initContext(&clientCtx); initContext(&serverCtx); uint8_t* data; size_t len; bool clientWantRead = false; bool serverWantRead = false; int timeoutMsClient = 0; int timeoutMsServer = 0; // starting to "listen" on server: handshakeIteration(&serverCtx, &data, &len, &timeoutMsServer); assert(len == 0); // initial negotiation: while (1) { handshakeIteration(&clientCtx, &data, &len, &timeoutMsClient); if (len) handleIncomingData(&serverCtx, data, len); if (clientCtx.handshakeCompleted) break; handshakeIteration(&serverCtx, &data, &len, &timeoutMsServer); if (len) handleIncomingData(&clientCtx, data, len); } assert(clientCtx.negotiationsCount == 1); printf("======== Renegotiating ========\n"); renegotiate(&clientCtx); int clientPacketCounter = 0; // renegotiation loop: while (1) { clientWantRead = handshakeIteration(&clientCtx, &data, &len, &timeoutMsClient); if (len) { clientPacketCounter++; printf("Client has some data to send to the server. Size %d\n", len); // if (clientPacketCounter != 2) handleIncomingData(&serverCtx, data, len); else printf("Intentionally dropping the packet and waiting\n"); if (clientPacketCounter > 5) { printf("Error: too much packets sent!\n"); goto end; } } // one renegotiation is enough (1 - initial negotiation, 2 - 1st renegotioation) if (clientCtx.negotiationsCount == 2) break; serverWantRead = handshakeIteration(&serverCtx, &data, &len, &timeoutMsServer); if (len) { printf("Server has some data to send to the client of size %d\n", len); handleIncomingData(&clientCtx, data, len); // client read request satisfied, no need to start timer at the end of the iteration: clientWantRead = false; } if (clientWantRead || serverWantRead) { int timeout = timeoutMsClient < timeoutMsServer ? timeoutMsClient : timeoutMsServer; printf("Waiting for %d ms for client to generate new flight\n", timeout); struct timespec rem; struct timespec req; req.tv_sec = timeout / 1000; req.tv_nsec = (timeout % 1000) * 1000000; nanosleep(&req, &rem); printf("Waiting is over\n"); } } printf("Renegotiated successfully!\n"); end: opensslCleanup(); return 0; } // Helper functions implementation static pthread_mutex_t* mutex_buf = NULL; unsigned long idFunction() { return (unsigned long)pthread_self(); } void opensslLockingFunc(int mode, int n, const char* file, int line) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(&mutex_buf[n]); else pthread_mutex_unlock(&mutex_buf[n]); } void opensslInit() { SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); mutex_buf = (pthread_mutex_t*)malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); int i; for (i = 0; i < CRYPTO_num_locks(); i++) pthread_mutex_init(&mutex_buf[i], NULL); CRYPTO_set_locking_callback(&opensslLockingFunc); CRYPTO_set_id_callback(&idFunction); } void opensslCleanup() { CRYPTO_set_id_callback(0); CRYPTO_set_locking_callback(0); int i; for (i = 0; i < CRYPTO_num_locks(); i++) pthread_mutex_destroy(&mutex_buf[i]); free(mutex_buf); ERR_free_strings(); ERR_remove_state(0); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); } const int gKeyLength = 1024; // number of random bits for certificate serial number const int gRandomBitsNum = 64; // one year certificate validity const int gCertificateLifetime = 60 * 60 * 24 * 365; // to compensate for slightly incorrect system clocks const int gCertificateValidationWindow = -60 * 60 * 24; EVP_PKEY* generateRsaKeyPair() { EVP_PKEY* pkey = EVP_PKEY_new(); BIGNUM* exponent = BN_new(); RSA* rsa = RSA_new(); if (!pkey || !exponent || !rsa || !BN_set_word(exponent, 0x10001) || !RSA_generate_key_ex(rsa, gKeyLength, exponent, NULL) || !EVP_PKEY_assign_RSA(pkey, rsa)) { EVP_PKEY_free(pkey); BN_free(exponent); RSA_free(rsa); return NULL; } BN_free(exponent); return pkey; } X509* generateCertificate(EVP_PKEY* pkey, const char* commonName) { X509* x509 = NULL; BIGNUM* serialNumber = NULL; X509_NAME* name = NULL; if ((x509 = X509_new()) == NULL) goto error; if (!X509_set_pubkey(x509, pkey)) goto error; ASN1_INTEGER* asn1SerialNumber; if ((serialNumber = BN_new()) == NULL || !BN_pseudo_rand(serialNumber, gRandomBitsNum, 0, 0) || (asn1SerialNumber = X509_get_serialNumber(x509)) == NULL || !BN_to_ASN1_INTEGER(serialNumber, asn1SerialNumber)) goto error; if (!X509_set_version(x509, 0L)) goto error; if ((name = X509_NAME_new()) == NULL || !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8, (unsigned char*)commonName, -1, -1, 0) || !X509_set_subject_name(x509, name) || !X509_set_issuer_name(x509, name)) goto error; if (!X509_gmtime_adj(X509_get_notBefore(x509), gCertificateValidationWindow) || !X509_gmtime_adj(X509_get_notAfter(x509), gCertificateLifetime)) goto error; if (!X509_sign(x509, pkey, EVP_sha256())) goto error; BN_free(serialNumber); X509_NAME_free(name); return x509; error: BN_free(serialNumber); X509_NAME_free(name); X509_free(x509); return NULL; } DtlsIdentity generateIdentity() { DtlsIdentity id; id.key = generateRsaKeyPair(); id.certificate = generateCertificate(id.key, "TestCompany Inc"); return id; }