You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Invalid memory accesses are probable if posixSocket::resolve uses getaddrinfo_a and getaddrinfo_a doesn't finish within m_timeoutHandler's defined interval. The default timeout of 30 seconds makes the problem unlikely but a very low timeout or slow name resolution may reveal it.
if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) {
if (!m_timeoutHandler->handleTimeOut()) {
throwexceptions::operation_timed_out();
} else {
// Reset timeout and keep waiting for connection
m_timeoutHandler->resetTimeOut();
}
}
If a timeout exception is thrown while getaddrinfo_a is still working, that is gai_error returns EAI_INPROGRESS, all local values that are directly or indirectly passed to getaddrinfo_a, like gaiRequest, address, portStr or hints, are destroyed. Subsequently, getaddrinfo_a may access invalid or repurposed memory. Unfortunately, gai_cancel can't cancel a request that is currently being processed (s. https://linux.die.net/man/3/gai_cancel).
This is an abridged reproduction of posixSocket::resolve:
#include <string>
#include <iostream>
#include <chrono>
#include <cstring>
#include <ctime>
#include <thread>
#include <netdb.h>
namespace {
bool resolve(const std::string& address, unsigned int port, addrinfo** addrInfo) {
gaicb gaiRequest;
addrinfo hints;
std::string portStr(std::to_string(port));
std::memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME | AI_NUMERICSERV;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::memset(&gaiRequest, 0, sizeof(gaiRequest));
gaiRequest.ar_name = address.c_str();
gaiRequest.ar_service = portStr.c_str();
gaiRequest.ar_request = &hints;
gaicb* gaiRequests = &gaiRequest;
int gaiError;
if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0) {
std::cerr << "getaddrinfo_a() failed: " << gai_strerror(gaiError) << std::endl;
return false;
}
std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
auto handler_timeout = std::chrono::milliseconds(1);
while (true) {
timespec gaiTimeout;
gaiTimeout.tv_sec = 0;
gaiTimeout.tv_nsec = 1000;
gaiError = gai_suspend(&gaiRequests, 1, &gaiTimeout);
if (gaiError == 0 || gaiError == EAI_ALLDONE) {
const int ret = gai_error(&gaiRequest);
if (ret != 0) {
std::cerr << "getaddrinfo_a() request failed: " << gai_strerror(ret) << std::endl;
return false;
}
else {
*addrInfo = gaiRequest.ar_result;
break;
}
}
else if (gaiError != EAI_AGAIN) {
if (gaiError == EAI_SYSTEM) {
const int ret = gai_error(&gaiRequest);
if (ret != EAI_INPROGRESS && errno != 0) {
std::cerr << "Error while connecting socket." << std::endl;
return false;
}
}
else {
std::cerr << "gai_suspend() failed: " << gai_strerror(gaiError) << std::endl;
return false;
}
}
if ((std::chrono::steady_clock::now() - start) >= handler_timeout) {
std::cerr << "timeout" << std::endl;
return false;
}
}
return true;
}
}
int main(int argc, char** argv) {
{
std::string address("smtp.office365.com");
unsigned int port = 587;
addrinfo* addrInfo = nullptr;
if (resolve(address, port, &addrInfo)) {
std::cout << "resolved" << std::endl;
}
freeaddrinfo(addrInfo);
}
// At this point, address, addrInfo and resolve' stack variables are all gone.
// Wait so that getaddrinfo_a has a chance to finish.
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
You may have to lower handler_timeout to reproduce the problem. Or you just return after getaddrinfo_a is called:
#include <string>
#include <iostream>
#include <chrono>
#include <cstring>
#include <ctime>
#include <thread>
#include <netdb.h>
namespace {
bool resolve(const std::string& address, unsigned int port, addrinfo** addrInfo) {
gaicb gaiRequest;
addrinfo hints;
std::string portStr(std::to_string(port));
std::memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME | AI_NUMERICSERV;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::memset(&gaiRequest, 0, sizeof(gaiRequest));
gaiRequest.ar_name = address.c_str();
gaiRequest.ar_service = portStr.c_str();
gaiRequest.ar_request = &hints;
gaicb* gaiRequests = &gaiRequest;
int gaiError;
if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0) {
std::cerr << "getaddrinfo_a() failed: " << gai_strerror(gaiError) << std::endl;
return false;
}
return false;
}
}
int main(int argc, char** argv) {
{
std::string address("smtp.office365.com");
unsigned int port = 587;
addrinfo* addrInfo = nullptr;
if (resolve(address, port, &addrInfo)) {
std::cout << "resolved" << std::endl;
}
freeaddrinfo(addrInfo);
}
// At this point, address, addrInfo and resolve' stack variables are all gone.
// Wait so that getaddrinfo_a has a chance to finish.
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
Since getaddrinfo_a basically creates a thread to call getaddrinfo, you can do it yourself like:
The idea is that the thread has its own copies of all necessary data and to wrap addrinfo* into a shared_ptr so that it gets freed once the thread, as it is detached, and all local sites are done with it.
The text was updated successfully, but these errors were encountered:
Invalid memory accesses are probable if
posixSocket::resolve
usesgetaddrinfo_a
andgetaddrinfo_a
doesn't finish withinm_timeoutHandler
's defined interval. The default timeout of 30 seconds makes the problem unlikely but a very low timeout or slow name resolution may reveal it.The root cause is:
vmime/src/vmime/platforms/posix/posixSocket.cpp
Lines 484 to 495 in fc69321
If a timeout exception is thrown while
getaddrinfo_a
is still working, that isgai_error
returnsEAI_INPROGRESS
, all local values that are directly or indirectly passed togetaddrinfo_a
, likegaiRequest
,address
,portStr
orhints
, are destroyed. Subsequently,getaddrinfo_a
may access invalid or repurposed memory. Unfortunately,gai_cancel
can't cancel a request that is currently being processed (s. https://linux.die.net/man/3/gai_cancel).This is an abridged reproduction of
posixSocket::resolve
:You may have to lower
handler_timeout
to reproduce the problem. Or you just return aftergetaddrinfo_a
is called:Since
getaddrinfo_a
basically creates a thread to callgetaddrinfo
, you can do it yourself like:The idea is that the thread has its own copies of all necessary data and to wrap
addrinfo*
into ashared_ptr
so that it gets freed once the thread, as it is detached, and all local sites are done with it.The text was updated successfully, but these errors were encountered: