diff --git a/src/internal.c b/src/internal.c index e4585216985..ee93a3c95ad 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8904,6 +8904,9 @@ static int InitSSL_StaticMemory(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ctx_hint = ((WOLFSSL_HEAP_HINT*)(ctx->heap)); ssl_hint->memory = ctx_hint->memory; + /* the SSL's ref; each allocated block holds another */ + ssl_hint->refCount = 1; + ssl_hint->sslOwned = 1; #ifndef WOLFSSL_STATIC_MEMORY_LEAN #ifndef SINGLE_THREADED /* lock and check IO count / handshake count */ @@ -10283,18 +10286,22 @@ static void FreeSSL_StaticMemory(WOLFSSL* ssl) /* avoid dereferencing a test value */ if (ssl->heap != (void*)WOLFSSL_HEAP_TEST) { #endif - void* heap = ssl->ctx ? ssl->ctx->heap : ssl->heap; #ifndef WOLFSSL_STATIC_MEMORY_LEAN WOLFSSL_HEAP_HINT* ssl_hint = (WOLFSSL_HEAP_HINT*)ssl->heap; - WOLFSSL_HEAP* ctx_heap; - WOLFSSL_MEM_CONN_STATS* stats; + WOLFSSL_HEAP* ctx_heap = ssl_hint->memory; + void* heap = ssl->ctx ? ssl->ctx->heap : ssl->heap; + WOLFSSL_MEM_CONN_STATS* stats = NULL; - ctx_heap = ssl_hint->memory; #ifndef SINGLE_THREADED if (wc_LockMutex(&(ctx_heap->memory_mutex)) != 0) { WOLFSSL_MSG("Bad memory_mutex lock"); } #endif + /* detach under the lock: live blocks may still use the hint */ + if (ctx_heap->flag & WOLFMEM_TRACK_STATS) { + stats = ssl_hint->stats; + ssl_hint->stats = NULL; + } ctx_heap->curIO--; if (FreeFixedIO(ctx_heap, &(ssl_hint->outBuf)) != 1) { WOLFSSL_MSG("Error freeing fixed output buffer"); @@ -10310,16 +10317,13 @@ static void FreeSSL_StaticMemory(WOLFSSL* ssl) #ifndef SINGLE_THREADED wc_UnLockMutex(&(ctx_heap->memory_mutex)); #endif - - /* check if tracking stats */ - if (ctx_heap->flag & WOLFMEM_TRACK_STATS) { - /* with ssl->ctx NULL, heap is ssl->heap and XFREE updates stats */ - stats = ssl_hint->stats; - ssl_hint->stats = NULL; - XFREE(stats, heap, DYNAMIC_TYPE_SSL); - } - #endif /* !WOLFSSL_STATIC_MEMORY_LEAN */ - XFREE(ssl->heap, heap, DYNAMIC_TYPE_SSL); + /* unlocked, XFREE takes memory_mutex; with ssl->ctx NULL, heap is + * ssl->heap, which the SSL's reference still keeps alive here */ + XFREE(stats, heap, DYNAMIC_TYPE_SSL); + #endif + /* drop the SSL's ref; the last block's free may free the hint */ + wc_HeapHint_Release(ssl->heap); + ssl->heap = NULL; #ifdef WOLFSSL_HEAP_TEST } #endif @@ -20695,7 +20699,7 @@ int DoFinished(WOLFSSL* ssl, const byte* input, word32* inOutIdx, word32 size, ssl->msgsReceived.got_session_ticket) { ForceZero(ssl->session->ticket, ssl->session->ticketLen); if (ssl->session->ticketLenAlloc > 0) { - XFREE(ssl->session->ticket, ssl->heap, + XFREE(ssl->session->ticket, ssl->session->heap, DYNAMIC_TYPE_SESSION_TICK); ssl->session->ticket = ssl->session->staticTicket; ssl->session->ticketLenAlloc = 0; @@ -39944,14 +39948,15 @@ int SetTicket(WOLFSSL* ssl, const byte* ticket, word32 length) /* Free old dynamic ticket if we already had one */ if (ssl->session->ticketLenAlloc > 0) { - XFREE(ssl->session->ticket, ssl->heap, DYNAMIC_TYPE_SESSION_TICK); + XFREE(ssl->session->ticket, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); ssl->session->ticket = ssl->session->staticTicket; ssl->session->ticketLenAlloc = 0; } if (length > sizeof(ssl->session->staticTicket)) { - byte* sessionTicket = - (byte*)XMALLOC(length, ssl->heap, DYNAMIC_TYPE_SESSION_TICK); + byte* sessionTicket = (byte*)XMALLOC(length, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); if (sessionTicket == NULL) return MEMORY_E; ssl->session->ticket = sessionTicket; @@ -44428,7 +44433,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (ssl->session->ticket != ssl->session->staticTicket) { /* Always use the static ticket buffer */ - XFREE(ssl->session->ticket, NULL, DYNAMIC_TYPE_SESSION_TICK); + XFREE(ssl->session->ticket, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); ssl->session->ticket = ssl->session->staticTicket; ssl->session->ticketLenAlloc = 0; } @@ -45069,7 +45075,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) if (ssl->session->ticketNonce.data != ssl->session->ticketNonce.dataStatic) { - XFREE(ssl->session->ticketNonce.data, ssl->heap, + XFREE(ssl->session->ticketNonce.data, ssl->session->heap, DYNAMIC_TYPE_SESSION_TICK); ssl->session->ticketNonce.data = ssl->session->ticketNonce.dataStatic; diff --git a/src/ssl_sess.c b/src/ssl_sess.c index ba42dea2168..de3179601ad 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -4031,7 +4031,7 @@ WOLFSSL_SESSION* wolfSSL_NewSession(void* heap) #ifdef WOLFSSL_REFCNT_ERROR_RETURN if (err != 0) { WOLFSSL_MSG("Error setting up session reference mutex"); - XFREE(ret, ret->heap, DYNAMIC_TYPE_SESSION); + XFREE(ret, heap, DYNAMIC_TYPE_SESSION); return NULL; } #else diff --git a/tests/api.c b/tests/api.c index 776c3a2f6be..166637e158f 100644 --- a/tests/api.c +++ b/tests/api.c @@ -38687,6 +38687,587 @@ static int test_wolfSSL_set_SSL_CTX_static_heap(void) return EXPECT_RESULT(); } +/* A session retained past wolfSSL_free() keeps the per-SSL static heap hint + * alive. */ +static int test_wolfSSL_get1_session_static_heap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_TLS) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_WOLFSSL_CLIENT) + byte mem[TEST_TLS_STATIC_MEMSZ]; + WOLFSSL_HEAP_HINT* heap = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_SESSION* dup = NULL; + WOLFSSL_HEAP_HINT* hint = NULL; + word32 refs = 0; + WOLFSSL_MEM_STATS before; + WOLFSSL_MEM_STATS after; + + XMEMSET(&before, 0, sizeof(before)); + XMEMSET(&after, 0, sizeof(after)); + ExpectIntEQ(wc_LoadStaticMemory(&heap, mem, sizeof(mem), + WOLFMEM_GENERAL | WOLFMEM_TRACK_STATS, 1), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new_ex(wolfSSLv23_client_method_ex(heap), + heap)); + /* The first SSL makes a one-time allocation for the CTX; take the + * baseline after it. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + wolfSSL_free(ssl); + ssl = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx, &before), 1); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* Held by the SSL and each allocated block. */ + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl->heap); + ExpectIntEQ(hint->sslOwned, 1); + ExpectIntGT(refs = hint->refCount, 1); + ExpectNotNull(sess = wolfSSL_get1_session(ssl)); + ExpectPtrEq(sess->heap, hint); + /* A session ref allocates nothing. */ + ExpectIntEQ(hint->refCount, refs); + wolfSSL_SESSION_free(sess); + sess = NULL; + ExpectIntEQ(hint->refCount, refs); + /* No handshake done, so clear replaces the session. */ + ExpectIntEQ(wolfSSL_clear(ssl), WOLFSSL_SUCCESS); + ExpectNotNull(sess = wolfSSL_get1_session(ssl)); + ExpectPtrEq(sess->heap, hint); + wolfSSL_free(ssl); + ssl = NULL; + /* Only the session holds the hint now. */ + ExpectIntGE(refs = hint->refCount, 1); + + /* Both operations go through sess->heap after the SSL is gone. */ + ExpectNotNull(dup = wolfSSL_SESSION_dup(sess)); + ExpectPtrEq(dup->heap, hint); + ExpectIntGT(hint->refCount, refs); + wolfSSL_SESSION_free(dup); + ExpectIntEQ(hint->refCount, refs); + /* Last holder: the hint goes back to the pool. */ + wolfSSL_SESSION_free(sess); + + /* Everything allocated for the SSL went back to the pool. Stats are not + * filled in with WOLFSSL_STATIC_MEMORY_LEAN. */ + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx, &after), 1); +#ifndef WOLFSSL_STATIC_MEMORY_LEAN + ExpectIntEQ(after.curAlloc, before.curAlloc); +#endif + + wolfSSL_CTX_free(ctx); + wc_UnloadStaticMemory(heap); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) +static int test_session_static_heap_new_pair(struct test_memio_ctx* test_ctx, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + WOLFSSL_SESSION* sess) +{ + EXPECT_DECLS; + char buf[1]; + + test_memio_clear_buffer(test_ctx, 1); + test_memio_clear_buffer(test_ctx, 0); + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + NULL, NULL), 0); + if (sess != NULL) { + ExpectIntEQ(wolfSSL_set_session(*ssl_c, sess), WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_memio_do_handshake(*ssl_c, *ssl_s, 10, NULL), 0); + /* Process the NewSessionTicket sent after the handshake. */ + ExpectIntEQ(wolfSSL_read(*ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + return EXPECT_RESULT(); +} +#endif + +/* Session tickets held by a session retained past wolfSSL_free() must come + * from, and go back to, the session's heap. */ +static int test_wolfSSL_session_ticket_static_heap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + byte cliMem[TEST_TLS_STATIC_MEMSZ]; + byte svrMem[TEST_TLS_STATIC_MEMSZ]; + byte bigTicket[SESSION_TICKET_LEN + 1]; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_SESSION* dup = NULL; + WOLFSSL_HEAP_HINT* hint = NULL; + word32 refs = 0; + WOLFSSL_MEM_STATS before; + WOLFSSL_MEM_STATS after; + char buf[1]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&before, 0, sizeof(before)); + XMEMSET(&after, 0, sizeof(after)); + XMEMSET(bigTicket, 0xA5, sizeof(bigTicket)); + + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_c, + wolfTLSv1_3_client_method_ex, cliMem, sizeof(cliMem), + WOLFMEM_GENERAL | WOLFMEM_TRACK_STATS, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, + wolfTLSv1_3_server_method_ex, svrMem, sizeof(svrMem), + WOLFMEM_GENERAL, 2), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + wolfSSL_CTX_set_verify(ctx_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + } + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + /* A first connection makes the one-time allocations for the CTXs; take + * the baseline after it. */ + ExpectIntEQ(test_session_static_heap_new_pair(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, NULL), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &before), 1); + + /* Full handshake: keep the session with its ticket after the SSL. */ + ExpectIntEQ(test_session_static_heap_new_pair(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, NULL), TEST_SUCCESS); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_c->heap); + ExpectPtrEq(sess->heap, hint); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + /* The session keeps the client's hint alive. */ + ExpectIntGE(refs = hint->refCount, 1); + + /* Resume with the session on a new SSL; a new ticket replaces the old. */ + ExpectIntEQ(test_session_static_heap_new_pair(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, sess), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_session_reused(ssl_c), 1); + /* sess was copied from sess->heap, so both are on the first hint. */ + ExpectPtrNE(ssl_c->heap, hint); + ExpectPtrNE(ssl_c->session, sess); + ExpectPtrEq(ssl_c->session->heap, hint); + ExpectIntGT(hint->refCount, refs); + wolfSSL_SESSION_free(sess); + sess = NULL; + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + /* The hint has outlived both SSLs. */ + ExpectIntGE(hint->refCount, 1); + wolfSSL_SESSION_free(sess); + sess = NULL; + + /* A ticket too big for the static buffer is allocated dynamically. */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_SessionTicket(ssl_c, bigTicket, + (word32)sizeof(bigTicket)), WOLFSSL_SUCCESS); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntEQ(sess->ticketLenAlloc, sizeof(bigTicket)); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_c->heap); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntGE(refs = hint->refCount, 1); + ExpectNotNull(dup = wolfSSL_SESSION_dup(sess)); + ExpectIntGT(hint->refCount, refs); + wolfSSL_SESSION_free(dup); + dup = NULL; + ExpectIntEQ(hint->refCount, refs); + wolfSSL_SESSION_free(sess); + sess = NULL; + + /* A full handshake replaces the dynamic ticket with the new, smaller one, + * freeing the dynamic buffer through the session's heap. */ + test_memio_clear_buffer(&test_ctx, 1); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + NULL, NULL), 0); + ExpectIntEQ(wolfSSL_set_SessionTicket(ssl_c, bigTicket, + (word32)sizeof(bigTicket)), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_c->session->ticketLenAlloc, sizeof(bigTicket)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_c->session->ticketLenAlloc, 0); + ExpectIntGT(ssl_c->session->ticketLen, 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_SESSION_free(sess); + + /* Everything allocated for the connections went back to the pool. */ + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &after), 1); + ExpectIntEQ(after.curAlloc, before.curAlloc); + + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) && !defined(NO_RSA) && \ + !defined(NO_SESSION_CACHE) && !defined(WOLFSSL_NO_CLIENT_AUTH) +static int test_peer_cert_static_heap_connect(struct test_memio_ctx* test_ctx, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s) +{ + EXPECT_DECLS; + + test_memio_clear_buffer(test_ctx, 1); + test_memio_clear_buffer(test_ctx, 0); + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfSSLv23_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_use_certificate_file(*ssl_c, cliCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(*ssl_c, cliKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(*ssl_c, *ssl_s, 10, NULL), 0); + + return EXPECT_RESULT(); +} +#endif + +/* On the server, wolfSSL_get_peer_cert_chain() moves the leaf certificate into + * the session as its peer. Session and X509 keep the per-SSL hint alive past + * wolfSSL_free(). */ +static int test_wolfSSL_peer_cert_chain_static_heap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) && !defined(NO_RSA) && \ + !defined(NO_SESSION_CACHE) && !defined(WOLFSSL_NO_CLIENT_AUTH) + byte svrMem[TEST_TLS_STATIC_MEMSZ]; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_HEAP_HINT* hint = NULL; + word32 refs = 0; +#ifdef KEEP_PEER_CERT + WOLFSSL_X509* peer = NULL; +#endif + WOLFSSL_MEM_STATS before; + WOLFSSL_MEM_STATS after; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&before, 0, sizeof(before)); + XMEMSET(&after, 0, sizeof(after)); + + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, + wolfSSLv23_server_method_ex, svrMem, sizeof(svrMem), + WOLFMEM_GENERAL | WOLFMEM_TRACK_STATS, 1), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + + /* A first connection makes the one-time allocations for the server CTX; + * take the baseline after it. */ + ExpectIntEQ(test_peer_cert_static_heap_connect(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &before), 1); + + /* The session is the last holder. */ + ExpectIntEQ(test_peer_cert_static_heap_connect(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s), TEST_SUCCESS); + /* Held by the SSL and each allocated block. */ + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_s->heap); + ExpectIntGT(refs = hint->refCount, 1); + + ExpectNotNull(wolfSSL_get_peer_cert_chain(ssl_s)); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_s)); + ExpectNotNull(sess->peer); + /* The peer is on the SSL's hint too. */ + ExpectPtrEq(sess->peer->heap, hint); + ExpectIntGT(hint->refCount, refs); + wolfSSL_free(ssl_s); + ssl_s = NULL; + /* Only the session and peer hold the hint now. */ + ExpectIntGE(hint->refCount, 2); + /* The peer is still usable once the SSL is gone. */ + ExpectNotNull(wolfSSL_SESSION_get0_peer(sess)); + wolfSSL_SESSION_free(sess); + sess = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + +#ifdef KEEP_PEER_CERT + /* An X509 is the last holder. */ + ExpectIntEQ(test_peer_cert_static_heap_connect(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s), TEST_SUCCESS); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_s->heap); + ExpectIntGT(refs = hint->refCount, 1); + /* A copy of ssl->peerCert, so on the SSL's hint. */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s)); + ExpectPtrEq(peer->heap, hint); + ExpectIntGT(hint->refCount, refs); + wolfSSL_free(ssl_s); + ssl_s = NULL; + /* Only the X509 holds the hint now. */ + ExpectIntGE(hint->refCount, 1); + ExpectNotNull(wolfSSL_X509_get_subject_name(peer)); + /* Last holder: the hint goes back to the pool. */ + wolfSSL_X509_free(peer); + peer = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; +#endif + + /* Everything allocated for the connections went back to the pool. Stats + * are not filled in with WOLFSSL_STATIC_MEMORY_LEAN. */ + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &after), 1); +#ifndef WOLFSSL_STATIC_MEMORY_LEAN + ExpectIntEQ(after.curAlloc, before.curAlloc); +#endif + + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_RSA) +/* ioFlag is WOLFMEM_IO_POOL or WOLFMEM_IO_POOL_FIXED. */ +static int test_static_heap_io_refs(int ioFlag) +{ + EXPECT_DECLS; + byte cliMem[TEST_TLS_STATIC_MEMSZ]; + byte cliIoMem[4 * (WOLFMEM_IO_SZ + WOLFSSL_STATIC_ALIGN + 64)]; + byte svrMem[TEST_TLS_STATIC_MEMSZ]; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_HEAP_HINT* hint = NULL; + void* io = NULL; + void* tmp = NULL; + word32 refs = 0; + int fixed = (ioFlag == WOLFMEM_IO_POOL_FIXED); + WOLFSSL_MEM_STATS before; + WOLFSSL_MEM_STATS stats; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&before, 0, sizeof(before)); + XMEMSET(&stats, 0, sizeof(stats)); + + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_c, + wolfSSLv23_client_method_ex, cliMem, sizeof(cliMem), + WOLFMEM_GENERAL | WOLFMEM_TRACK_STATS, 1), WOLFSSL_SUCCESS); + /* IO buffers go into the same CTX hint. */ + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_c, NULL, cliIoMem, + sizeof(cliIoMem), ioFlag, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, + wolfSSLv23_server_method_ex, svrMem, sizeof(svrMem), + WOLFMEM_GENERAL, 1), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + wolfSSL_CTX_set_verify(ctx_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + } + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + /* A first connection makes the one-time allocations for the CTXs; take + * the baseline after it. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + NULL, NULL), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &before), 1); + ExpectIntGT(before.avaIO, 0); + + /* The session outlives the SSL whose IO buffers came from the pool. */ + test_memio_clear_buffer(&test_ctx, 1); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + NULL, NULL), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_c->heap); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectPtrEq(sess->heap, hint); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectIntGE(hint->refCount, 1); + /* All IO buffers are back in the pool while the hint lives on. */ + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &stats), 1); + ExpectIntEQ(stats.avaIO, before.avaIO); + /* Last holder: the hint goes back to the pool. */ + wolfSSL_SESSION_free(sess); + sess = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &stats), 1); + ExpectIntEQ(stats.curAlloc, before.curAlloc); + ExpectIntEQ(stats.avaIO, before.avaIO); + + /* IO buffers taken directly from the per-SSL hint. Fixed ones belong to + * the SSL and hold no ref; pool ones hold one, and realloc keeps it. */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl_c->heap); + if (EXPECT_SUCCESS()) + refs = hint->refCount; + ExpectNotNull(io = XMALLOC(64, hint, DYNAMIC_TYPE_IN_BUFFER)); + ExpectIntEQ(hint->refCount, fixed ? refs : refs + 1); +#ifndef WOLFSSL_NO_REALLOC + ExpectNotNull(tmp = XREALLOC(io, 128, hint, DYNAMIC_TYPE_IN_BUFFER)); + /* IO buffers are fixed size, so resized in place. */ + ExpectPtrEq(tmp, io); + ExpectIntEQ(hint->refCount, fixed ? refs : refs + 1); +#else + (void)tmp; +#endif + XFREE(io, hint, DYNAMIC_TYPE_IN_BUFFER); + io = NULL; + ExpectIntEQ(hint->refCount, refs); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_c, &stats), 1); + ExpectIntEQ(stats.curAlloc, before.curAlloc); + ExpectIntEQ(stats.avaIO, before.avaIO); + + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* IO pool and fixed IO blocks keep the per-SSL hint's ref count balanced. */ +static int test_wolfSSL_static_heap_io_refs(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) && !defined(NO_RSA) + ExpectIntEQ(test_static_heap_io_refs(WOLFMEM_IO_POOL_FIXED), TEST_SUCCESS); + ExpectIntEQ(test_static_heap_io_refs(WOLFMEM_IO_POOL), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A reallocated block keeps its ref on the per-SSL hint, even as the last + * holder after wolfSSL_free(). */ +static int test_wolfSSL_static_heap_realloc_refs(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_REALLOC) + byte mem[TEST_TLS_STATIC_MEMSZ]; + WOLFSSL_HEAP_HINT* heap = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_HEAP_HINT* hint = NULL; + void* p = NULL; + void* tmp = NULL; + word32 refs = 0; + WOLFSSL_MEM_STATS before; + WOLFSSL_MEM_STATS after; + + XMEMSET(&before, 0, sizeof(before)); + XMEMSET(&after, 0, sizeof(after)); + ExpectIntEQ(wc_LoadStaticMemory(&heap, mem, sizeof(mem), + WOLFMEM_GENERAL | WOLFMEM_TRACK_STATS, 1), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new_ex(wolfSSLv23_client_method_ex(heap), + heap)); + /* The first SSL makes a one-time allocation for the CTX; take the + * baseline after it. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + wolfSSL_free(ssl); + ssl = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx, &before), 1); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectNotNull(hint = (WOLFSSL_HEAP_HINT*)ssl->heap); + if (EXPECT_SUCCESS()) + refs = hint->refCount; + ExpectNotNull(p = XMALLOC(64, hint, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(hint->refCount, refs + 1); + /* A bigger bucket: the new block takes the ref the old one drops. */ + ExpectNotNull(tmp = XREALLOC(p, 1000, hint, DYNAMIC_TYPE_TMP_BUFFER)); + if (tmp != NULL) { + p = tmp; + } + ExpectIntEQ(hint->refCount, refs + 1); + wolfSSL_free(ssl); + ssl = NULL; + /* Only the block holds the hint now. */ + ExpectIntEQ(hint->refCount, 1); + /* The new block is held before the old one drops the last ref. */ + ExpectNotNull(tmp = XREALLOC(p, 2000, hint, DYNAMIC_TYPE_TMP_BUFFER)); + if (tmp != NULL) { + p = tmp; + } + ExpectIntEQ(hint->refCount, 1); + /* Last holder: the hint goes back to the pool. */ + XFREE(p, hint, DYNAMIC_TYPE_TMP_BUFFER); + p = NULL; + + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx, &after), 1); +#ifndef WOLFSSL_STATIC_MEMORY_LEAN + ExpectIntEQ(after.curAlloc, before.curAlloc); +#endif + + wolfSSL_CTX_free(ctx); + wc_UnloadStaticMemory(heap); +#endif + return EXPECT_RESULT(); +} + static int test_openssl_FIPS_drbg(void) { EXPECT_DECLS; @@ -46237,6 +46818,11 @@ TEST_CASE testCases[] = { /* Can't memory test as client/server hangs. */ TEST_DECL(test_wolfSSL_CTX_StaticMemory), TEST_DECL(test_wolfSSL_set_SSL_CTX_static_heap), + TEST_DECL(test_wolfSSL_get1_session_static_heap), + TEST_DECL(test_wolfSSL_session_ticket_static_heap), + TEST_DECL(test_wolfSSL_peer_cert_chain_static_heap), + TEST_DECL(test_wolfSSL_static_heap_io_refs), + TEST_DECL(test_wolfSSL_static_heap_realloc_refs), #if !defined(NO_FILESYSTEM) && \ defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) diff --git a/tests/api/test_session.c b/tests/api/test_session.c index e2a2e4d07df..e9fe31d80e0 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -513,11 +513,13 @@ static int twcase_server_sess_ctx_pre_shutdown(WOLFSSL* ssl) /* Now save the session in the internal store to make it available * for lookup. For TLS 1.3, we can't save the session without * WOLFSSL_TICKET_HAVE_ID because there is no way to retrieve the - * session from cache. */ + * session from cache. The same holds for a TLS 1.2 ticket session, + * which has no session ID without WOLFSSL_TICKET_HAVE_ID. */ if (wolfSSL_is_server(ssl) #ifndef WOLFSSL_TICKET_HAVE_ID && wolfSSL_version(ssl) != TLS1_3_VERSION && wolfSSL_version(ssl) != DTLS1_3_VERSION + && *sess != NULL && (*sess)->sessionIDSz > 0 #endif ) { ExpectIntEQ(wolfSSL_CTX_add_session(wolfSSL_get_SSL_CTX(ssl), @@ -870,7 +872,13 @@ static WC_MAYBE_UNUSED int test_wolfSSL_CTX_add_session_ext( else { /* The ticket is reused, no new session */ ExpectIntEQ(twcase_new_session_called, 0); + #ifdef WOLFSSL_TICKET_HAVE_ID ExpectIntEQ(twcase_remove_session_called, 1); + #else + /* No session ID, so the session was not cached in + * twcase_server_sess_ctx_pre_shutdown to be removed */ + ExpectIntEQ(twcase_remove_session_called, 0); + #endif } break; #ifdef WOLFSSL_EARLY_DATA diff --git a/wolfcrypt/src/memory.c b/wolfcrypt/src/memory.c index a2df2258ebe..89bc4b5bbbc 100644 --- a/wolfcrypt/src/memory.c +++ b/wolfcrypt/src/memory.c @@ -845,6 +845,91 @@ void wc_UnloadStaticMemory(WOLFSSL_HEAP_HINT* heap) #endif } + +/* Returns 1 if hint is a per-SSL hint created by InitSSL_StaticMemory. Only + * these are reference counted; CTX, user and global hints are left alone. + * + * Needs hint to not be garbage data. */ +static int wc_HeapHint_IsRefCounted(const WOLFSSL_HEAP_HINT* hint) +{ + if (hint == NULL) { + return 0; + } +#ifdef WOLFSSL_HEAP_TEST + /* avoid dereferencing a test value */ + if (hint == (const WOLFSSL_HEAP_HINT*)WOLFSSL_HEAP_TEST) { + return 0; + } +#endif + return hint->sslOwned && hint->memory != NULL; +} + +/* An allocated block's unused next field holds its per-SSL hint (or NULL), + * on which it holds a ref. */ +#define WC_MEM_OWNER(pt) ((WOLFSSL_HEAP_HINT*)(void*)(pt)->next) +#define WC_MEM_SET_OWNER(pt, o) ((pt)->next = (wc_Memory*)(void*)(o)) + +/* Ref hint for a new block. Returns hint if ref counted, else NULL. Call with + * memory_mutex held. */ +static WOLFSSL_HEAP_HINT* wc_HeapHint_Hold(WOLFSSL_HEAP_HINT* hint) +{ + if (!wc_HeapHint_IsRefCounted(hint)) { + return NULL; + } + hint->refCount++; + return hint; +} + +/* Drop a ref. Returns owner if that was the last, to pass to wc_HeapHint_Free + * once memory_mutex is released, else NULL. Call with memory_mutex held. */ +static WOLFSSL_HEAP_HINT* wc_HeapHint_Drop(WOLFSSL_HEAP_HINT* owner) +{ + if (owner == NULL || --owner->refCount != 0) { + return NULL; + } + return owner; +} + +/* Free a hint whose last ref was dropped. Call without memory_mutex. */ +static void wc_HeapHint_Free(WOLFSSL_HEAP_HINT* owner) +{ + WOLFSSL_HEAP_HINT tmp; + + if (owner == NULL) { + return; + } + /* free through a temporary hint on the same pool so neither the CTX + * nor the hint being freed is needed */ + XMEMSET(&tmp, 0, sizeof(tmp)); + tmp.memory = owner->memory; + XFREE(owner, &tmp, DYNAMIC_TYPE_SSL); +} + +/* Drop the SSL's ref on its hint. Do not use the hint after. */ +void wc_HeapHint_Release(void* heap) +{ + WOLFSSL_HEAP_HINT* hint = (WOLFSSL_HEAP_HINT*)heap; +#ifndef SINGLE_THREADED + WOLFSSL_HEAP* mem = NULL; +#endif + + if (!wc_HeapHint_IsRefCounted(hint)) { + return; + } +#ifndef SINGLE_THREADED + mem = hint->memory; + if (wc_LockMutex(&(mem->memory_mutex)) != 0) { + WOLFSSL_MSG("Bad memory_mutex lock"); + return; + } +#endif + hint = wc_HeapHint_Drop(hint); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&(mem->memory_mutex)); +#endif + wc_HeapHint_Free(hint); +} + #ifndef WOLFSSL_STATIC_MEMORY_LEAN /* returns the size of management memory needed for each bucket. * This is memory that is used to keep track of and align memory buckets. */ @@ -1147,6 +1232,11 @@ void* wolfSSL_Malloc(size_t size, void* heap, int type) } } } + + /* fixed IO buffers hold no ref */ + if (pt != NULL) { + WC_MEM_SET_OWNER(pt, wc_HeapHint_Hold(hint)); + } } if (pt != NULL) { @@ -1267,6 +1357,7 @@ void wolfSSL_Free(void *ptr, void* heap, int type) else { WOLFSSL_HEAP_HINT* hint = (WOLFSSL_HEAP_HINT*)heap; WOLFSSL_HEAP* mem; + WOLFSSL_HEAP_HINT* owner = NULL; wc_Memory* pt; word32 padSz = -(int)sizeof(wc_Memory) & (WOLFSSL_STATIC_ALIGN - 1); @@ -1302,12 +1393,14 @@ void wolfSSL_Free(void *ptr, void* heap, int type) else if (mem->flag & WOLFMEM_IO_POOL && pt->sz == WOLFMEM_IO_SZ && (type == DYNAMIC_TYPE_OUT_BUFFER || type == DYNAMIC_TYPE_IN_BUFFER)) { + owner = wc_HeapHint_Drop(WC_MEM_OWNER(pt)); pt->next = mem->io; mem->io = pt; } else #endif { /* general memory free */ + owner = wc_HeapHint_Drop(WC_MEM_OWNER(pt)); for (i = 0; i < WOLFMEM_MAX_BUCKETS; i++) { if (pt->sz == mem->sizeList[i]) { pt->next = mem->ava[i]; @@ -1359,6 +1452,8 @@ void wolfSSL_Free(void *ptr, void* heap, int type) #ifndef SINGLE_THREADED wc_UnLockMutex(&(mem->memory_mutex)); #endif + /* set if this block held the last ref on a released hint */ + wc_HeapHint_Free(owner); } } @@ -1414,6 +1509,7 @@ void* wolfSSL_Realloc(void *ptr, size_t size, void* heap, int type) return wolfSSL_Malloc(size, heap, type); #endif } + #ifndef SINGLE_THREADED if (wc_LockMutex(&(mem->memory_mutex)) != 0) { WOLFSSL_MSG("Bad memory_mutex lock"); @@ -1422,7 +1518,8 @@ void* wolfSSL_Realloc(void *ptr, size_t size, void* heap, int type) #endif #ifndef WOLFSSL_STATIC_MEMORY_LEAN - /* case of using fixed IO buffers or IO pool */ + /* case of using fixed IO buffers or IO pool, resized in place + * keeping their owner */ if (((mem->flag & WOLFMEM_IO_POOL)||(mem->flag & WOLFMEM_IO_POOL_FIXED)) && (type == DYNAMIC_TYPE_OUT_BUFFER || type == DYNAMIC_TYPE_IN_BUFFER)) { @@ -1464,6 +1561,8 @@ void* wolfSSL_Realloc(void *ptr, size_t size, void* heap, int type) mem->inUse += pt->sz; mem->alloc += 1; #endif + /* before the old block's ref drops */ + WC_MEM_SET_OWNER(pt, wc_HeapHint_Hold(hint)); /* free memory that was previously being used */ #ifndef SINGLE_THREADED diff --git a/wolfssl/wolfcrypt/memory.h b/wolfssl/wolfcrypt/memory.h index 91200566acb..aa7fe615311 100644 --- a/wolfssl/wolfcrypt/memory.h +++ b/wolfssl/wolfcrypt/memory.h @@ -277,6 +277,8 @@ WOLFSSL_API int wolfSSL_GetAllocators(wolfSSL_Malloc_cb* mf, wc_Memory* inBuf; byte haFlag; /* flag used for checking handshake count */ #endif + word32 refCount; /* SSL + each allocated block */ + byte sslOwned; /* created by InitSSL_StaticMemory */ } WOLFSSL_HEAP_HINT; WOLFSSL_API void* wolfSSL_SetGlobalHeapHint(void* heap); @@ -298,6 +300,7 @@ WOLFSSL_API int wolfSSL_GetAllocators(wolfSSL_Malloc_cb* mf, WOLFSSL_API int wc_LoadStaticMemory(WOLFSSL_HEAP_HINT** pHint, unsigned char* buf, unsigned int sz, int flag, int max); WOLFSSL_API void wc_UnloadStaticMemory(WOLFSSL_HEAP_HINT* heap); + WOLFSSL_LOCAL void wc_HeapHint_Release(void* heap); WOLFSSL_API int wolfSSL_GetMemStats(WOLFSSL_HEAP* heap, WOLFSSL_MEM_STATS* stats);