curl · Report #3761647
TLS conn reuse and session cache ignore fsslctx callback and ssl_config_data flags ( incomplete fix variant of 7541ae569 )
How it was found
Summary
match_ssl_primary_config() in lib/vtls/vtls.c:194 and the session-cache key built by cf_ssl_peer_key_build() in lib/vtls/vtls_scache.c:240 both compare only struct ssl_primary_config fields when deciding whether to reuse a TLS connection or cached session. Several fields that materially change the TLS handshake outcome live in struct ssl_config_data instead and are not compared. When two easy handles share a connection pool (curl_share or curl_multi) and target the same host but differ on any of these fields, the second handle silently inherits the first handle's TLS session - including any security weakening applied by CURLOPT_SSL_CTX_FUNCTION.
This is an incomplete-fix variant of commit 7541ae569 (PR #21667, Joshua Rogers / AISLE Research, merged 2026-05-19), which moved cert, cert_type, key, key_type, key_passwd, and cert_blob/key_blob from ssl_config_data into ssl_primary_config for exactly this reason. The following fields were missed and remain in ssl_config_data:
fsslctx/fsslctxp(CURLOPT_SSL_CTX_FUNCTION/CURLOPT_SSL_CTX_DATA) - application callback that can arbitrarily modifySSL_CTX(disable verification, install custom cert-verify callback, change cipher suite)auto_client_cert(CURLSSLOPT_AUTO_CLIENT_CERT, Schannel) - automatic client certificate selectionearlydata(TLS 1.3 0-RTT) - enables early data which changes replay-attack surfaceno_revoke/revoke_best_effort- certificate revocation check flagsno_partialchain- chain verification strictnessnative_ca_store- OS certificate store usage
Affected version
Tested on libcurl HEAD 230a98663 (origin/master, 2026-05-26) with OpenSSL backend. The bug exists in all versions since CURLOPT_SSL_CTX_FUNCTION was introduced, but became more impactful after PR #21667 (2026-05-19) explicitly established the pattern of moving security-relevant fields into ssl_primary_config for comparison - and missed these.
curl 8.14.0-DEV (x86_64-pc-linux-gnu) libcurl/8.14.0-DEV OpenSSL/3.5.0
Steps To Reproduce
- Start a self-signed HTTPS server (not in the host CA bundle):
openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem \
-days 1 -nodes -subj "/CN=poc.local"
openssl s_server -cert c.pem -key k.pem -accept 14443 -WWW &
echo "PoC OK" > /tmp/poc_index.html
- Compile the attached
poc.c:
gcc -o poc poc.c -lcurl -lssl
- Run the PoC:
./poc https://localhost:14443/
- Observed output (BUGGY - current upstream):
[A: CB-installed ] rc= 0 (No error) http=200
[B: no-CB ] rc= 0 (No error) http=200
Handle A installs SSL_CTX_set_cert_verify_callback() via CURLOPT_SSL_CTX_FUNCTION that approves every certificate chain. Handle B has NO callback and CURLOPT_SSL_VERIFYPEER=1 (strict). Both share a curl_share with CURL_LOCK_DATA_CONNECT and CURL_LOCK_DATA_SSL_SESSION. B silently reuses A's TLS session and inherits A's loosened verification.
- Control test (without share - B correctly fails):
# Compile and run the control (no curl_share):
gcc -o poc_noshare poc_noshare.c -lcurl -lssl
./poc_noshare https://localhost:14443/
Output:
[A] rc=0 (No error) http=200
[B] rc=60 (SSL peer certificate or SSH remote key was not OK) http=0
Without the shared pool, B correctly rejects the self-signed certificate. This differential proves the connection/session reuse is the bypass path.
Again: there is no bug-bounty for curl. We do not offer any rewards, only proper credits.
Understood. Submitting for CVE assignment and credit.
Suggested fix
Move the missing fields into ssl_primary_config (same pattern as PR #21667), or add explicit comparisons in match_ssl_primary_config() and include them in the session-cache key:
// lib/vtls/vtls.c match_ssl_primary_config() -- add:
if(data->ssl_config.fsslctx != needle->ssl_config.fsslctx)
return FALSE;
if(data->ssl_config.fsslctxp != needle->ssl_config.fsslctxp)
return FALSE;
if(data->ssl_config.auto_client_cert != needle->ssl_config.auto_client_cert)
return FALSE;
if(data->ssl_config.earlydata != needle->ssl_config.earlydata)
return FALSE;
if(data->ssl_config.no_revoke != needle->ssl_config.no_revoke)
return FALSE;
if(data->ssl_config.revoke_best_effort != needle->ssl_config.revoke_best_effort)
return FALSE;
if(data->ssl_config.no_partialchain != needle->ssl_config.no_partialchain)
return FALSE;
if(data->ssl_config.native_ca_store != needle->ssl_config.native_ca_store)
return FALSE;
// lib/vtls/vtls_scache.c cf_ssl_peer_key_build() -- include in key hash:
// Append fsslctx pointer value + flags to the key string.
Impact
Summary
An application that uses curl_share or curl_multi to pool TLS connections between easy handles with different SSL configurations will silently inherit the first handle's TLS security posture on subsequent handles. Specifically:
-
TLS verification bypass - Handle A installs
CURLOPT_SSL_CTX_FUNCTIONthat disables certificate verification. Handle B connects to the same host with strictCURLOPT_SSL_VERIFYPEER=1but no callback. B silently reuses A's session and connects without verification - a MitM attacker can intercept B's traffic with a forged certificate. -
Client certificate confusion (Schannel) - Handle A enables
CURLSSLOPT_AUTO_CLIENT_CERTfor automatic client certificate selection. Handle B does not. B may unintentionally present A's client certificate to the server, authenticating as the wrong identity. -
Revocation check bypass - Handle A sets
no_revoke=1. Handle B expects revocation checking. B inherits A's session and skips revocation checks, potentially trusting a revoked certificate.
This is the same class of bug as 7541ae569 (PR #21667, AISLE Research, 2026-05-19) which moved cert/key fields into ssl_primary_config for comparison. The fields listed above were missed in that fix.