HexBrain

curl · Report #3969255

CVE-2026-80229: OpenSSL provider use-after-free

Validstatus
7upvotes
3 Sept 2026disclosed

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_PROVIDERS defined)
  • CURLOPT_SSLENGINE set on at least one easy handle (triggers ossl_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

  1. curl_easy_setopt(CURLOPT_SSLENGINE, "default") → Curl_ssl_set_engine → ossl_set_engine → ossl_set_provider (line 1768).
  2. ossl_set_provider calls OSSL_LIB_CTX_new() (line 1791), stores result in data->state.libctx.
  3. On TLS connect, ossl_connect_common → ossl_connect_step1 reaches SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method) (line 3750). The SSL_CTX stores the libctx pointer internally.
  4. Transfer completes. curl_multi_remove_handle detaches the easy handle; the TLS connection moves to the multi handle's connection pool.
  5. curl_easy_cleanup(data) → ossl_close_all(data) → ossl_provider_cleanup(data) → OSSL_LIB_CTX_free(data->state.libctx) (line 1752). The libctx is freed.
  6. A new easy handle B with matching connection parameters is added to the same multi handle. Curl_ssl_conn_config_match finds the pooled connection (engine/provider is not compared in match_ssl_primary_config).
  7. B reuses the pooled connection. Any SSL operation on the SSL_CTX dereferences the freed libctx → heap-use-after-free.

Remediation

The OSSL_LIB_CTX must outlive every SSL_CTX that references it. Options:

  1. 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.
  2. Transfer ownership to the SSL_CTX wrapper (struct ossl_ctx): move libctx out of data->state into octx when SSL_CTX_new_ex is called. ossl_close (connection teardown) frees it instead of ossl_close_all (handle teardown).
  3. Duplicate the library context per connection, so each SSL_CTX owns 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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original