OpenSSL HTTP/3 bogus CURLINFO_TLS_SSL_PTR
C
curl
Submitted None
Actions:
Reported by
nyymi
Vulnerability Details
Technical details and impact analysis
## 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:
1. Compile libcurl again OpenSSL 3.5.1, and fresh enough nghttp2, nghttp3 and ngtcp2 (`--with-openssl --with-nghttp2 --with-ngtcp2 --with-nghttp3`)
2. 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;
}
```
3. 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.
Report Details
Additional information and metadata
State
Closed
Substate
Informative
Submitted
Weakness
Use After Free