Reports
BAJA[OTROS]#3969255

CVE-2026-80229: uso tras liberación en libcurl con OpenSSL 3.x al reutilizar conexiones TLS con CURLOPT_SSLENGINE

libcurl liberaba el contexto de librería de OpenSSL al limpiar un handle, aunque una conexión TLS guardada en el pool seguía apuntando a él; reutilizarla desde otro handle provocaba un use-after-free.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

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_PROVIDERS definido).
  • Al menos un easy handle con CURLOPT_SSLENGINE configurado.
  • 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:

  1. 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
  1. Dentro de ossl_set_provider, se llama a OSSL_LIB_CTX_new() y el resultado se guarda en data->state.libctx.
  2. Añadir A a un multi handle y hacer una transferencia TLS. Durante la conexión, ossl_connect_common → ossl_connect_step1 crea el contexto TLS pasándole el libctx del handle, que queda referenciado dentro del SSL_CTX:
SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method)
  1. Al terminar la transferencia, curl_multi_remove_handle separa el handle A y la conexión TLS pasa al pool del multi handle.
  2. 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)
  1. Añadir al mismo multi handle un nuevo handle B con los mismos parámetros de conexión. Curl_ssl_conn_config_match encuentra la conexión del pool, porque el engine/proveedor no se compara en match_ssl_primary_config.
  2. B reutiliza la conexión; cualquier operación SSL sobre ese SSL_CTX accede al libctx ya 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_SSLENGINE con 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:

  1. Contar referencias del contexto de librería y compartir su propiedad entre el easy handle y el SSL_CTX del filtro de conexión, liberándolo solo cuando ambos hayan terminado.
  2. Pasar la propiedad a la estructura de la conexión (struct ossl_ctx): mover el libctx de data->state a octx al llamar a SSL_CTX_new_ex, de modo que lo libere ossl_close (cierre de la conexión) y no ossl_close_all (cierre del handle).
  3. Duplicar el contexto de librería para cada conexión, de forma que cada SSL_CTX tenga 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ó.