From 7eb0cd5d3ed0291074e2745a2ed2a268eb8bb580 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sun, 4 Oct 2026 12:50:57 +0200 Subject: [PATCH] posix: fix use-after-free when name resolution is abandoned posixSocket::resolve() passed stack variables (and the caller's address string) to getaddrinfo_a(). When the timeout handler aborted the loop while the request was still being processed, glibc kept reading these and wrote the result into a dead stack frame, corrupting the stack. The request now lives on the heap with its own copies of the data. When leaving resolve(), it is deleted if glibc is done with it (or if it can still be cancelled); otherwise, its deletion is deferred to a later call. Also, keep waiting when gai_suspend() is interrupted by a signal (EAI_INTR), instead of failing and abandoning the request. Fixes #273. --- src/vmime/platforms/posix/posixSocket.cpp | 134 ++++++++++++++++++++-- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/src/vmime/platforms/posix/posixSocket.cpp b/src/vmime/platforms/posix/posixSocket.cpp index c0ce0cfa..cba3d3fd 100644 --- a/src/vmime/platforms/posix/posixSocket.cpp +++ b/src/vmime/platforms/posix/posixSocket.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include "vmime/utility/stringUtils.hpp" @@ -89,6 +90,116 @@ GNU_UNUSED char* vmime_strerror_r_result(char* res, char* /* buf */) { #endif // VMIME_HAVE_STRERROR_R +#if VMIME_HAVE_GETADDRINFO_A + +namespace { + +// Asynchronous name resolution request. glibc keeps using it as long as the +// request is being processed, possibly after posixSocket::resolve() has +// returned (eg. on timeout), so it holds everything the request refers to. +struct gaiRequestContext { + + gaiRequestContext( + const vmime::string& address, + const char* service, + const struct ::addrinfo& hints + ) + : address(address), + service(service), + hints(hints), + next(NULL) { + + memset(&request, 0, sizeof(request)); + + request.ar_name = this->address.c_str(); + request.ar_service = this->service.c_str(); + request.ar_request = &this->hints; + } + + vmime::string address; + vmime::string service; + struct ::addrinfo hints; + struct ::gaicb request; + + gaiRequestContext* next; // in list of pending requests +}; + + +// Requests which were still being processed when they were released; +// they are deleted later, once completed +pthread_mutex_t g_pendingRequestsMutex = PTHREAD_MUTEX_INITIALIZER; +gaiRequestContext* g_pendingRequests = NULL; + + +// Delete a request, unless it is still being processed by glibc. +// Returns false in this case. +bool tryDeleteRequest(gaiRequestContext* ctx) { + + switch (gai_cancel(&ctx->request)) { + + case EAI_CANCELED: // was not processed yet; removed from the queue + case EAI_ALLDONE: // completed + + if (ctx->request.ar_result) { + freeaddrinfo(ctx->request.ar_result); + } + + delete ctx; + return true; + + default: // EAI_NOTCANCELED + + return false; + } +} + + +// Delete pending requests which have been completed in the meantime +void deletePendingRequests() { + + pthread_mutex_lock(&g_pendingRequestsMutex); + + gaiRequestContext** link = &g_pendingRequests; + + while (*link) { + + gaiRequestContext* ctx = *link; + gaiRequestContext* next = ctx->next; + + if (tryDeleteRequest(ctx)) { + *link = next; + } else { + link = &ctx->next; + } + } + + pthread_mutex_unlock(&g_pendingRequestsMutex); +} + + +// Delete a request, or defer deletion if it is still being processed +struct gaiRequestDeleter { + + void operator()(gaiRequestContext* ctx) const { + + if (tryDeleteRequest(ctx)) { + return; + } + + pthread_mutex_lock(&g_pendingRequestsMutex); + + ctx->next = g_pendingRequests; + g_pendingRequests = ctx; + + pthread_mutex_unlock(&g_pendingRequestsMutex); + } +}; + +} + +#endif // VMIME_HAVE_GETADDRINFO_A + + namespace vmime { namespace platforms { @@ -418,14 +529,14 @@ void posixSocket::resolve( // If getaddrinfo_a() is available, use asynchronous resolving to allow // the timeout handler to cancel the operation - struct ::gaicb gaiRequest; - memset(&gaiRequest, 0, sizeof(gaiRequest)); + deletePendingRequests(); - gaiRequest.ar_name = address.c_str(); - gaiRequest.ar_service = portStr; - gaiRequest.ar_request = &hints; + // If we give up while the request is being processed (eg. timeout), + // it cannot be cancelled: its deletion is deferred until it completes + std::unique_ptr ctx + (new gaiRequestContext(address, portStr, hints)); - struct ::gaicb* gaiRequests = &gaiRequest; + struct ::gaicb* gaiRequests = &ctx->request; int gaiError; if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0) { @@ -449,7 +560,7 @@ void posixSocket::resolve( if (gaiError == 0 || gaiError == EAI_ALLDONE) { - const int ret = gai_error(&gaiRequest); + const int ret = gai_error(&ctx->request); if (ret != 0) { @@ -459,15 +570,18 @@ void posixSocket::resolve( } else { - *addrInfo = gaiRequest.ar_result; + // Take ownership of the result + *addrInfo = ctx->request.ar_result; + ctx->request.ar_result = NULL; + break; } - } else if (gaiError != EAI_AGAIN) { + } else if (gaiError != EAI_AGAIN && gaiError != EAI_INTR) { if (gaiError == EAI_SYSTEM) { - const int ret = gai_error(&gaiRequest); + const int ret = gai_error(&ctx->request); if (ret != EAI_INPROGRESS && errno != 0) {