Package: libuim8
Version: 1:1.8.6-8
Severity: normal

Dear Maintainer,

When uim client exec() to long-running process (e.g. iceweasel restarts), it leaks open socket to uim-helper-server, and by then uim-helper-server begins to constantly consume memory, as it cannot write to those sockets and have to infinitely buffer unsent messages to them:

... strace -p `pidof uim-helper-server`|egrep '^(select|mremap)' ...
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
select(11, [0 1 3 4 5 6 7 9 10], [1 4 5 6 7 9 10], NULL, NULL) = 6 (out [4 5 6 7 9 10])
   note: here fd 1 points to unix-domain-socket to such iceweasel
   process, and it never returns "writeable" status; there are another
   socket to same iceweasel process {which was opened after restart},
   which is properly handled
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
select(11, [0 1 3 4 5 6 7 9 10], [1 4 5 6 7 9 10], NULL, NULL) = 6 (out [4 5 6 7 9 10])
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
mremap(0xb7318000, 1404928, 1409024, MREMAP_MAYMOVE) = 0xb7318000
   note: this is apparently reallocation of buffered messages for this
   socket; after few days, it already grown to 1.4M
select(11, [0 1 3 4 5 6 7 9 10], [1 4 5 6 7 9 10], NULL, NULL) = 6 (out [4 5 6 7 9 10])
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
select(11, [0 1 3 4 5 6 7 9 10], [1 4 5 6 7 9 10], NULL, NULL) = 6 (out [4 5 6 7 9 10])
select(11, [0 1 3 4 5 6 7 9 10], [1], NULL, NULL) = 1 (in [0])
...

Attached patch fixes this by setting CLOEXEC flag on all sockets (only
uim-helper-client.c part is definitely needed and safe; uim/socket.c part theoretically can break scm scripts that intentionally passes open socket fd to spawned processes [but AFAIK none of them does]).

Optional 2nd patch tries to use SOCK_CLOEXEC option, available in
linux-2.6.27+ (it would avoid race window between socket() and fcntl(), if another thread spawned process at the exact same time with socket creation), with fallback for older kernel.

This bug is also present in (all) older uim versions, and in the upstream master branch as well.

P.S. @upstream: also, uim-helper-server could be improved a bit for better handling of "stuck client" scenario [aside from client bugs {e.g. uim-input-pad-ja is broken this way}, that could be stopped processes (^Z/kill -STOP), etc]. E.g. it could forget/merge older prop_update & focus_* messages [after reaching some size/last-writeable-time threshold, to avoid complication in common-case scenario].

P.P.S. BTW, uim-helper-client.c looks *extremely* fragile. If someone, somehow will call uim_helper_init_client twice, all hell will broke loose.

-- System Information:
Debian Release: 8.0
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable')
Architecture: i386 (i686)

Kernel: Linux 3.16.0-4-686-pae (SMP w/1 CPU core)
Locale: LANG=ru_RU.UTF-8, LC_CTYPE=ru_RU.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages libuim8 depends on:
ii  libc6              2.19-18
ii  libgcroots0        0.8.5-4.1
ii  libuim-scm0        1:1.8.6-8
ii  multiarch-support  2.19-18
ii  uim-common         1:1.8.6-8

libuim8 recommends no packages.

libuim8 suggests no packages.

-- no debconf information

Index: uim-1.8.6/uim/uim-helper-client.c
===================================================================
--- uim-1.8.6.orig/uim/uim-helper-client.c
+++ uim-1.8.6/uim/uim-helper-client.c
@@ -44,6 +44,7 @@
 #include <errno.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <fcntl.h>
 
 #ifdef HAVE_STRINGS_H
 #include <strings.h>
@@ -93,6 +94,7 @@ int uim_helper_init_client_fd(void (*dis
     perror("fail to create socket");
     goto error;
   }
+  fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
   
 #ifdef LOCAL_CREDS /* for NetBSD */
   /* Set the socket to receive credentials on the next message */
Index: uim-1.8.6/uim/socket.c
===================================================================
--- uim-1.8.6.orig/uim/socket.c
+++ uim-1.8.6/uim/socket.c
@@ -278,7 +278,10 @@ c_freeaddrinfo(uim_lisp addrinfo_)
 static uim_lisp
 c_socket(uim_lisp domain_, uim_lisp type_, uim_lisp protocol_)
 {
-  return MAKE_INT(socket(C_INT(domain_), C_INT(type_), C_INT(protocol_)));
+  int fd = socket(C_INT(domain_), C_INT(type_), C_INT(protocol_));
+  if (fd != -1)
+    fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
+  return MAKE_INT(fd);
 }
 
 static uim_lisp

Index: uim-1.8.6/uim/uim-helper-client.c
===================================================================
diff -u uim-1.8.6.orig/uim/uim-helper-client.c uim-1.8.6/uim/uim-helper-client.c
--- uim-1.8.6.orig/uim/uim-helper-client.c
+++ uim-1.8.6/uim/uim-helper-client.c
@@ -89,6 +89,12 @@
   server.sun_family = PF_UNIX;
   strlcpy(server.sun_path, path, sizeof(server.sun_path));
 
+#ifdef SOCK_CLOEXEC
+  /* linux-2.6.27+ variant that prevents racing on concurrent fork & exec in other thread */
+  fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  if (fd == -1 && errno == EINVAL)
+    /* fallback to plain SOCK_TYPE on older kernel */
+#endif
   fd = socket(PF_UNIX, SOCK_STREAM, 0);
   if (fd < 0) {
     perror("fail to create socket");
Index: uim-1.8.6/uim/socket.c
===================================================================
diff -u uim-1.8.6.orig/uim/socket.c uim-1.8.6/uim/socket.c
--- uim-1.8.6.orig/uim/socket.c
+++ uim-1.8.6/uim/socket.c
@@ -278,7 +278,15 @@
 static uim_lisp
 c_socket(uim_lisp domain_, uim_lisp type_, uim_lisp protocol_)
 {
-  int fd = socket(C_INT(domain_), C_INT(type_), C_INT(protocol_));
+  int type_i = C_INT(type_);
+  int fd;
+#ifdef SOCK_CLOEXEC
+  /* linux-2.6.27+ variant that prevents racing on concurrent fork & exec in other thread */
+  fd = socket(C_INT(domain_), type_i | SOCK_CLOEXEC, C_INT(protocol_));
+  if (fd == -1 && errno == EINVAL)
+    /* fallback to plain SOCK_TYPE on older kernel */
+#endif
+    fd = socket(C_INT(domain_), type_i, C_INT(protocol_));
   if (fd != -1)
     fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
   return MAKE_INT(fd);

Reply via email to