Skip to content

Conversation

ungive
Copy link

@ungive ungive commented Sep 5, 2025

The following code makes use of several dangling references, when the call to getaddrinfo times out:

cpp-httplib/httplib.h

Lines 3800 to 3830 in 89c932f

// 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);
std::lock_guard<std::mutex> lock(result_mutex);
result = thread_result;
completed = true;
result_cv.notify_one();
});
// Wait for completion or timeout
std::unique_lock<std::mutex> lock(result_mutex);
auto finished = result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
[&] { return completed; });
if (finished) {
// Operation completed within timeout
resolve_thread.join();
*res = result_addrinfo;
return result;
} else {
// Timeout occurred
resolve_thread.detach(); // Let the thread finish in background
return EAI_AGAIN; // Return timeout error
}

Consider the following scenario:

  1. A low timeout of e.g. 1 second is chosen, getaddrinfo takes 5 seconds to fail for an address that does not resolve.
  2. As a consequence the wait_for call returns with finished containing false, the last value of completed.
  3. The following else branch is executed and the resolve_thread is detached.
  4. All local variables are destroyed.
  5. After 4 seconds getaddrinfo returns control back to the caller (the resolve thread).
  6. The lock guard locks result_mutex, which is destroyed (other variables are read from/written to as well).

The following test will fail on the current master branch:

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);
}

Since it's UB, you can trigger it more easily like this e.g.:

  std::thread resolve_thread([&]() {
    auto thread_result = getaddrinfo(node, service, hints, &result_addrinfo);
    {std::lock_guard<std::mutex> lock(result_mutex);
    result = thread_result;
    completed = true;
    result_cv.notify_one();}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
    {std::lock_guard<std::mutex> lock(result_mutex);}
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant