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

Reply via email to