Buy commercial curl support. We
help you work out your issues, debug your libcurl applications, use the API,
port to new platforms, add new features and more. With a team lead by the
curl founder Daniel himself.
Re: Crash in some use case using shared handle with connection sharing in 8.18 and above
- Contemporary messages sorted: [ by date ] [ by thread ] [ by subject ] [ by author ] [ by messages with attachments ]
From: Stefan Eissing via curl-library <curl-library_at_lists.haxx.se>
Date: Mon, 9 Mar 2026 16:14:17 +0100
Dmitry,
I took your suggested fix for the connection among some other improvements to shares in https://github.com/curl/curl/pull/20870
Let me know how that work for you. Thanks!
- Stefan
> Am 03.03.2026 um 20:56 schrieb Dmitry Karpov <dkarpov_at_roku.com>:
>
>> An application can get into lots of trouble when removing a share again from an easy handle. I'd recommend not to do that.
>
> But removing a share may be a part of a more complex code or framework, which is invoked on certain conditions.
> So, it may be not always possible to avoid removing the share when some kind of cleanup for an easy handle wrapped into some complex code is done.
>
> And I think that libcurl should handle it gracefully and not crash in such cases, especially if it worked just fine in the previous libcurl versions.
>
> Besides, the same crash occurs if a share is not removed from an easy handle, but replaced by some other share (i.e. client wants to use connections from some other pool stored in a different share),
> which is like using connections from a different multi-handle.
>
> And the problem is only for the connection sharing option, because the cleanup code when a new share is set doesn't clear the connection data in easy handle as it does for the other share options.
> Could you please have a look at the fix that I suggested which adds a cleanup for connection data before a new share is set and let me know if it is OK?
>
> Thanks,
> Dmitry
>
>
> -----Original Message-----
> From: Stefan Eissing <stefan_at_eissing.org>
> Sent: Monday, March 2, 2026 11:32 PM
> To: libcurl development <curl-library_at_lists.haxx.se>
> Cc: Dmitry Karpov <dkarpov_at_roku.com>
> Subject: [EXTERNAL] Re: Crash in some use case using shared handle with connection sharing in 8.18 and above
>
> An application can get into lots of trouble when removing a share again from an easy handle. I'd recommend not to do that.
>
> Cheers,
> Stefan
>
>> Am 03.03.2026 um 07:51 schrieb Dmitry Karpov via curl-library <curl-library_at_lists.haxx.se>:
>>
>> Hi All, I discovered a crash case scenario with shared handle with connection sharing when trying libcurl 8.18.0 in my application.
>> The crash case can be described as follows:
>> - client create a share handle to share connections.
>> - client creates a multi handle.
>> - client creates an easy handle.
>> - client sets a share handle in the easy handle.
>> - client adds the easy handle to the multi handle.
>> - client performs the transfer via the multi-handle until it gets some data.
>> NOTE: The download must be long enough, so we break the transfer before it is fully done.
>> - client sets a null share to the easy handle.
>> - client removes the easy handle from the multi handle (via curl_multi_remove_handle) - CRASH!
>>
>> In the debug build the crash happens because of the assert in the cpool_remove_conn() function in \lib\conncache.c:
>>
>> static void cpool_remove_conn(struct cpool *cpool,
>> struct connectdata *conn) {
>> ….
>> else {
>> /* Should have been in the bundle list */
>> DEBUGASSERT(NULL); // Crash happens here!!!
>> }
>> }
>> }
>> In the release build it happens at some point after the assertion is ignored when trying to remove non-existing connection from the multi-handle connection pool.
>>
>> The reason for the assertion/crash is that when we set a null share to
>> the easy handle before calling the curl_multi_remove_handle(), then cpool_get_instance() returns the connection pool for the multi-handle while the easy handle keep the connection data from the shared handle connection pool.
>>
>> And when the curl_multi_remove_handle() tries to remove the connection data from the easy handle, it uses the connection pool from the multi-handle, not from the share and asserts/crashes.
>> A possible logical fix for this issue would be detaching the connection data from the easy handle when a new share handle is set, similar to the other easy settings, like:
>>
>> \lib\setopt.c:
>>
>> case CURLOPT_SHARE: {
>> …
>> if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) {
>> Curl_resolv_unlink(data, &data->state.dns[0]);
>> Curl_resolv_unlink(data, &data->state.dns[1]);
>> }
>> /* Detaching the connection if the easy handle was using connection sharing */
>> if (data->share->specifier & (1 << CURL_LOCK_DATA_CONNECT))
>> Curl_detach_connection(data); …
>> }
>> It worked in my test, but maybe there is a better solution.
>> Here is some small example program that can help to reproduce the crash:
>>
>> static size_t writeFunc(void* ptr, size_t size, size_t nmemb,
>> void* data) {
>> (void)ptr;
>> bool* data_received = (bool*)data;
>> *data_received = true;
>> return size * nmemb;
>> }
>> void setNullShareRemoveTest() {
>> // Creating share with connection sharing option.
>> CURLSH* share = curl_share_init();
>> curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
>> // Creating an easy handle and set share and other options.
>> CURL* easy = curl_easy_init();
>> curl_easy_setopt(easy, CURLOPT_SHARE, share);
>> // The URL should be for a long enough download, so the transfer is not
>> // completed when the first data chunk is delivered to the write function.
>> curl_easy_setopt(easy, CURLOPT_URL, ”http://example.com/file_1MB.bin”);
>> bool data_received = false;
>> curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, writeFunc);
>> curl_easy_setopt(easy, CURLOPT_WRITEDATA, (void*)&data_received);
>> curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
>> // Creating a multi handle.
>> CURLM* multi = curl_multi_init();
>> // Run transfer
>> curl_multi_add_handle(multi, easy);
>> int still_running = 0;
>> for (;;) {
>> CURLMcode mresult = curl_multi_perform(multi, &still_running);
>> if (mresult != CURLM_OK) {
>> printf("curl_multi_perform() failed, code %d.\n",
>> (int)mresult);
>> break;
>> }
>> if (!still_running || data_received) {
>> break; // Break when first data chunk is received or transfer is done.
>> }
>> /* wait for activity, timeout or "nothing" */
>> mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);
>> if (mresult != CURLM_OK) {
>> printf("curl_multi_poll() failed, code %d.\n", (int)mresult);
>> break;
>> }
>> } /* if there are still transfers, loop */
>> // Set a null share first.
>> curl_easy_setopt(easy, CURLOPT_SHARE, NULL);
>> // Remove the easy handle after clearing the share. !!! Crash!!!
>> curl_multi_remove_handle(multi, easy); // cleanup:
>> curl_easy_cleanup(easy);
>> curl_multi_cleanup(multi);
>> curl_share_cleanup(share);
>> printf("\nDone\n");
>> }
>> Thanks,
>> Dmitry Karpov
>>
>>
>> --
>> Unsubscribe: https://lists.haxx.se/mailman/listinfo/curl-library
>> Etiquette: https://curl.se/mail/etiquette.html
>
>
Date: Mon, 9 Mar 2026 16:14:17 +0100
Dmitry,
I took your suggested fix for the connection among some other improvements to shares in https://github.com/curl/curl/pull/20870
Let me know how that work for you. Thanks!
- Stefan
> Am 03.03.2026 um 20:56 schrieb Dmitry Karpov <dkarpov_at_roku.com>:
>
>> An application can get into lots of trouble when removing a share again from an easy handle. I'd recommend not to do that.
>
> But removing a share may be a part of a more complex code or framework, which is invoked on certain conditions.
> So, it may be not always possible to avoid removing the share when some kind of cleanup for an easy handle wrapped into some complex code is done.
>
> And I think that libcurl should handle it gracefully and not crash in such cases, especially if it worked just fine in the previous libcurl versions.
>
> Besides, the same crash occurs if a share is not removed from an easy handle, but replaced by some other share (i.e. client wants to use connections from some other pool stored in a different share),
> which is like using connections from a different multi-handle.
>
> And the problem is only for the connection sharing option, because the cleanup code when a new share is set doesn't clear the connection data in easy handle as it does for the other share options.
> Could you please have a look at the fix that I suggested which adds a cleanup for connection data before a new share is set and let me know if it is OK?
>
> Thanks,
> Dmitry
>
>
> -----Original Message-----
> From: Stefan Eissing <stefan_at_eissing.org>
> Sent: Monday, March 2, 2026 11:32 PM
> To: libcurl development <curl-library_at_lists.haxx.se>
> Cc: Dmitry Karpov <dkarpov_at_roku.com>
> Subject: [EXTERNAL] Re: Crash in some use case using shared handle with connection sharing in 8.18 and above
>
> An application can get into lots of trouble when removing a share again from an easy handle. I'd recommend not to do that.
>
> Cheers,
> Stefan
>
>> Am 03.03.2026 um 07:51 schrieb Dmitry Karpov via curl-library <curl-library_at_lists.haxx.se>:
>>
>> Hi All, I discovered a crash case scenario with shared handle with connection sharing when trying libcurl 8.18.0 in my application.
>> The crash case can be described as follows:
>> - client create a share handle to share connections.
>> - client creates a multi handle.
>> - client creates an easy handle.
>> - client sets a share handle in the easy handle.
>> - client adds the easy handle to the multi handle.
>> - client performs the transfer via the multi-handle until it gets some data.
>> NOTE: The download must be long enough, so we break the transfer before it is fully done.
>> - client sets a null share to the easy handle.
>> - client removes the easy handle from the multi handle (via curl_multi_remove_handle) - CRASH!
>>
>> In the debug build the crash happens because of the assert in the cpool_remove_conn() function in \lib\conncache.c:
>>
>> static void cpool_remove_conn(struct cpool *cpool,
>> struct connectdata *conn) {
>> ….
>> else {
>> /* Should have been in the bundle list */
>> DEBUGASSERT(NULL); // Crash happens here!!!
>> }
>> }
>> }
>> In the release build it happens at some point after the assertion is ignored when trying to remove non-existing connection from the multi-handle connection pool.
>>
>> The reason for the assertion/crash is that when we set a null share to
>> the easy handle before calling the curl_multi_remove_handle(), then cpool_get_instance() returns the connection pool for the multi-handle while the easy handle keep the connection data from the shared handle connection pool.
>>
>> And when the curl_multi_remove_handle() tries to remove the connection data from the easy handle, it uses the connection pool from the multi-handle, not from the share and asserts/crashes.
>> A possible logical fix for this issue would be detaching the connection data from the easy handle when a new share handle is set, similar to the other easy settings, like:
>>
>> \lib\setopt.c:
>>
>> case CURLOPT_SHARE: {
>> …
>> if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) {
>> Curl_resolv_unlink(data, &data->state.dns[0]);
>> Curl_resolv_unlink(data, &data->state.dns[1]);
>> }
>> /* Detaching the connection if the easy handle was using connection sharing */
>> if (data->share->specifier & (1 << CURL_LOCK_DATA_CONNECT))
>> Curl_detach_connection(data); …
>> }
>> It worked in my test, but maybe there is a better solution.
>> Here is some small example program that can help to reproduce the crash:
>>
>> static size_t writeFunc(void* ptr, size_t size, size_t nmemb,
>> void* data) {
>> (void)ptr;
>> bool* data_received = (bool*)data;
>> *data_received = true;
>> return size * nmemb;
>> }
>> void setNullShareRemoveTest() {
>> // Creating share with connection sharing option.
>> CURLSH* share = curl_share_init();
>> curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
>> // Creating an easy handle and set share and other options.
>> CURL* easy = curl_easy_init();
>> curl_easy_setopt(easy, CURLOPT_SHARE, share);
>> // The URL should be for a long enough download, so the transfer is not
>> // completed when the first data chunk is delivered to the write function.
>> curl_easy_setopt(easy, CURLOPT_URL, ”http://example.com/file_1MB.bin”);
>> bool data_received = false;
>> curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, writeFunc);
>> curl_easy_setopt(easy, CURLOPT_WRITEDATA, (void*)&data_received);
>> curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
>> // Creating a multi handle.
>> CURLM* multi = curl_multi_init();
>> // Run transfer
>> curl_multi_add_handle(multi, easy);
>> int still_running = 0;
>> for (;;) {
>> CURLMcode mresult = curl_multi_perform(multi, &still_running);
>> if (mresult != CURLM_OK) {
>> printf("curl_multi_perform() failed, code %d.\n",
>> (int)mresult);
>> break;
>> }
>> if (!still_running || data_received) {
>> break; // Break when first data chunk is received or transfer is done.
>> }
>> /* wait for activity, timeout or "nothing" */
>> mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);
>> if (mresult != CURLM_OK) {
>> printf("curl_multi_poll() failed, code %d.\n", (int)mresult);
>> break;
>> }
>> } /* if there are still transfers, loop */
>> // Set a null share first.
>> curl_easy_setopt(easy, CURLOPT_SHARE, NULL);
>> // Remove the easy handle after clearing the share. !!! Crash!!!
>> curl_multi_remove_handle(multi, easy); // cleanup:
>> curl_easy_cleanup(easy);
>> curl_multi_cleanup(multi);
>> curl_share_cleanup(share);
>> printf("\nDone\n");
>> }
>> Thanks,
>> Dmitry Karpov
>>
>>
>> --
>> Unsubscribe: https://lists.haxx.se/mailman/listinfo/curl-library
>> Etiquette: https://curl.se/mail/etiquette.html
>
>
-- Unsubscribe: https://lists.haxx.se/mailman/listinfo/curl-library Etiquette: https://curl.se/mail/etiquette.htmlReceived on 2026-03-09