I did this
I built the current curl master (87704c4) with AddressSanitizer and LeakSanitizer enabled, then imported serialized SSL session data containing two CURL_SPACK_TICKET fields.
The second ticket replaces the pointer stored for the first ticket without freeing the first allocation.
Minimal reproducer:
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IMPORT_COUNT 128
#define FIRST_TICKET_SIZE 4096
static unsigned char *make_packet(size_t *packet_len)
{
const size_t total =
1 + /* format version */
1 + 2 + FIRST_TICKET_SIZE + /* first ticket */
1 + 2 + 1; /* second ticket */
unsigned char *packet = malloc(total);
unsigned char *p;
if(!packet)
return NULL;
p = packet;
/* CURL_SPACK_VERSION */
*p++ = 0x01;
/* First CURL_SPACK_TICKET: 4096 bytes. */
*p++ = 0x04;
*p++ = (unsigned char)(FIRST_TICKET_SIZE >> 8);
*p++ = (unsigned char)FIRST_TICKET_SIZE;
memset(p, 'A', FIRST_TICKET_SIZE);
p += FIRST_TICKET_SIZE;
/* Second CURL_SPACK_TICKET: one byte. */
*p++ = 0x04;
*p++ = 0x00;
*p++ = 0x01;
*p++ = 'B';
*packet_len = total;
return packet;
}
int main(void)
{
unsigned char *packet;
size_t packet_len;
CURLSH *share;
CURL *easy;
CURLSHcode shrc;
CURLcode result = CURLE_OK;
int i;
packet = make_packet(&packet_len);
if(!packet)
return 1;
result = curl_global_init(CURL_GLOBAL_DEFAULT);
if(result != CURLE_OK)
return 1;
share = curl_share_init();
easy = curl_easy_init();
if(!share || !easy)
return 1;
shrc = curl_share_setopt(share,
CURLSHOPT_SHARE,
CURL_LOCK_DATA_SSL_SESSION);
if(shrc != CURLSHE_OK)
return 1;
result = curl_easy_setopt(easy, CURLOPT_SHARE, share);
if(result != CURLE_OK)
return 1;
for(i = 0; i < IMPORT_COUNT; ++i) {
result = curl_easy_ssls_import(easy,
"example.test:443",
NULL,
0,
packet,
packet_len);
if(result != CURLE_OK) {
fprintf(stderr,
"import %d failed: %d (%s)\n",
i,
(int)result,
curl_easy_strerror(result));
break;
}
}
free(packet);
curl_easy_cleanup(easy);
curl_share_cleanup(share);
curl_global_cleanup();
return result == CURLE_OK ? 0 : 1;
}
I configured curl on Fedora Linux with:
cmake -S . -B build-asan-linux -G Ninja \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_STATIC_LIBS=OFF \
-DBUILD_CURL_EXE=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_EXAMPLES=OFF \
-DUSE_SSLS_EXPORT=ON \
-DCURL_USE_OPENSSL=ON \
-DCURL_ZLIB=OFF \
-DCURL_BROTLI=OFF \
-DCURL_ZSTD=OFF \
-DUSE_NGHTTP2=OFF \
-DUSE_LIBIDN2=OFF \
-DCURL_USE_LIBPSL=OFF \
-DCURL_USE_LIBSSH2=OFF \
-DCURL_USE_LIBSSH=OFF \
-DCURL_DISABLE_LDAP=ON \
-DCURL_DISABLE_LDAPS=ON \
-DCMAKE_C_FLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address"
I then built the local shared libcurl:
cmake --build build-asan-linux \
--target libcurl_shared \
-j"$(nproc)"
I compiled the reproducer against the locally built libcurl:
LIBCURL_SO="$(
find "$PWD/build-asan-linux/lib" \
-maxdepth 1 \
-name 'libcurl*.so*' \
-print |
head -1
)"
clang \
-O1 \
-g \
-fsanitize=address \
-fno-omit-frame-pointer \
-fno-optimize-sibling-calls \
-I"$PWD/include" \
ssls_import_leak.c \
"$LIBCURL_SO" \
-Wl,-rpath,"$PWD/build-asan-linux/lib" \
-o ssls_import_leak
Run:
ASAN_OPTIONS=detect_leaks=1:halt_on_error=0 \
LSAN_OPTIONS=report_objects=1 \
./ssls_import_leak
LeakSanitizer reports:
==1383615==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 524416 byte(s) in 128 object(s) allocated from:
#0 0x0000004a6f98 in malloc (/omitted/curl-master/ssls_import_leak+0x4a6f98) (BuildId: d8f7c4dc12985f17896846f2707c00052f903097)
#1 0x7f526dd85a9f in curlx_memdup0 /omitted/curl-master/lib/curlx/strdup.c:87:37
#2 0x7f526dd80bfc in spack_decdata16 /omitted/curl-master/lib/vtls/vtls_spack.c:179:10
#3 0x7f526dd80bfc in Curl_ssl_session_unpack /omitted/curl-master/lib/vtls/vtls_spack.c:298:16
#4 0x7f526dd7ccd0 in Curl_ssl_session_import /omitted/curl-master/lib/vtls/vtls_scache.c:1193:12
#5 0x7f526dc61fe1 in curl_easy_ssls_import /omitted/curl-master/lib/easy.c:1350:10
#6 0x000000400a96 in main /omitted/curl-master/ssls_import_leak.c:177:14
#7 0x7f526d8f3680 in __libc_start_call_main (/lib64/libc.so.6+0x3680) (BuildId: 18472003bbf1c5f098a09b5016b9b8bd4c7c59f0)
#8 0x7f526d8f3797 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3797) (BuildId: 18472003bbf1c5f098a09b5016b9b8bd4c7c59f0)
#9 0x000000400ee4 in _start (/omitted/curl-master/ssls_import_leak+0x400ee4) (BuildId: d8f7c4dc12985f17896846f2707c00052f903097)
524416 is 128 * 4097. Each import leaks the first 4096-byte ticket allocation plus the extra terminating byte allocated by curlx_memdup0().
The relevant code is:
case CURL_SPACK_TICKET:
result = spack_decdata16(&pval8, &s->sdata_len, &buf, end);
s->sdata = pval8;
break;
When another CURL_SPACK_TICKET field is encountered, s->sdata is overwritten without freeing the allocation associated with the previous field.
I expected the following
Malformed serialized session data containing duplicate ticket fields should either be rejected or handled without losing the previous allocation.
curl/libcurl version
curl master (87704c4)
operating system
Fedora Linux 44
7.0.12-201.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Jun 11 01:30:16 UTC 2026 x86_64 GNU/Linux
I did this
I built the current curl master (87704c4) with AddressSanitizer and LeakSanitizer enabled, then imported serialized SSL session data containing two
CURL_SPACK_TICKETfields.The second ticket replaces the pointer stored for the first ticket without freeing the first allocation.
Minimal reproducer:
I configured curl on Fedora Linux with:
I then built the local shared libcurl:
cmake --build build-asan-linux \ --target libcurl_shared \ -j"$(nproc)"I compiled the reproducer against the locally built libcurl:
Run:
LeakSanitizer reports:
524416is128 * 4097. Each import leaks the first 4096-byte ticket allocation plus the extra terminating byte allocated bycurlx_memdup0().The relevant code is:
When another
CURL_SPACK_TICKETfield is encountered,s->sdatais overwritten without freeing the allocation associated with the previous field.I expected the following
Malformed serialized session data containing duplicate ticket fields should either be rejected or handled without losing the previous allocation.
curl/libcurl version
curl master (87704c4)
operating system
Fedora Linux 44
7.0.12-201.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Jun 11 01:30:16 UTC 2026 x86_64 GNU/Linux