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 <[email protected]>: > >> 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 <[email protected]> > Sent: Monday, March 2, 2026 11:32 PM > To: libcurl development <[email protected]> > Cc: Dmitry Karpov <[email protected]> > 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 >> <[email protected]>: >> >> 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.html
