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.htm...
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]; }
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Philip,
I didn't use the patch itself, but I implemented a similar functionality in stunnel 5.25b2: https://www.stunnel.org/downloads.html
Best regards, Mike
On 14.10.2015 10:41, Philip Craig wrote:
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_atfo
rk.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]; } _______________________________________________ stunnel-users mailing list stunnel-users@stunnel.org https://www.stunnel.org/cgi-bin/mailman/listinfo/stunnel-users
Hi Mike,
On 15/10/15 03:01, Michal Trojnara wrote:
I didn't use the patch itself, but I implemented a similar functionality in stunnel 5.25b2: https://www.stunnel.org/downloads.html
Thanks, that's a better fix, and I've tested it works.