curl · Report #3261310
OpenSSL HTTP/3 bogus CURLINFO_TLS_SSL_PTR
How it was found
Summary:
curl_easy_getinfo CURLINFO_TLS_SSL_PTR appears to return invalid SSL connection pointer for OpenSSL HTTP/3 connections. Using this SSL connection results in a crash, and potential other impacts.
This issue does not happen with libcurl 8.14.1, suggesting that the bug is in libcurl itself (or libcurl 8.14.1 somehow didn't trigger a bug in the other support libraries).
Some debug output suggest that this could be a use after free / dangling pointer issue. If so, the issue might lead to remote code execution. This has not been confirmed at time of writing this report.
Affected version
8.15.0 (release) 8.15.1-DEV (7c23e88d17e0939b4e01c8d05f430e167e148f4b)
Steps To Reproduce:
- Compile libcurl again OpenSSL 3.5.1, and fresh enough nghttp2, nghttp3 and ngtcp2 (
--with-openssl --with-nghttp2 --with-ngtcp2 --with-nghttp3) - Compile the following Proof of Concept app with
-fsanitize=address:
#include <curl/curl.h>
#include <openssl/ssl.h>
#include <stdio.h>
static size_t header_callback(char *buffer, size_t size,
size_t nitems, void *userdata)
{
size_t actual = nitems * size;
CURL *curl = (CURL *) userdata;
size_t printactual = actual;
if (printactual && buffer[printactual - 1] == '\n')
{
printactual--;
if (printactual && buffer[printactual - 1] == '\r')
printactual--;
}
printf("H: %.*s\n", (int) printactual, buffer);
if ((actual == 1 && buffer[0] == '\n') ||
(actual == 2 && buffer[0] == '\r' && buffer[1] == '\n'))
{
const struct curl_tlssessioninfo *info = NULL;
CURLcode res = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &info);
printf("D: headers completed, res %d info %p\n", res, info);
if (info && !res)
{
printf("D: backend %d internals %p\n", info->backend, info->internals);
if(CURLSSLBACKEND_OPENSSL == info->backend)
{
SSL *ssl = info->internals;
printf("D: OpenSSL ver. %s\n", SSL_get_version(ssl));
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
printf("D: chain %p\n", chain);
}
}
}
return actual;
}
int main(void)
{
printf("libcurl version: %s\n", curl_version());
CURL *curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.se");
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3ONLY);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, curl);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return 0;
}
- Execute the poc:
$ ./curlpoc
libcurl version: libcurl/8.15.1-DEV OpenSSL/3.5.1 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.66.0 ngtcp2/1.14.0-DEV nghttp3/1.1 OpenLDAP/2.6.10
H: HTTP/3 200
H: content-length: 11441
H: server: nginx/1.27.5
H: content-type: text/html
H: x-frame-options: SAMEORIGIN
H: last-modified: Sun, 20 Jul 2025 02:11:09 GMT
H: etag: "2cb1-63a52df596e87"
H: cache-control: max-age=60
H: expires: Sun, 20 Jul 2025 02:14:40 GMT
H: x-content-type-options: nosniff
H: content-security-policy: default-src 'self' curl.haxx.se www.curl.se curl.se; style-src 'unsafe-inline' 'self' curl.haxx.se www.curl.se curl.se; require-trusted-types-for 'script';
H: strict-transport-security: max-age=31536000
H: via: 1.1 varnish, 1.1 varnish
H: accept-ranges: bytes
H: date: Sun, 20 Jul 2025 04:44:47 GMT
H: age: 69
H: x-served-by: cache-bma-essb1270054-BMA, cache-hel1410021-HEL
H: x-cache: HIT, HIT
H: x-cache-hits: 1, 15
H: x-timer: S1752986688.638651,VS0,VE0
H: vary: Accept-Encoding
H: alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
H:
D: headers completed, res 0 info 0x622000010600
D: backend 1 internals 0x61c000001080
D: OpenSSL ver. unknown
AddressSanitizer:DEADLYSIGNAL
=================================================================
==81895==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000005c9 (pc 0x00010472785c bp 0x00016b6ed690 sp 0x00016b6ed690 T0)
==81895==The signal is caused by a READ memory access.
==81895==Hint: address points to the zero page.
#0 0x00010472785c in SSL_get_peer_cert_chain+0x28 (libssl.3.dylib:arm64+0x1385c)
#1 0x000104700c5c in header_callback+0x2d0 (curlpoc:arm64+0x100000c5c)
#2 0x00010488a494 in cw_out_ptr_flush+0x10c (libcurl.4.dylib:arm64+0x1a494)
#3 0x00010488a000 in cw_out_do_write+0xa8 (libcurl.4.dylib:arm64+0x1a000)
#4 0x000104889c74 in cw_out_write+0x70 (libcurl.4.dylib:arm64+0x19c74)
#5 0x00010488a870 in cw_pause_write+0x200 (libcurl.4.dylib:arm64+0x1a870)
#6 0x0001048c6d1c in cw_download_write+0x94 (libcurl.4.dylib:arm64+0x56d1c)
#7 0x0001048c59cc in Curl_client_write+0x48 (libcurl.4.dylib:arm64+0x559cc)
#8 0x0001048a3180 in http_write_header+0x54 (libcurl.4.dylib:arm64+0x33180)
#9 0x0001048a2068 in http_rw_hd+0x1598 (libcurl.4.dylib:arm64+0x32068)
#10 0x00010489dfac in Curl_http_write_resp_hd+0x28 (libcurl.4.dylib:arm64+0x2dfac)
#11 0x0001048f55ac in cb_h3_end_headers+0xe0 (libcurl.4.dylib:arm64+0x855ac)
#12 0x00010497ecd8 in nghttp3_conn_read_bidi nghttp3_conn.c
#13 0x00010497e4c4 in nghttp3_conn_read_stream nghttp3_conn.c:526
#14 0x0001048f4790 in cb_recv_stream_data+0x4c (libcurl.4.dylib:arm64+0x84790)
#15 0x000104a2c4dc in conn_recv_stream ngtcp2_conn.c:7103
#16 0x000104a2b178 in conn_recv_pkt ngtcp2_conn.c:9077
#17 0x000104a1fcc4 in ngtcp2_conn_read_pkt_versioned ngtcp2_conn.c:9855
#18 0x0001048f5cb8 in recv_pkt+0x80 (libcurl.4.dylib:arm64+0x85cb8)
#19 0x0001048f67c4 in vquic_recv_packets+0x12c (libcurl.4.dylib:arm64+0x867c4)
#20 0x0001048f34cc in cf_ngtcp2_recv+0xe8 (libcurl.4.dylib:arm64+0x834cc)
#21 0x0001048d5730 in Curl_sendrecv+0x254 (libcurl.4.dylib:arm64+0x65730)
#22 0x0001048b96d8 in multi_runsingle+0x980 (libcurl.4.dylib:arm64+0x496d8)
#23 0x0001048b8b68 in curl_multi_perform+0x200 (libcurl.4.dylib:arm64+0x48b68)
#24 0x00010488db84 in curl_easy_perform+0x188 (libcurl.4.dylib:arm64+0x1db84)
#25 0x00010470096c in main+0x84 (curlpoc:arm64+0x10000096c)
#26 0x0001991feb94 in start+0x17b8 (dyld:arm64e+0xfffffffffff3ab94)
==81895==Register values:
x[0] = 0x000061c000001080 x[1] = 0x0000000000000000 x[2] = 0x00000000000120a8 x[3] = 0x0000000000000002
x[4] = 0x0000000104700fa0 x[5] = 0x000000016b6ed690 x[6] = 0x000000016af04000 x[7] = 0x0000000000000001
x[8] = 0x0000000000000301 x[9] = 0x00000002076b8588 x[10] = 0x0000000000000002 x[11] = 0x0000010000000000
x[12] = 0x00000000fffffffd x[13] = 0x0000000000000000 x[14] = 0x0000000000000000 x[15] = 0x0000000000000000
x[16] = 0x0000000104727834 x[17] = 0x00000002086fde70 x[18] = 0x0000000000000000 x[19] = 0x000000016b6ed6e0
x[20] = 0x0000000000000000 x[21] = 0x0000000000000002 x[22] = 0x000061c000001080 x[23] = 0x000000016b6ed6c0
x[24] = 0x0000000000000001 x[25] = 0x000000016b6ed6a0 x[26] = 0x000000702d6fdad4 x[27] = 0x0000007000020000
x[28] = 0x000000010470098c fp = 0x000000016b6ed690 lr = 0x0000000104700c60 sp = 0x000000016b6ed690
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (libssl.3.dylib:arm64+0x1385c) in SSL_get_peer_cert_chain+0x28
==81895==ABORTING
Abort trap: 6
Supporting Material/References:
- This report is AI free for your pleasure. Stop the slop!
Impact
Summary:
- At least: Denial of service when accessing HTTP/3 sites.
- Potential: Memory corruption if the returned pointer points to already free memory. Possible remote code execution, depending on the target platform. Further analysis needed to determine the full impact.