From f46df5b4427462a908ce554aa429dfdb5b39b73d Mon Sep 17 00:00:00 2001 From: Jonas van den Berg Date: Fri, 5 Sep 2025 21:22:17 +0200 Subject: [PATCH 1/5] Fix use of dangling references When the resolve thread is detached, local variables were still used, which could lead to a program crash. --- httplib.h | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/httplib.h b/httplib.h index e15ba44f22..f5940a6513 100644 --- a/httplib.h +++ b/httplib.h @@ -3798,31 +3798,51 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service, } #else // Fallback implementation using thread-based timeout for other Unix systems - std::mutex result_mutex; - std::condition_variable result_cv; - auto completed = false; - auto result = EAI_SYSTEM; - struct addrinfo *result_addrinfo = nullptr; - std::thread resolve_thread([&]() { - auto thread_result = getaddrinfo(node, service, hints, &result_addrinfo); + struct GetAddrInfoState { + std::mutex mutex{}; + std::condition_variable result_cv; + bool completed{false}; + int result{0}; + std::string node{}; + std::string service{}; + struct addrinfo hints {}; + struct addrinfo *info{nullptr}; + }; - std::lock_guard lock(result_mutex); - result = thread_result; - completed = true; - result_cv.notify_one(); + // Allocate on the heap, so the resolver thread can keep using the data. + auto state = std::make_shared(); + + state->result = EAI_SYSTEM; + state->node = node; + state->service = service; + state->hints.ai_flags = hints->ai_flags; + state->hints.ai_family = hints->ai_family; + state->hints.ai_socktype = hints->ai_socktype; + state->hints.ai_protocol = hints->ai_protocol; + // The remaining fields of "hints" must be zeroed, so do not copy them. + + std::thread resolve_thread([=]() { + auto thread_result = getaddrinfo( + state->node.c_str(), state->service.c_str(), hints, &state->info); + + std::lock_guard lock(state->mutex); + state->result = thread_result; + state->completed = true; + state->result_cv.notify_one(); }); // Wait for completion or timeout - std::unique_lock lock(result_mutex); - auto finished = result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), - [&] { return completed; }); + std::unique_lock lock(state->mutex); + auto finished = + state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), + [&] { return state->completed; }); if (finished) { // Operation completed within timeout resolve_thread.join(); - *res = result_addrinfo; - return result; + *res = state->info; + return state->result; } else { // Timeout occurred resolve_thread.detach(); // Let the thread finish in background From c2a45c267d50239cd2b04a82848f63eebfad531c Mon Sep 17 00:00:00 2001 From: Jonas van den Berg Date: Fri, 5 Sep 2025 21:59:39 +0200 Subject: [PATCH 2/5] Add test to verify dangling ref fix --- test/test.cc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/test.cc b/test/test.cc index 490260675e..0e0f9db4ea 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1331,6 +1331,27 @@ TEST(RangeTest, FromHTTPBin_Online) { } } +TEST(GetAddrInfoDanglingRefTest, LongTimeout) { + auto host = "unresolvableaddress.local"; + auto path = std::string{"/"}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + auto port = 443; + SSLClient cli(host, port); +#else + auto port = 80; + Client cli(host, port); +#endif + cli.set_connection_timeout(1); + + { + auto res = cli.Get(path); + ASSERT_FALSE(res); + } + + std::this_thread::sleep_for(8s); +} + TEST(ConnectionErrorTest, InvalidHost) { auto host = "-abcde.com"; From f4d7669187bb1856d9c452d01ed1b5db74e847e1 Mon Sep 17 00:00:00 2001 From: Jonas van den Berg Date: Fri, 5 Sep 2025 22:17:13 +0200 Subject: [PATCH 3/5] Add missing brace initialization --- httplib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index f5940a6513..f7fc2dc809 100644 --- a/httplib.h +++ b/httplib.h @@ -3801,7 +3801,7 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service, struct GetAddrInfoState { std::mutex mutex{}; - std::condition_variable result_cv; + std::condition_variable result_cv{}; bool completed{false}; int result{0}; std::string node{}; From 02bbdfb05b86d174c452e80a60efb1860a8fd947 Mon Sep 17 00:00:00 2001 From: Jonas van den Berg Date: Fri, 5 Sep 2025 22:17:25 +0200 Subject: [PATCH 4/5] Assert that the remaining fields are really zeroed --- httplib.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/httplib.h b/httplib.h index f7fc2dc809..054e37a7c2 100644 --- a/httplib.h +++ b/httplib.h @@ -3821,6 +3821,10 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service, state->hints.ai_socktype = hints->ai_socktype; state->hints.ai_protocol = hints->ai_protocol; // The remaining fields of "hints" must be zeroed, so do not copy them. + assert(hints->ai_addrlen == 0); + assert(hints->ai_addr == nullptr); + assert(hints->ai_canonname == nullptr); + assert(hints->ai_next == nullptr); std::thread resolve_thread([=]() { auto thread_result = getaddrinfo( From 412a5a8ef74e6b1100ba70a400e31453eadede47 Mon Sep 17 00:00:00 2001 From: Jonas van den Berg Date: Thu, 11 Sep 2025 01:51:46 +0200 Subject: [PATCH 5/5] Fix use of chrono literals --- test/test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.cc b/test/test.cc index 0e0f9db4ea..a9ac0d17f1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1349,7 +1349,7 @@ TEST(GetAddrInfoDanglingRefTest, LongTimeout) { ASSERT_FALSE(res); } - std::this_thread::sleep_for(8s); + std::this_thread::sleep_for(std::chrono::seconds(8)); } TEST(ConnectionErrorTest, InvalidHost) {