Reports
BAJA[OTROS]#3969368

curl en macOS reutiliza conexiones TLS sin pasar por SecTrust al no comparar native_ca_store (CVE-2026-80231)

En macOS, curl podía reutilizar para una transferencia que esperaba verificación con SecTrust una conexión verificada solo con OpenSSL, porque el campo native_ca_store no se tenía en cuenta al decidir si dos conexiones son equivalentes.

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

curl mantiene un pool de conexiones y, antes de abrir una nueva, busca si ya tiene una abierta con la misma configuración TLS. Esa comparación la hace match_ssl_primary_config(), que solo revisa los campos de struct ssl_primary_config. El problema es que el indicador native_ca_store (que decide si se usa el almacén de certificados del sistema, en macOS a través de SecTrust) no vive en esa estructura, sino en struct ssl_config_data (línea 73 de vtls_config.h), así que nunca entra en la comparación.

En macOS esto tiene consecuencias por cómo se rellena la configuración por defecto. Curl_ssl_easy_config_complete() (línea 258) activa native_ca_store = TRUE automáticamente cuando el usuario no ha indicado ningún CA propio, y lo hace sin tocar ssl_options. Si además curl está compilado con CURL_CA_BUNDLE, la ruta de ese bundle se copia a primary.CAfile (línea 269). El resultado es que dos transferencias pueden tener exactamente los mismos campos "primarios" y, sin embargo, una esperar verificación con SecTrust y la otra no:

  • Transferencia A: fija CURLOPT_CAINFO explícitamente con la misma ruta que CURL_CA_BUNDLE. Queda custom_cafile=TRUE, no se activa el almacén nativo (native_ca_store=FALSE) y el certificado se verifica solo con OpenSSL.
  • Transferencia B: no fija CURLOPT_CAINFO. El autorrelleno pone el mismo CAfile, custom_cafile=FALSE y se activa native_ca_store=TRUE, por lo que debería verificarse con SecTrust.

Como CAfile y ssl_options coinciden, match_ssl_primary_config() da las conexiones por equivalentes y B reutiliza la conexión que abrió A. SecTrust no llega a ejecutarse nunca para B.

El fallo tiene asignado el CVE-2026-80231, con severidad baja.

Pasos de reproducción

El investigador lo reprodujo en un Mac físico con un programa de prueba (poc-native-ca.c) enlazado contra un curl compilado con estas opciones:

-DUSE_APPLE_SECTRUST=ON -DCURL_CA_BUNDLE=<path> -DCURL_USE_OPENSSL=ON
  1. Lanzar una primera transferencia HTTPS (A) fijando CURLOPT_CAINFO con la misma ruta que se usó en CURL_CA_BUNDLE. La verificación la hace OpenSSL.
  2. Lanzar después una segunda transferencia (B) sin fijar CURLOPT_CAINFO. Por el autorrelleno, B tiene native_ca_store=TRUE y debería verificar con SecTrust.
  3. Comprobar que aparece el mensaje:
Reusing existing https: connection

B se sirve por la conexión de A y SecTrust no se ejecuta.

La cadena de causas, en orden:

  1. Curl_ssl_easy_config_complete() (línea 258): si se cumple !custom_capath && !custom_cafile && !custom_cablob, pone native_ca_store = TRUE, sin modificar ssl_options.
  2. El autorrelleno de CURL_CA_BUNDLE (línea 269) fija STRING_SSL_CAFILE, con lo que primary.CAfile pasa a ser la ruta del bundle.
  3. La transferencia A fija CURLOPT_CAINFO a esa misma ruta: custom_cafile=TRUE, no hay activación automática y native_ca_store=FALSE. Mismo primary.CAfile y mismo ssl_options=0 que B.
  4. match_ssl_primary_config() encuentra todos los campos iguales y reutiliza la conexión, porque native_ca_store no forma parte de lo que compara.

Impacto

Una transferencia que espera que el certificado del servidor se valide con la política de confianza del sistema (SecTrust) puede acabar usando, sin aviso, una conexión que solo validó OpenSSL, o al revés. Un certificado que una de las dos políticas acepta y la otra rechazaría pasa sin volver a verificarse. El escenario descrito requiere un build de macOS con SecTrust y OpenSSL activados y CURL_CA_BUNDLE definido.

Remediación

El investigador propuso dos opciones:

  • Mover native_ca_store dentro de ssl_primary_config, para que match_ssl_primary_config() lo compare como el resto de campos.
  • O añadir una comprobación explícita de ese campo en Curl_ssl_conn_config_match().

El reporte figura como resuelto, pero no detalla cuál de las dos se aplicó ni en qué versión.