curl · Report #3969255
CVE-2026-80229: OpenSSL provider use-after-free
How it was found
Summary
When CURLOPT_SSLENGINE is set (e.g. "default" on OpenSSL 3.x), ossl_set_provider() (line 1791) allocates a per-handle OSSL_LIB_CTX and stores it in data->state.libctx. During TLS connection setup, SSL_CTX_new_ex() (line 3750) receives this library context and embeds a pointer to it inside the newly created SSL_CTX. The SSL_CTX is owned by the connection filter attached to the pooled connection — its lifetime is independent of the easy handle.
When curl_easy_cleanup() is called on the originating handle, ossl_close_all() → ossl_provider_cleanup() (line 2001) calls OSSL_LIB_CTX_free(data->state.libctx) (line 1752), destroying the library context. The pooled connection's SSL_CTX now holds a dangling pointer. When a second easy handle reuses this pooled connection via the multi handle's connection cache, any SSL operation — session resumption, re-handshake, cipher lookup — dereferences the freed OSSL_LIB_CTX, producing a heap-use-after-free.
The bug is a lifetime mismatch: data->state.libctx is scoped to the easy handle, but SSL_CTX_new_ex() borrows it without taking ownership, and the resulting SSL_CTX outlives the easy handle via connection pooling.
Preconditions
- libcurl built with OpenSSL 3.x (
OPENSSL_HAS_PROVIDERSdefined) CURLOPT_SSLENGINEset on at least one easy handle (triggersossl_set_provider)- Easy handle cleaned up while its TLS connection remains pooled in a multi handle
- A subsequent easy handle reuses that pooled connection
Root cause trace
curl_easy_setopt(CURLOPT_SSLENGINE, "default")→Curl_ssl_set_engine→ossl_set_engine→ossl_set_provider(line 1768).ossl_set_providercallsOSSL_LIB_CTX_new()(line 1791), stores result indata->state.libctx.- On TLS connect,
ossl_connect_common→ossl_connect_step1reachesSSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method)(line 3750). TheSSL_CTXstores thelibctxpointer internally. - Transfer completes.
curl_multi_remove_handledetaches the easy handle; the TLS connection moves to the multi handle's connection pool. curl_easy_cleanup(data)→ossl_close_all(data)→ossl_provider_cleanup(data)→OSSL_LIB_CTX_free(data->state.libctx)(line 1752). Thelibctxis freed.- A new easy handle B with matching connection parameters is added to the same multi handle.
Curl_ssl_conn_config_matchfinds the pooled connection (engine/provider is not compared inmatch_ssl_primary_config). - B reuses the pooled connection. Any SSL operation on the
SSL_CTXdereferences the freedlibctx→ heap-use-after-free.
Remediation
The OSSL_LIB_CTX must outlive every SSL_CTX that references it. Options:
- Reference-count the library context and share ownership between the easy handle and the connection filter's
SSL_CTX. Release only when both are done. - Transfer ownership to the SSL_CTX wrapper (
struct ossl_ctx): movelibctxout ofdata->stateintooctxwhenSSL_CTX_new_exis called.ossl_close(connection teardown) frees it instead ofossl_close_all(handle teardown). - Duplicate the library context per connection, so each
SSL_CTXowns its own copy. More expensive but simplest lifetime model.
Impact
Under ASan, the dereference triggers an immediate crash. Without ASan, behavior is undefined: the freed heap region may be reallocated to attacker-controlled data, potentially allowing corruption of OpenSSL internal state. In a multi-tenant application using CURLOPT_SSLENGINE with a shared multi handle, this creates a use-after-free on every pooled TLS connection whose originating easy handle is cleaned up before the connection is evicted.