Use-After-Free race condition in url_move_hostname() via shared connection pool

Disclosed: 2026-03-31 20:09:32 By h3xb1tx To curl
Medium
Vulnerability Details
## Summary: In lib/url.c, url_conn_reuse_adjust() calls url_move_hostname() which frees conn->host.rawalloc and conn->host.encalloc via Curl_safefree() and Curl_free_idnconverted_hostname() after Curl_cpool_find() has already released the connection pool lock. A second thread doing a concurrent pool lookup still holds that lock and reads conn->host.name inside url_match_destination() via curl_strequal() — racing with the free. This is not triggered by a malicious server — it is triggered by normal concurrent use of the documented CURLSH API. ## Affected version: curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 OpenSSL/3.6.1 Release-Date: 2026-03-11 Platform: Arch Linux x86_64 BuildId: ab8272972e08f105716dc8989cb8d3afa27753a6 ## Steps To Reproduce: 1. Install dependencies: sudo pacman -S gcc curl 2. Save the two attached files race_poc.c and curl_min.h in the same directory. 3. Compile with ThreadSanitizer: gcc -g -O1 -pthread -fsanitize=thread \ -I. race_poc.c -o race_tsan \ /usr/lib/libcurl.so.4 -Wl,-rpath,/usr/lib 4. Run TSAN: TSAN_OPTIONS="halt_on_error=0:history_size=4" \ timeout 15 ./race_tsan 2>&1 Expected: libcurl.so.4+0x360d2 (curl_strequal in url_match_destination) racing with libcurl.so.4+0x3770a (hostname swap in url_conn_reuse_adjust). 5. Compile with AddressSanitizer: gcc -g -O1 -pthread -fsanitize=address \ -I. race_poc.c -o race_asan \ /usr/lib/libcurl.so.4 -Wl,-rpath,/usr/lib 6. Run ASAN: ASAN_OPTIONS="halt_on_error=0:detect_leaks=0" \ timeout 15 ./race_asan 2>&1 Expected: SIGSEGV crash inside libcurl.so.4 on most runs. ## Root cause in lib/url.c: url_move_hostname() frees the old hostname without any lock: Curl_safefree(dest->rawalloc); // frees conn->host.rawalloc Curl_free_idnconverted_hostname(dest); // frees conn->host.encalloc *dest = *src; memset(src, 0, sizeof(*src)); This runs inside url_conn_reuse_adjust() AFTER Curl_cpool_find() released the pool lock. A concurrent thread inside Curl_cpool_find() holds the lock and reads conn->host.name (pointing into the freed rawalloc block) inside url_match_destination() via curl_strequal(). url_conn_reuse_adjust() also does this without the lock: curlx_free(conn->user); curlx_free(conn->passwd); conn->user = needle->user; conn->passwd = needle->passwd; On a multiplexed HTTP/2 connection where a concurrent transfer is mid-authentication reading conn->user, this is a second race on credentials. CURLOPT_CONNECT_TO is NOT required to trigger this. url_move_hostname() is called unconditionally on every connection reuse. Any two threads sharing a CURLSH handle with CURL_LOCK_DATA_CONNECT hitting the connection reuse path simultaneously will trigger the race. ## Impact Any multi-threaded application using CURLSH with CURL_LOCK_DATA_CONNECT is affected. This is a standard documented libcurl pattern. - Thread T3: memcmp read at libcurl.so.4+0x360d2 (curl_strequal inside url_match_destination) - Thread T2: memcpy write at libcurl.so.4+0x3770a (hostname swap inside url_conn_reuse_adjust) - Both on the same 58-byte heap block allocated inside libcurl ASAN produced a hard SIGSEGV on most runs The PoC only calls curl_easy_perform() via the standard public API. It never calls free() directly. The non-deterministic crash addresses across runs confirm real unpredictable heap corruption different memory layouts produce different crash manifestations from the same root cause. Confirmed impact: reliable crash in any application using the affected pattern. Secondary potential impact: conn->user and conn->passwd are freed and replaced in the same function without the lock. On a multiplexed connection where authentication is in progress on a concurrent thread, wrong or stale credentials could be used for that auth exchange.
Actions
View on HackerOne
Report Stats
  • Report ID: 3638715
  • State: Closed
  • Substate: not-applicable
  • Upvotes: 3
Share this report