Bug #17220 » fix_bug17220_2.patch
configure.ac | ||
---|---|---|
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
|
||
... | ... | |
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)
|
ext/socket/raddrinfo.c | ||
---|---|---|
}
|
||
#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->gaicb);
|
||
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->gaicb);
|
||
xfree(tmp);
|
||
}
|
||
}
|
||
}
|
||
static void
|
||
gaicbs_wait_all(void)
|
||
{
|
||
struct gaicbs *tmp, *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
|
||
// cleanup
|
||
request = requests;
|
||
while (request) {
|
||
tmp = request;
|
||
request = request->next;
|
||
xfree(tmp->gaicb);
|
||
xfree(tmp);
|
||
}
|
||
}
|
||
/* 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. */
|
||
static VALUE
|
||
rb_getaddrinfo_a_before_exec(VALUE self)
|
||
{
|
||
gaicbs_cancel_all();
|
||
gaicbs_wait_all();
|
||
/* wait worker threads in getaddrinfo_a(3) to be finished.
|
||
worker threads will finish after 1 second sleep. */
|
||
rb_thread_sleep(1);
|
||
return Qnil;
|
||
}
|
||
int
|
||
rb_getaddrinfo_a(const char *node, const char *service,
|
||
const struct addrinfo *hints,
|
||
... | ... | |
else {
|
||
struct gai_suspend_arg arg;
|
||
struct gaicb *reqs[1];
|
||
struct gaicb req;
|
||
struct gaicb *req = (struct gaicb *)xmalloc(sizeof(struct gaicb));
|
||
req.ar_name = node;
|
||
req.ar_service = service;
|
||
req.ar_request = hints;
|
||
req->ar_name = node;
|
||
req->ar_service = service;
|
||
req->ar_request = hints;
|
||
reqs[0] = &req;
|
||
reqs[0] = req;
|
||
ret = getaddrinfo_a(GAI_NOWAIT, reqs, 1, NULL);
|
||
if (ret) return ret;
|
||
gaicbs_add(req);
|
||
arg.req = &req;
|
||
arg.req = req;
|
||
arg.timeout = timeout;
|
||
ret = (int)(VALUE)rb_thread_call_without_gvl(nogvl_gai_suspend, &arg, RUBY_UBF_IO, 0);
|
||
if (ret && ret != EAI_ALLDONE) {
|
||
gaicbs_remove(req);
|
||
/* EAI_ALLDONE indicates that the request already completed and gai_suspend was redundant */
|
||
/* on Ubuntu 18.04 (or other systems), gai_suspend(3) returns EAI_SYSTEM/ENOENT on timeout */
|
||
if (ret == EAI_SYSTEM && errno == ENOENT) {
|
||
... | ... | |
}
|
||
}
|
||
ret = gai_error(reqs[0]);
|
||
ai = reqs[0]->ar_result;
|
||
gaicbs_remove(req);
|
||
}
|
||
if (ret == 0) {
|
||
... | ... | |
rb_define_singleton_method(rb_cAddrinfo, "unix", addrinfo_s_unix, -1);
|
||
#endif
|
||
#ifdef HAVE_GETADDRINFO_A
|
||
// internal method. it will be called from process.c before fork().
|
||
rb_define_singleton_method(rb_cAddrinfo, "__getaddrinfo_a_before_exec", rb_getaddrinfo_a_before_exec, 0);
|
||
#endif
|
||
rb_define_method(rb_cAddrinfo, "afamily", addrinfo_afamily, 0);
|
||
rb_define_method(rb_cAddrinfo, "pfamily", addrinfo_pfamily, 0);
|
||
rb_define_method(rb_cAddrinfo, "socktype", addrinfo_socktype, 0);
|
process.c | ||
---|---|---|
return rb_detach_process(NUM2PIDT(pid));
|
||
}
|
||
#ifdef HAVE_GETADDRINFO_A
|
||
static VALUE
|
||
call_getaddrinfo_a_before_exec(VALUE unused)
|
||
{
|
||
ID addrinfo = rb_intern("Addrinfo");
|
||
if (rb_const_defined(rb_cObject, addrinfo)) {
|
||
VALUE rb_cAddrinfo = rb_const_get(rb_cObject, addrinfo);
|
||
if (RB_TYPE_P(rb_cAddrinfo, T_CLASS)) {
|
||
return rb_funcallv(rb_cAddrinfo, rb_intern("__getaddrinfo_a_before_exec"), 0, 0);
|
||
}
|
||
}
|
||
return Qnil;
|
||
}
|
||
static void
|
||
getaddrinfo_a_before_exec(void)
|
||
{
|
||
rb_protect(call_getaddrinfo_a_before_exec, Qnil, NULL);
|
||
}
|
||
#endif
|
||
/* This function should be async-signal-safe. Actually it is. */
|
||
static void
|
||
before_exec_async_signal_safe(void)
|
||
... | ... | |
static void
|
||
before_exec_non_async_signal_safe(void)
|
||
{
|
||
#ifdef HAVE_GETADDRINFO_A
|
||
/* A mitigation for [Bug #17220]. See ext/socket/raddrinfo.c */
|
||
getaddrinfo_a_before_exec();
|
||
#endif
|
||
/*
|
||
* On Mac OS X 10.5.x (Leopard) or earlier, exec() may return ENOTSUP
|
||
* if the process have multiple threads. Therefore we have to kill
|
test/ruby/test_process.rb | ||
---|---|---|
end
|
||
assert_empty(Process.waitall)
|
||
end
|
||
def test_getaddrinfo_after_fork
|
||
skip "fork not supported" unless Process.respond_to?(:fork)
|
||
assert_normal_exit(<<-"end;", '[ruby-core:100329] [Bug #17220]')
|
||
require "socket"
|
||
Socket.getaddrinfo("localhost", nil)
|
||
pid = fork { Socket.getaddrinfo("localhost", nil) }
|
||
assert_equal pid, Timeout.timeout(30) { Process.wait(pid) }
|
||
end;
|
||
end
|
||
end
|