diff --git a/configure.ac b/configure.ac index 6c6d73f11f..84680cb29e 100644 --- a/configure.ac +++ b/configure.ac @@ -1152,6 +1152,7 @@ AC_CHECK_LIB(crypt, crypt) # glibc (GNU/Linux, GNU/Hurd, GNU/kFreeBSD) AC_CHECK_LIB(dl, dlopen) # Dynamic linking for SunOS/Solaris and SYSV AC_CHECK_LIB(dld, shl_load) # Dynamic linking for HP-UX AC_CHECK_LIB(socket, shutdown) # SunOS/Solaris +AC_CHECK_LIB(anl, getaddrinfo_a) dnl Checks for header files. AC_HEADER_DIRENT @@ -1943,6 +1944,7 @@ AC_CHECK_FUNCS(fsync) AC_CHECK_FUNCS(ftruncate) AC_CHECK_FUNCS(ftruncate64) # used for Win32 platform AC_CHECK_FUNCS(getattrlist) +AC_CHECK_FUNCS(getaddrinfo_a) AC_CHECK_FUNCS(getcwd) AC_CHECK_FUNCS(getgidx) AC_CHECK_FUNCS(getgrnam) diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 211f05c7eb..27d3db6413 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -340,6 +340,123 @@ rb_getaddrinfo(const char *node, const char *service, } #ifdef HAVE_GETADDRINFO_A +struct gaicbs { + struct gaicbs *next; + struct gaicb *gaicb; +}; + +/* linked list to retain all outstanding and ongoing requests */ +static struct gaicbs *requests = NULL; + +static void +gaicbs_add(struct gaicb *req) +{ + struct gaicbs *request; + + if (!req) return; + request = (struct gaicbs *)xmalloc(sizeof(struct gaicbs)); + request->gaicb = req; + request->next = requests; + + requests = request; +} + +static void +gaicbs_remove(struct gaicb *req) +{ + struct gaicbs *request = requests; + struct gaicbs *prev = NULL; + + if (!req) return; + + while (request) { + if (request->gaicb == req) break; + prev = request; + request = request->next; + } + + if (request) { + if (prev) { + prev->next = request->next; + } else { + requests = request->next; + } + xfree(request); + } +} + +static void +gaicbs_cancel_all(void) +{ + struct gaicbs *request = requests; + struct gaicbs *tmp, *prev = NULL; + int ret; + + while (request) { + ret = gai_cancel(request->gaicb); + if (ret == EAI_NOTCANCELED) { + // continue to next request + prev = request; + request = request->next; + } else { + // remove the request from the list + if (prev) { + prev->next = request->next; + } else { + requests = request->next; + } + tmp = request; + request = request->next; + xfree(tmp); + } + } +} + +static void +gaicbs_wait_all(void) +{ + struct gaicbs *request = requests; + int size = 0; + + // count gaicbs + while (request) { + size++; + request = request->next; + } + + // create list to wait + const struct gaicb *reqs[size]; + request = requests; + for (int i=0; request; i++) { + reqs[i] = request->gaicb; + request = request->next; + } + + // wait requests + gai_suspend(reqs, size, NULL); // ignore result intentionally +} + +#define GAI_THREADS 20 // maximal number of worker threads in getaddrinfo_a(3) +pthread_mutex_t __gai_requests_mutex; +pthread_cond_t __gai_new_request_notification; + +/* A mitigation for [Bug #17220]. + It cancels all outstanding requests and waits for ongoing requests. + Then, it waits internal worker threads in getaddrinfo_a(3) to be finished. */ +void +rb_getaddrinfo_a_before_exec(void) +{ + gaicbs_cancel_all(); + gaicbs_wait_all(); + + /* wake up worker threads in getaddrinfo_a(3) immediately */ + for (int i=0; i