Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 35 additions & 38 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -8088,7 +8088,7 @@ int InitSSL_Suites(WOLFSSL* ssl)
#ifdef OPENSSL_EXTRA
/* Apply the protocol version mask and downgrade rules. Returns VERSION_ERROR
* when the requested options are inconsistent, 0 otherwise. */
static int SetSSL_CTX_CheckVersion(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
int SetSSL_CTX_CheckVersion(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
{
#ifdef WOLFSSL_TLS13
if (ssl->version.minor == TLSv1_3_MINOR &&
Expand Down Expand Up @@ -10652,6 +10652,33 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl)
}

/* Free any handshake resources no longer needed */
/* Free the ephemeral key exchange material. Each handshake, a renegotiation
* included, makes its own. */
void FreeEphemeralKeys(WOLFSSL* ssl)
{
#if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448)
/* Present holds the type; only an ECC key is left allocated without it. */
FreeKey(ssl, (ssl->eccTempKeyPresent != 0) ? ssl->eccTempKeyPresent :
DYNAMIC_TYPE_ECC, (void**)&ssl->eccTempKey);
ssl->eccTempKeyPresent = 0;
#endif
#ifndef NO_DH
if (ssl->buffers.serverDH_Priv.buffer != NULL) {
ForceZero(ssl->buffers.serverDH_Priv.buffer,
ssl->buffers.serverDH_Priv.length);
}
XFREE(ssl->buffers.serverDH_Priv.buffer, ssl->heap,
DYNAMIC_TYPE_PRIVATE_KEY);
ssl->buffers.serverDH_Priv.buffer = NULL;
ssl->buffers.serverDH_Priv.length = 0;
XFREE(ssl->buffers.serverDH_Pub.buffer, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
ssl->buffers.serverDH_Pub.buffer = NULL;
ssl->buffers.serverDH_Pub.length = 0;
FreeKey(ssl, DYNAMIC_TYPE_DH, (void**)&ssl->buffers.serverDH_Key);
#endif
(void)ssl;
}

void FreeHandshakeResources(WOLFSSL* ssl)
{
WOLFSSL_ENTER("FreeHandshakeResources");
Expand All @@ -10675,6 +10702,8 @@ void FreeHandshakeResources(WOLFSSL* ssl)
}
#endif

FreeEphemeralKeys(ssl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 pre-existing, not blocking: pre-existing: DTLS 1.2 servers, and connections using wolfSSL_KeepHandshakeResources(), still reuse the previous handshake's ECDHE/X25519/X448 key on renegotiation. The new free sits only in FreeHandshakeResources() (internal.c:10705). DTLS calls that only after application data arrives (internal.c:26937), and keepResources skips it (ssl_api_hs.c:301). Fix: free the ephemeral keys when each new handshake starts, e.g. in wolfssl_rehandshake_prepare() and when a server accepts a renegotiation ClientHello, so freshness never depends on FreeHandshakeResources().
A small fix can ride a push you are already making; otherwise a short reply is enough.

Why this was flagged

For DTLS, wolfssl_handshake_done() sets dtlsHsRetain instead of calling FreeHandshakeResources() (ssl_api_hs.c:300-308). The only later call is in DoProcessAppDataRecord(), and only when application data arrives with no SCR in progress (internal.c:26937-26948). So if the client sends its renegotiation ClientHello before any application data, or the server calls wolfSSL_Rehandshake() first, eccTempKey and eccTempKeyPresent from handshake 1 are still set. wolfssl_rehandshake_prepare() (ssl_api_ext.c:1051-1105) and the client-initiated path free nothing. SendServerKeyExchange then skips EccMakeKey/X25519MakeKey/X448MakeKey because eccTempKeyPresent != 0 (internal.c:41873, 41901, 41928) and signs the old ephemeral public key again. TLS connections with options.keepResources set never call FreeHandshakeResources() (ssl_api_hs.c:301, tls13.c:17100), so they reuse the key the same way. The base branch behaves the same on these paths.

Verification: pre-existing. Trigger: a DTLS 1.2 connection renegotiates before the server has processed any application data, or the app has called wolfSSL_KeepHandshakeResources(). In both cases the server's ECDHE/X25519/X448 temp key from handshake 1 is reused. SendServerKeyExchange regenerates the key only when ssl->eccTempKeyPresent == 0 (internal.c:41873, 41901, 41928).


#ifdef HAVE_SECURE_RENEGOTIATION
/* advertiseOnly clients never renegotiate, so do not retain (and leave
* unzeroed) the master secret and other handshake resources for them. */
Expand Down Expand Up @@ -10776,35 +10805,6 @@ void FreeHandshakeResources(WOLFSSL* ssl)
FreeKey(ssl, DYNAMIC_TYPE_ECC, (void**)&ssl->peerEccKey);
ssl->peerEccKeyPresent = 0;
#endif
#if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448)
{
int dtype;
#ifdef HAVE_ECC
dtype = DYNAMIC_TYPE_ECC;
#elif defined(HAVE_CURVE25519)
dtype = DYNAMIC_TYPE_CURVE25519;
#else
dtype = DYNAMIC_TYPE_CURVE448;
#endif
#if defined(HAVE_ECC) && defined(HAVE_CURVE25519)
if (ssl->peerX25519KeyPresent ||
ssl->eccTempKeyPresent == DYNAMIC_TYPE_CURVE25519)
{
dtype = DYNAMIC_TYPE_CURVE25519;
}
#endif
#if (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \
defined(HAVE_CURVE448)
if (ssl->peerX448KeyPresent ||
ssl->eccTempKeyPresent == DYNAMIC_TYPE_CURVE448)
{
dtype = DYNAMIC_TYPE_CURVE448;
}
#endif
FreeKey(ssl, dtype, (void**)&ssl->eccTempKey);
ssl->eccTempKeyPresent = 0;
}
#endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */
#ifdef HAVE_CURVE25519
FreeKey(ssl, DYNAMIC_TYPE_CURVE25519, (void**)&ssl->peerX25519Key);
ssl->peerX25519KeyPresent = 0;
Expand All @@ -10815,14 +10815,6 @@ void FreeHandshakeResources(WOLFSSL* ssl)
#endif

#ifndef NO_DH
if (ssl->buffers.serverDH_Priv.buffer) {
ForceZero(ssl->buffers.serverDH_Priv.buffer,
ssl->buffers.serverDH_Priv.length);
}
XFREE(ssl->buffers.serverDH_Priv.buffer, ssl->heap, DYNAMIC_TYPE_PRIVATE_KEY);
ssl->buffers.serverDH_Priv.buffer = NULL;
XFREE(ssl->buffers.serverDH_Pub.buffer, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
ssl->buffers.serverDH_Pub.buffer = NULL;
/* Release the parameters (p,g) back here rather than hold them for
* the life of the connection. */
if (ssl->buffers.weOwnDH) {
Expand Down Expand Up @@ -43939,6 +43931,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
}

XMEMCPY(ssl->clSuites->suites, input + i, ssl->clSuites->suiteSz);
#ifdef OPENSSL_EXTRA
/* Built from the previous hello's list. */
wolfSSL_sk_SSL_CIPHER_free(ssl->clSuitesStack);
ssl->clSuitesStack = NULL;
#endif

#ifdef HAVE_SERVER_RENEGOTIATION_INFO
/* check for TLS_EMPTY_RENEGOTIATION_INFO_SCSV suite */
Expand Down
52 changes: 52 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5823,8 +5823,29 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
#endif
}

/* The peer's version is not the limit for the next connection. */
if (ssl->options.downgrade) {
ssl->version.minor = ssl->options.versionSet ?
ssl->options.maxVersionMinor : ssl->ctx->method->version.minor;
#ifdef OPENSSL_EXTRA
/* The version mask only covers TLS. */
if (!ssl->options.dtls &&
(SetSSL_CTX_CheckVersion(ssl, ssl->ctx) != 0)) {
return WOLFSSL_FAILURE;
}
#endif
}
Comment on lines +5827 to +5837

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 In builds without OPENSSL_EXTRA, a reused WOLFSSL can negotiate TLS 1.3 after wolfSSL_clear() even though wolfSSL_set_options() disabled it. wolfSSL_clear() now raises version.minor back to the method maximum. It re-applies ssl->options.mask only under OPENSSL_EXTRA (ssl.c:5830). Both functions are compiled in every build, and there the lowered ssl->version is the only TLS 1.3 block. Fix: re-apply the mask in every build after the version reset, e.g. take SetSSL_CTX_CheckVersion() out of the OPENSSL_EXTRA guard or repeat the step-down from wolfSSL_set_options().

Why this was flagged

Take a default build (no OPENSSL_EXTRA, HAVE_WEBSERVER or WOLFSSL_WPAS_SMALL) and a server made with wolfSSLv23_server_method(). The app calls wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3), which is unguarded (ssl.c:7665, ssl.h:3282) and lowers ssl->version.minor to TLSv1_2_MINOR (ssl.c:7679-7682). After the first connection the app calls wolfSSL_clear(), which is also public in every build (ssl.h:3589). ssl.c:5828 sets version.minor = ctx->method->version.minor (TLSv1_3_MINOR), and the SetSSL_CTX_CheckVersion() call at ssl.c:5833 is compiled out by #ifdef OPENSSL_EXTRA. The server's supported_versions parser (tls.c:7530-7563) only compares against ssl->version.minor and never reads the mask, so the next accept negotiates TLS 1.3. On the client side, the supported_versions writer checks WOLFSSL_OP_NO_TLSv1_3 only under OPENSSL_EXTRA/HAVE_WEBSERVER/WOLFSSL_WPAS_SMALL (tls.c:7403-7406), so a client offers TLS 1.3 again too. On the base branch wolfSSL_clear() did not touch ssl->version, so the disabled version stayed off for every reuse.

Verification: wolfSSL_clear (ssl.c:5793) is also compiled in every build. The only call that re-applies the mask, SetSSL_CTX_CheckVersion (ssl.c:5830-5836), is inside #ifdef OPENSSL_EXTRA. So in these builds the lowered ssl->version is the only thing that blocks TLS 1.3, and wolfSSL_clear now undoes it. The base wolfSSL_clear did not touch version.minor, so TLS 1.3 stayed blocked.


/* reset error */
ssl->error = 0;
ssl->alert_history.last_rx.code = -1;
ssl->alert_history.last_rx.level = -1;
ssl->alert_history.last_tx.code = -1;
ssl->alert_history.last_tx.level = -1;
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \
defined(OPENSSL_ALL)
ssl->peerVerifyRet = 0;
#endif

/* reset option bits */
ssl->options.isClosed = 0;
Expand Down Expand Up @@ -5929,9 +5950,14 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
#ifdef HAVE_CURVE448
ssl->peerX448KeyPresent = 0;
#endif
ssl->pssAlgo = 0;
FreeEphemeralKeys(ssl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Async-crypt builds can hit a use-after-free when a server abandons a handshake that is waiting on hardware and reuses the object with wolfSSL_clear(). FreeEphemeralKeys() now frees eccTempKey and serverDH_Key there. A pending key generation still has its event in ssl->ctx->event_queue, and ssl->asyncDev still points into the freed key. The block at ssl.c:5985 removes the event only when asyncDev is &ssl->kdfAsyncDev. Fix: before FreeEphemeralKeys(), when ssl->asyncDev is set, remove its event from the CTX queue and set ssl->asyncDev to NULL, generalising the kdfAsyncDev block to any pending device.

Why this was flagged

This needs a WOLFSSL_ASYNC_CRYPT build. A TLS 1.2 ECDHE or DHE server's SendServerKeyExchange makes eccTempKey (internal.c:41929) or serverDH_Key, and gets WC_PENDING_E. wolfSSL_AsyncPush (internal.c:48182-48185) then sets ssl->asyncDev to the key's asyncDev and pushes its event onto ssl->ctx->event_queue. If the application gives up on that handshake and calls wolfSSL_clear(), the new FreeEphemeralKeys() call at ssl.c:5954 frees the key, which holds the WC_ASYNC_DEV and its WOLF_EVENT. The cleanup at ssl.c:5985-5993 only removes the event and clears ssl->asyncDev when asyncDev == &ssl->kdfAsyncDev. So the CTX event queue keeps a pointer into freed memory, and ssl->asyncDev dangles. The next wolfSSL_CTX_AsyncPoll / wolfSSL_AsyncPoll (ssl.c:9429/9442), or the reused object's next connect/accept, then reads or writes freed memory. On the base branch wolfSSL_clear() did not free these keys, so the event stayed valid until wolfSSL_free().

Verification: The diff adds FreeEphemeralKeys(ssl); to wolfSSL_clear (ssl.c:5954). So after clear, the ctx event queue holds a pointer into freed memory. wolfSSL_clear's only queue cleanup is limited to the KDF device: if (ssl->asyncDev == &ssl->kdfAsyncDev) (ssl.c:5985-5993). On the base, wolfSSL_clear did not free eccTempKey or serverDH_Key, so the queued event still pointed at live memory.

#ifdef WOLFSSL_DTLS
ssl->options.dtlsStateful = 0;
#endif
#if defined(WOLFSSL_DTLS_CID) && defined(WOLFSSL_DTLS13)
ssl->options.haveSupportedVersions = 0;
#endif
#ifdef WOLFSSL_TLS13
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
ssl->options.pskNegotiated = 0;
Expand Down Expand Up @@ -6007,6 +6033,21 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
TLSX_CertificateAuthorities_FreeAll(ssl->ws_peer_ca_names, ssl->heap);
ssl->ws_peer_ca_names = NULL;
#endif
#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA)
wolfSSL_sk_X509_NAME_pop_free(ssl->peer_ca_names, NULL);
ssl->peer_ca_names = NULL;
#endif
#ifdef OPENSSL_EXTRA
wolfSSL_sk_X509_pop_free(ssl->peerCertChain, NULL);
ssl->peerCertChain = NULL;
wolfSSL_sk_X509_pop_free(ssl->verifiedChain, NULL);
ssl->verifiedChain = NULL;
#endif
Comment on lines +6036 to +6045

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Apps that read the peer chain or CA list after a clean shutdown now hit freed memory. Before this change those stacks lasted until wolfSSL_free(). wolfSSL_shutdown() calls wolfSSL_clear() once the shutdown succeeds (src/ssl_api_rw.c:1216, OPENSSL_EXTRA/WPAS_SMALL). The new code there frees peerCertChain, verifiedChain and peer_ca_names, but the getters hand out those exact pointers. Fix: free these stacks only when the next handshake starts, or skip freeing them on the shutdown path. They must stay valid until wolfSSL_free() or the next connection, as the getter docs promise.

Why this was flagged

An OPENSSL_EXTRA application calls wolfSSL_get_peer_cert_chain(), which returns ssl->peerCertChain itself (src/ssl_api_cert.c:3112). The docs say that stack "is owned by the SSL/TLS object and is disposed of with it". wolfSSL_get0_verified_chain() (verifiedChain) and the get0_peer_CA_list/get_client_CA_list getters (src/ssl_api_cert.c:1573, 1657, peer_ca_names) work the same way. The app then calls wolfSSL_shutdown(). When that returns WOLFSSL_SUCCESS, it calls wolfSSL_clear() (src/ssl_api_rw.c:1214-1219). The new lines at src/ssl.c:6036-6045 pop-free all three stacks there. If the app reads the chain after shutdown, for example to log the peer subject before wolfSSL_free(), it now reads freed X509 objects. On the base branch these stacks lived until wolfSSL_ResourceFree (src/internal.c:10612-10624). The same shutdown-time clear now also zeroes peerVerifyRet and alert_history, so wolfSSL_get_verify_result() and wolfSSL_get_alert_history() called after shutdown report a clean result instead of the real one.

Verification: Trigger: an OPENSSL_EXTRA build where the app keeps a stack pointer from wolfSSL_get_peer_cert_chain, calls wolfSSL_shutdown() until it succeeds, and then reads that stack. src/ssl_api_rw.c:1213-1220 calls wolfSSL_clear(ssl), which the diff makes free peerCertChain, verifiedChain and peer_ca_names (src/ssl.c ~6036-6044). In the base src/ssl.c, wolfSSL_clear never touches these stacks, so this use-after-free is a regression.

#if defined(HAVE_TLS_EXTENSIONS) && defined(HAVE_ALPN)
XFREE(ssl->alpn_peer_requested, ssl->heap, DYNAMIC_TYPE_ALPN);
ssl->alpn_peer_requested = NULL;
ssl->alpn_peer_requested_length = 0;
#endif

#if defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS)
TLSX_FreeAll(ssl->extensions, ssl->heap);
Expand Down Expand Up @@ -6034,6 +6075,15 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
ssl->buffers.clearOutputBuffer.length = 0;
ssl->buffers.inputBuffer.idx = 0;
ssl->buffers.inputBuffer.length = 0;
/* Output the last connection could not send is not the next peer's. */
ShrinkOutputBuffer(ssl);
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
ssl->pendingAlert.code = 0;
ssl->pendingAlert.level = alert_none;
#ifdef WOLFSSL_ASYNC_IO
FreeAsyncCtx(ssl, 1);
#endif
/* Recycling the object for a new connection must not carry the
* previous connection's key material along with it. A freshly
* created object has all of this zeroed. */
Expand Down Expand Up @@ -6130,6 +6180,8 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,

FreeCiphers(ssl);
InitCiphers(ssl);
ssl->encrypt.state = CIPHER_STATE_BEGIN;
ssl->decrypt.state = CIPHER_STATE_BEGIN;
InitCipherSpecs(&ssl->specs);

if (InitSSL_Suites(ssl) != WOLFSSL_SUCCESS)
Expand Down
5 changes: 5 additions & 0 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -8441,6 +8441,11 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
if (ssl->clSuites->suiteSz > WOLFSSL_MAX_SUITE_SZ)
ERROR_OUT(BUFFER_ERROR, exit_dch);
XMEMCPY(ssl->clSuites->suites, input + args->idx, ssl->clSuites->suiteSz);
#ifdef OPENSSL_EXTRA
/* Built from the previous hello's list. */
wolfSSL_sk_SSL_CIPHER_free(ssl->clSuitesStack);
ssl->clSuitesStack = NULL;
#endif
args->idx += ssl->clSuites->suiteSz;
ssl->clSuites->hashSigAlgoSz = 0;
#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG)
Expand Down
Loading
Loading