Project

General

Profile

Bug #17220 » fix_bug17220_cond.patch

Glass_saga (Masaki Matsushita), 12/04/2020 04:05 AM

View differences:

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);
}
}
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<GAI_THREADS; i++) {
pthread_mutex_lock(&__gai_requests_mutex);
pthread_cond_signal(&__gai_new_request_notification);
pthread_mutex_unlock(&__gai_requests_mutex);
}
}
int
rb_getaddrinfo_a(const char *node, const char *service,
const struct addrinfo *hints,
......
reqs[0] = &req;
ret = getaddrinfo_a(GAI_NOWAIT, reqs, 1, NULL);
if (ret) return ret;
gaicbs_add(&req);
arg.req = &req;
arg.timeout = timeout;
ret = (int)(VALUE)rb_thread_call_without_gvl(nogvl_gai_suspend, &arg, RUBY_UBF_IO, 0);
gaicbs_remove(&req);
if (ret && ret != EAI_ALLDONE) {
/* 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 */
......
rb_define_method(rb_cAddrinfo, "marshal_dump", addrinfo_mdump, 0);
rb_define_method(rb_cAddrinfo, "marshal_load", addrinfo_mload, 1);
#ifdef HAVE_GETADDRINFO_A
rb_socket_before_exec_func = rb_getaddrinfo_a_before_exec;
#endif
}
ext/socket/rubysocket.h
#include "ruby/io.h"
#include "ruby/ruby.h"
#include "ruby/thread.h"
#include "ruby/thread_native.h" // to call pthread_cond_signal() from rb_getaddrinfo_a_before_exec()
#include "ruby/util.h"
#include "sockport.h"
include/ruby/internal/intern/process.h
RBIMPL_SYMBOL_EXPORT_BEGIN()
/* process.c */
RUBY_EXTERN void (* rb_socket_before_exec_func)();
void rb_last_status_set(int status, rb_pid_t pid);
VALUE rb_last_status_get(void);
int rb_proc_exec(const char*);
process.c
static ID id_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC;
#endif
static ID id_hertz;
#ifdef HAVE_GETADDRINFO_A
void (* rb_socket_before_exec_func)() = NULL;
#endif
/* execv and execl are async-signal-safe since SUSv4 (POSIX.1-2008, XPG7) */
#if defined(__sun) && !defined(_XPG7) /* Solaris 10, 9, ... */
......
static void
before_exec_non_async_signal_safe(void)
{
#ifdef HAVE_GETADDRINFO_A
if (rb_socket_before_exec_func) {
/* A mitigation for [Bug #17220]. See ext/socket/raddrinfo.c */
rb_socket_before_exec_func();
}
#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/socket/test_socket.rb
assert_nothing_raised('[ruby-core:29427]'){ TCPServer.open('localhost', 0) {} }
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
def test_getnameinfo
assert_raise(SocketError) { Socket.getnameinfo(["AF_UNIX", 80, "0.0.0.0"]) }
(5-5/5)