Bug #17220 » fix_bug17220_4.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); 
   | 
||
| 
         } 
   | 
||
| 
     } 
   | 
||
| 
     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 
   | 
||
| 
     } 
   | 
||
| 
     /*  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(); 
   | 
||
| 
         /* wait worker threads in getaddrinfo_a(3) to be finished. 
   | 
||
| 
            they will finish after 1 second sleep. */ 
   | 
||
| 
         sleep(1); 
   | 
||
| 
     } 
   | 
||
| 
     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 
   | 
||
| 
     } 
   | 
||
| 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"]) } 
   | 
||