[stunnel-users] [PATCH] only use aysnc-signal-safe functions after fork

Philip Craig philipjcraig at gmail.com
Wed Oct 14 10:41:10 CEST 2015


POSIX requires that multi-threaded processes only use async-signal-safe
functions after calling fork(). For more info on the problems this
avoids, see the rationale at:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html

The specific problem this patch solves is that, when there are multiple
simultaneous requests, stunnel is often hanging on a futex syscall when
a child process calls free as a result of tls_alloc().

This problem is experienced when using uClibc. I haven't tested, but I
believe the malloc implementation in glibc does not have this problem.


--- stunnel-5.24.orig/src/client.c
+++ stunnel-5.24/src/client.c
@@ -1100,6 +1100,8 @@ NOEXPORT SOCKET connect_local(CLI *c) {
 
 NOEXPORT SOCKET connect_local(CLI *c) { /* spawn local process */
     char *name, host[40], port[6];
+    char *env[10];
+    int i, envc;
     int fd[2], pid;
     X509 *peer_cert;
 #ifdef HAVE_PTHREAD_SIGMASK
@@ -1115,19 +1117,55 @@ NOEXPORT SOCKET connect_local(CLI *c) {
     } else
         if(make_sockets(fd))
             longjmp(c->err, 1);
+    set_nonblock(fd[1], 0); /* switch back to blocking mode */
+
+    envc = 0;
+
+    if(!getnameinfo(&c->peer_addr.sa, c->peer_addr_len,
+            host, 40, port, 6, NI_NUMERICHOST|NI_NUMERICSERV)) {
+        /* just don't set these variables if getnameinfo() fails */
+        env[envc++] = str_printf("REMOTE_HOST=%s", host);
+        env[envc++] = str_printf("REMOTE_PORT=%s", port);
+        if(c->opt->option.transparent_src) {
+#ifndef LIBDIR
+#define LIBDIR "."
+#endif
+#ifdef MACH64
+            env[envc++] = str_dup("LD_PRELOAD_32=" LIBDIR "/libstunnel.so");
+            env[envc++] = str_dup("LD_PRELOAD_64=" LIBDIR "/" MACH64 "/libstunnel.so");
+#elif __osf /* for Tru64 _RLD_LIST is used instead */
+            env[envc++] = str_dup("_RLD_LIST=" LIBDIR "/libstunnel.so:DEFAULT");
+#else
+            env[envc++] = str_dup("LD_PRELOAD=" LIBDIR "/libstunnel.so");
+#endif
+        }
+    }
+
+    if(c->ssl) {
+        peer_cert=SSL_get_peer_certificate(c->ssl);
+        if(peer_cert) {
+            name=X509_NAME2text(X509_get_subject_name(peer_cert));
+            env[envc++] = str_printf("SSL_CLIENT_DN=%s", name);
+            name=X509_NAME2text(X509_get_issuer_name(peer_cert));
+            env[envc++] = str_printf("SSL_CLIENT_I_DN=%s", name);
+            X509_free(peer_cert);
+        }
+    }
+
+    env[envc] = NULL;
 
     pid=fork();
     c->pid=(unsigned long)pid;
     switch(pid) {
     case -1:    /* error */
+        for (i = 0; i < envc; i++)
+            str_free(env[i]);
         closesocket(fd[0]);
         closesocket(fd[1]);
         ioerror("fork");
         longjmp(c->err, 1);
     case  0:    /* child */
-        tls_alloc(NULL, c->tls, NULL); /* reuse thread-local storage */
         closesocket(fd[0]);
-        set_nonblock(fd[1], 0); /* switch back to blocking mode */
         /* dup2() does not copy FD_CLOEXEC flag */
         dup2(fd[1], 0);
         dup2(fd[1], 1);
@@ -1135,36 +1173,6 @@ NOEXPORT SOCKET connect_local(CLI *c) {
             dup2(fd[1], 2);
         closesocket(fd[1]); /* not really needed due to FD_CLOEXEC */
 
-        if(!getnameinfo(&c->peer_addr.sa, c->peer_addr_len,
-                host, 40, port, 6, NI_NUMERICHOST|NI_NUMERICSERV)) {
-            /* just don't set these variables if getnameinfo() fails */
-            putenv(str_printf("REMOTE_HOST=%s", host));
-            putenv(str_printf("REMOTE_PORT=%s", port));
-            if(c->opt->option.transparent_src) {
-#ifndef LIBDIR
-#define LIBDIR "."
-#endif
-#ifdef MACH64
-                putenv("LD_PRELOAD_32=" LIBDIR "/libstunnel.so");
-                putenv("LD_PRELOAD_64=" LIBDIR "/" MACH64 "/libstunnel.so");
-#elif __osf /* for Tru64 _RLD_LIST is used instead */
-                putenv("_RLD_LIST=" LIBDIR "/libstunnel.so:DEFAULT");
-#else
-                putenv("LD_PRELOAD=" LIBDIR "/libstunnel.so");
-#endif
-            }
-        }
-
-        if(c->ssl) {
-            peer_cert=SSL_get_peer_certificate(c->ssl);
-            if(peer_cert) {
-                name=X509_NAME2text(X509_get_subject_name(peer_cert));
-                putenv(str_printf("SSL_CLIENT_DN=%s", name));
-                name=X509_NAME2text(X509_get_issuer_name(peer_cert));
-                putenv(str_printf("SSL_CLIENT_I_DN=%s", name));
-                X509_free(peer_cert);
-            }
-        }
 #ifdef HAVE_PTHREAD_SIGMASK
         sigemptyset(&newmask);
         sigprocmask(SIG_SETMASK, &newmask, NULL);
@@ -1176,11 +1184,13 @@ NOEXPORT SOCKET connect_local(CLI *c) {
         signal(SIGTERM, SIG_DFL);
         signal(SIGQUIT, SIG_DFL);
         signal(SIGINT, SIG_DFL);
-        execvp(c->opt->exec_name, c->opt->exec_args);
+        execve(c->opt->exec_name, c->opt->exec_args, env);
         ioerror(c->opt->exec_name); /* execvp failed */
         _exit(1);
     default: /* parent */
         s_log(LOG_INFO, "Local mode child started (PID=%lu)", c->pid);
+        for (i = 0; i < envc; i++)
+            str_free(env[i]);
         closesocket(fd[1]);
         return fd[0];
     }


More information about the stunnel-users mailing list