Resumen
El fallo está en el backend de OpenSSL de libcurl y es un problema de tiempo de vida de un objeto: un recurso que pertenece al easy handle acaba siendo usado por una conexión que sobrevive a ese handle.
Cuando una aplicación compilada contra OpenSSL 3.x fija CURLOPT_SSLENGINE (por ejemplo con el valor "default"), la función ossl_set_provider() crea un OSSL_LIB_CTX exclusivo para ese handle y lo guarda en data->state.libctx. Al montar la conexión TLS, ese contexto se pasa a SSL_CTX_new_ex(), y el SSL_CTX resultante se queda con un puntero a él, pero sin hacerse dueño.
El problema es que el SSL_CTX pertenece al filtro de conexión, no al handle. Si la conexión queda en el pool del multi handle, sigue viva después de que el handle original se destruya. Y al destruirlo, curl_easy_cleanup() libera el OSSL_LIB_CTX con OSSL_LIB_CTX_free(). A partir de ahí, el SSL_CTX de la conexión en el pool apunta a memoria liberada. En cuanto otro handle reutiliza esa conexión y hace cualquier operación SSL (reanudar sesión, repetir el handshake, buscar un cifrado…), se produce un heap use-after-free.
A esto se suma que, al buscar una conexión reutilizable, match_ssl_primary_config no compara el engine/proveedor configurado, así que la conexión "contaminada" se considera apta para cualquier handle con los mismos parámetros de conexión.
Datos del caso:
- CVE: CVE-2026-80229
- Severidad indicada en HackerOne: none
- Enviado: 25-ago-2026; divulgado: 3-sep-2026
Pasos de reproducción
Condiciones necesarias:
- libcurl compilado con OpenSSL 3.x (con
OPENSSL_HAS_PROVIDERSdefinido). - Al menos un easy handle con
CURLOPT_SSLENGINEconfigurado. - Ese handle se limpia mientras su conexión TLS sigue en el pool de un multi handle.
- Otro easy handle reutiliza después esa conexión.
Secuencia completa:
- Configurar el engine en un primer handle (A). La llamada recorre esta cadena:
curl_easy_setopt(CURLOPT_SSLENGINE, "default") → Curl_ssl_set_engine → ossl_set_engine → ossl_set_provider
- Dentro de
ossl_set_provider, se llama aOSSL_LIB_CTX_new()y el resultado se guarda endata->state.libctx. - Añadir A a un multi handle y hacer una transferencia TLS. Durante la conexión,
ossl_connect_common→ossl_connect_step1crea el contexto TLS pasándole ellibctxdel handle, que queda referenciado dentro delSSL_CTX:
SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method)
- Al terminar la transferencia,
curl_multi_remove_handlesepara el handle A y la conexión TLS pasa al pool del multi handle. - Limpiar A. La cadena de cierre libera el contexto de librería:
curl_easy_cleanup(data) → ossl_close_all(data) → ossl_provider_cleanup(data) → OSSL_LIB_CTX_free(data->state.libctx)
- Añadir al mismo multi handle un nuevo handle B con los mismos parámetros de conexión.
Curl_ssl_conn_config_matchencuentra la conexión del pool, porque el engine/proveedor no se compara enmatch_ssl_primary_config. - B reutiliza la conexión; cualquier operación SSL sobre ese
SSL_CTXaccede allibctxya liberado → heap use-after-free.
Impacto
- Compilado con AddressSanitizer, el acceso a memoria liberada provoca un cierre inmediato del proceso.
- Sin ASan el comportamiento es indefinido: la zona de heap liberada puede reasignarse a datos controlados por un atacante, lo que podría permitir corromper el estado interno de OpenSSL.
- En aplicaciones multiusuario que usan
CURLOPT_SSLENGINEcon un multi handle compartido, el problema se repite en cada conexión TLS del pool cuyo handle de origen se limpia antes de que la conexión sea expulsada.
Remediación
La regla de fondo es que el OSSL_LIB_CTX tiene que vivir al menos tanto como cualquier SSL_CTX que lo use. El investigador propuso tres formas de conseguirlo:
- Contar referencias del contexto de librería y compartir su propiedad entre el easy handle y el
SSL_CTXdel filtro de conexión, liberándolo solo cuando ambos hayan terminado. - Pasar la propiedad a la estructura de la conexión (
struct ossl_ctx): mover ellibctxdedata->stateaoctxal llamar aSSL_CTX_new_ex, de modo que lo libereossl_close(cierre de la conexión) y noossl_close_all(cierre del handle). - Duplicar el contexto de librería para cada conexión, de forma que cada
SSL_CTXtenga el suyo. Es más costoso, pero el modelo de tiempo de vida es el más sencillo.
El reporte divulgado no indica cuál de estas opciones se aplicó finalmente ni en qué versión de curl se corrigió.