[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [torsocks/master] Fix: improve Unix socket passing detection
commit eb80d5cd10d10158b39c344ad035afe8d31a899f
Author: David Goulet <dgoulet@xxxxxxxxx>
Date: Wed Oct 22 15:25:23 2014 -0400
Fix: improve Unix socket passing detection
This commit adds the support for the torsocks recvmsg wrapper to detect
multiple FDs being passed through a Unix socket.
Furthermore, we now don't exit anymore but simply fire a debug message
and return EACCES to the caller.
Finally, a test is added for inet socket passing detection called
test_fd_passing.
Signed-off-by: David Goulet <dgoulet@xxxxxxxxx>
---
.gitignore | 1 +
src/lib/recv.c | 132 +++++++++---
tests/Makefile.am | 5 +-
tests/test_fd_passing.c | 520 +++++++++++++++++++++++++++++++++++++++++++++++
tests/test_list | 1 +
5 files changed, 632 insertions(+), 27 deletions(-)
diff --git a/.gitignore b/.gitignore
index ff6012e..c836b9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ src/bin/torsocks
tests/test_connect
tests/test_dns
tests/test_socket
+tests/test_fd_passing
tests/unit/test_onion
tests/unit/test_connection
tests/unit/test_utils
diff --git a/src/lib/recv.c b/src/lib/recv.c
index 036fa91..b034d72 100644
--- a/src/lib/recv.c
+++ b/src/lib/recv.c
@@ -26,21 +26,60 @@
TSOCKS_LIBC_DECL(recvmsg, LIBC_RECVMSG_RET_TYPE, LIBC_RECVMSG_SIG)
/*
+ * This is the maximum hardcoded amount of fd that is possible to pass through
+ * a Unix socket in the Linux kernel. On FreeBSD for instance it's MLEN which
+ * is defined to MSIZE (256) minus the msg header size thus way below this
+ * Linux limit. Such a shame there is no way to dynamically get that value or
+ * get it in an exposed ABI...
+ */
+#define SCM_MAX_FD 253
+
+/*
+ * Close all fds in the given array of size count.
+ */
+static void close_fds(int *fds, size_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ tsocks_libc_close(fds[i]);
+ }
+}
+
+/*
* Torsocks call for recvmsg(2)
*
* We only hijack this call to handle the FD passing between process on Unix
* socket. If an INET/INET6 socket is recevied, we stop everything because at
* that point we can't guarantee traffic going through Tor.
+ *
+ * Note that we don't rely on the given "msg" structure since it's controlled
+ * by the user and might not have been zeroed thus containing wrong values for
+ * ancillary data. Thus, we are going to expect SCM_MAX_FD and see what we can
+ * get from that if any.
*/
LIBC_RECVMSG_RET_TYPE tsocks_recvmsg(LIBC_RECVMSG_SIG)
{
- int fd;
+ int sock_domain;
+ socklen_t optlen;
ssize_t ret = 0;
- char dummy, recv_fd[CMSG_SPACE(sizeof(fd))];
+ char dummy, recv_fd[CMSG_SPACE(SCM_MAX_FD)];
struct iovec iov[1];
struct cmsghdr *cmsg;
struct msghdr msg_hdr;
+ /* Don't bother if the socket family is NOT Unix. */
+ optlen = sizeof(sock_domain);
+ ret = getsockopt(sockfd, SOL_SOCKET, SO_DOMAIN, &sock_domain, &optlen);
+ if (ret < 0) {
+ DBG("[recvmsg] Fail getsockopt() on sock %d", sockfd);
+ errno = EBADF;
+ goto error;
+ }
+ if (sock_domain != AF_UNIX) {
+ goto libc;
+ }
+
memset(&msg_hdr, 0, sizeof(msg_hdr));
/* Prepare to receive the structures */
@@ -55,42 +94,83 @@ LIBC_RECVMSG_RET_TYPE tsocks_recvmsg(LIBC_RECVMSG_SIG)
/* Just peek the data to inspect the payload for fd. */
ret = tsocks_libc_recvmsg(sockfd, &msg_hdr, MSG_PEEK);
} while (ret < 0 && errno == EINTR);
-
+ if (ret < 0) {
+ /* Use the current errno set by the call above. */
+ goto error;
+ }
cmsg = CMSG_FIRSTHDR(&msg_hdr);
if (!cmsg) {
- goto end;
+ /* No control message header, safe to pass to libc. */
+ goto libc;
+ }
+ if (msg_hdr.msg_flags & MSG_CTRUNC) {
+ /*
+ * This means there are actually *more* data in the control thus
+ * exceeding somehow our hard limit of SCM_MAX_FD. In that case, return
+ * an error since we can't guarantee anything for socket passing
+ */
+ errno = EMSGSIZE;
+ goto error;
}
/*
* Detecting FD passing, the next snippet of code will check if we get a
- * inet/inet6 socket. If so, everything stops immediately before going
- * further.
+ * inet/inet6 socket. If so, we are going to close the received socket,
+ * wipe clean the cmsg payload and return an unauthorized access code.
*/
if (cmsg->cmsg_type == SCM_RIGHTS || cmsg->cmsg_level == SOL_SOCKET) {
- struct sockaddr addr;
- socklen_t addrlen;
- sa_family_t family = AF_UNSPEC;
-
- memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
-
- /* Get socket protocol family. */
- addrlen = sizeof(addr);
- ret = getsockname(fd, &addr, &addrlen);
- if (ret < 0) {
- /* Use the getsockname() errno value. */
- goto end;
- }
-
- family = addr.sa_family;
- if (family == AF_INET || family == AF_INET6) {
- ERR("[recvmsg] Inet socket passing detected. Aborting everything! "
- "A non Tor socket could be used thus leaking information.");
- exit(EXIT_FAILURE);
+ /*
+ * The kernel control that len value and there is a hard limit so no
+ * chance here of having a crazy high value that could exhaust the
+ * stack memory.
+ */
+ size_t sizeof_fds = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(int);
+ int i, fds[sizeof_fds];
+
+ memcpy(&fds, CMSG_DATA(cmsg), sizeof(fds));
+
+ /*
+ * For each received fds, we will inspect them to see if there is an
+ * inet socket in there and if so, we have to stop, close everything to
+ * avoid fd leak and return an error.
+ */
+ for (i = 0; i < sizeof_fds; i++) {
+ struct sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+
+ memset(&addr, 0, addrlen);
+
+ /* Get socket protocol family. */
+ ret = getsockname(fds[i], &addr, &addrlen);
+ if (ret < 0) {
+ /* Either a bad fd or not a socket. */
+ continue;
+ }
+
+ if (addr.sa_family == AF_INET || addr.sa_family == AF_INET6) {
+ DBG("[recvmsg] Inet socket passing detected. Denying it.");
+ /* We found socket, close everything and return error. */
+ close_fds(fds, sizeof_fds);
+ /*
+ * The recv(2) man page does *not* mention that errno value
+ * however it's acceptable because Linux LSM can return this
+ * code if the access is denied in the application by a
+ * security module. We are basically simulating this here.
+ */
+ errno = EACCES;
+ ret = -1;
+ goto error;
+ }
}
}
-end:
+ /* At this point, NO socket was detected, continue to the libc safely. */
+
+libc:
return tsocks_libc_recvmsg(LIBC_RECVMSG_ARGS);
+
+error:
+ return ret;
}
/*
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 141ac5e..b916134 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,7 +6,7 @@ LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la
LIBTORSOCKS=$(top_builddir)/src/lib/libtorsocks.la
-noinst_PROGRAMS = test_dns test_socket test_connect
+noinst_PROGRAMS = test_dns test_socket test_connect test_fd_passing
test_dns_SOURCES = test_dns.c
test_dns_LDADD = $(LIBTAP) $(LIBTORSOCKS)
@@ -17,6 +17,9 @@ test_socket_LDADD = $(LIBTAP) $(LIBTORSOCKS)
test_connect_SOURCES = test_connect.c
test_connect_LDADD = $(LIBTAP) $(LIBTORSOCKS)
+test_fd_passing_SOURCES = test_fd_passing.c
+test_fd_passing_LDADD = $(LIBTAP) $(LIBTORSOCKS) -lpthread
+
check-am:
./run.sh test_list
diff --git a/tests/test_fd_passing.c b/tests/test_fd_passing.c
new file mode 100644
index 0000000..1803126
--- /dev/null
+++ b/tests/test_fd_passing.c
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2014 - David Goulet <dgoulet@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License, version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <limits.h>
+#include <sys/un.h>
+
+#include <lib/torsocks.h>
+
+#include <tap/tap.h>
+
+#define NUM_TESTS 5
+
+/*
+ * Indicate if the thread recv is ready. 0 means no, 1 means yes and -1 means
+ * error occured.
+ */
+static volatile int thread_recv_ready;
+
+/* Unix socket for this test. */
+static const char *sockpath = "/tmp/torsocks-unix-fd-passing.sock";
+
+/* Order libtap output. */
+static pthread_mutex_t tsocks_test_log = PTHREAD_MUTEX_INITIALIZER;
+#define OK(cond, args...) \
+ do { \
+ pthread_mutex_lock(&tsocks_test_log); \
+ ok(cond, ## args); \
+ pthread_mutex_unlock(&tsocks_test_log); \
+ } while (0);
+
+/*
+ * Send buf data of size len. Using sendmsg API.
+ *
+ * Return the size of sent data.
+ */
+static ssize_t send_unix_sock(int sock, void *buf, size_t len)
+{
+ struct msghdr msg;
+ struct iovec iov[1];
+ ssize_t ret = -1;
+
+ memset(&msg, 0, sizeof(msg));
+
+ iov[0].iov_base = buf;
+ iov[0].iov_len = len;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ ret = sendmsg(sock, &msg, 0);
+ if (ret < 0) {
+ /*
+ * Only warn about EPIPE when quiet mode is deactivated.
+ * We consider EPIPE as expected.
+ */
+ if (errno != EPIPE) {
+ perror("sendmsg");
+ }
+ }
+
+ return ret;
+}
+
+static ssize_t send_fds_unix_sock(int sock, int *fds, size_t nb_fd)
+{
+ struct msghdr msg;
+ struct cmsghdr *cmptr;
+ struct iovec iov[1];
+ ssize_t ret = -1;
+ unsigned int sizeof_fds = nb_fd * sizeof(int);
+ char tmp[CMSG_SPACE(sizeof_fds)];
+ char dummy = 0;
+
+ memset(&msg, 0, sizeof(msg));
+
+ msg.msg_control = (caddr_t)tmp;
+ msg.msg_controllen = CMSG_LEN(sizeof_fds);
+
+ cmptr = CMSG_FIRSTHDR(&msg);
+ cmptr->cmsg_level = SOL_SOCKET;
+ cmptr->cmsg_type = SCM_RIGHTS;
+ cmptr->cmsg_len = CMSG_LEN(sizeof_fds);
+ memcpy(CMSG_DATA(cmptr), fds, sizeof_fds);
+ /* Sum of the length of all control messages in the buffer: */
+ msg.msg_controllen = cmptr->cmsg_len;
+
+ iov[0].iov_base = &dummy;
+ iov[0].iov_len = 1;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ ret = sendmsg(sock, &msg, 0);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ /*
+ * Only warn about EPIPE when quiet mode is deactivated.
+ * We consider EPIPE as expected.
+ */
+ if (errno != EPIPE) {
+ perror("sendmsg");
+ }
+ }
+ return ret;
+}
+
+/*
+ * Receive data of size len in put that data into the buf param. Using recvmsg
+ * API.
+ *
+ * Return the size of received data.
+ */
+static ssize_t recv_unix_sock(int sock, void *buf, size_t len)
+{
+ struct msghdr msg;
+ struct iovec iov[1];
+ ssize_t ret = -1;
+ size_t len_last;
+
+ memset(&msg, 0, sizeof(msg));
+
+ iov[0].iov_base = buf;
+ iov[0].iov_len = len;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ len_last = iov[0].iov_len;
+ ret = recvmsg(sock, &msg, 0);
+ if (ret > 0) {
+ iov[0].iov_base += ret;
+ iov[0].iov_len -= ret;
+ assert(ret <= len_last);
+ }
+ } while ((ret > 0 && ret < len_last) || (ret < 0 && errno == EINTR));
+ if (ret < 0) {
+ perror("recvmsg");
+ } else if (ret > 0) {
+ ret = len;
+ }
+ /* Else ret = 0 meaning an orderly shutdown. */
+
+ return ret;
+}
+
+/*
+ * Recv a message accompanied by fd(s) from a unix socket.
+ *
+ * Returns the size of received data, or negative error value.
+ *
+ * Expect at most "nb_fd" file descriptors. Returns the number of fd
+ * actually received in nb_fd.
+ */
+static ssize_t recv_fds_unix_sock(int sock, int *fds, size_t nb_fd)
+{
+ struct iovec iov[1];
+ ssize_t ret = 0;
+ struct cmsghdr *cmsg;
+ size_t sizeof_fds = nb_fd * sizeof(int);
+ char recv_fd[CMSG_SPACE(sizeof_fds)];
+ struct msghdr msg;
+ char dummy;
+
+ memset(&msg, 0, sizeof(msg));
+
+ /* Prepare to receive the structures */
+ iov[0].iov_base = &dummy;
+ iov[0].iov_len = 1;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = recv_fd;
+ msg.msg_controllen = sizeof(recv_fd);
+
+ do {
+ ret = recvmsg(sock, &msg, 0);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ goto end;
+ }
+ if (ret != 1) {
+ fprintf(stderr, "Error: Received %zd bytes, expected %d\n",
+ ret, 1);
+ goto end;
+ }
+ if (msg.msg_flags & MSG_CTRUNC) {
+ fprintf(stderr, "Error: Control message truncated.\n");
+ ret = -1;
+ goto end;
+ }
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg) {
+ fprintf(stderr, "Error: Invalid control message header\n");
+ ret = -1;
+ goto end;
+ }
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
+ fprintf(stderr, "Didn't received any fd\n");
+ ret = -1;
+ goto end;
+ }
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof_fds)) {
+ fprintf(stderr, "Error: Received %zu bytes of ancillary data, expected %zu\n",
+ (size_t) cmsg->cmsg_len, (size_t) CMSG_LEN(sizeof_fds));
+ ret = -1;
+ goto end;
+ }
+ memcpy(fds, CMSG_DATA(cmsg), sizeof_fds);
+ ret = sizeof_fds;
+end:
+ return ret;
+}
+
+/*
+ * Connect to unix socket using the path name.
+ */
+static int connect_unix_sock(const char *pathname)
+{
+ struct sockaddr_un sun;
+ int fd, ret, closeret;
+
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ perror("socket");
+ ret = fd;
+ goto error;
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, pathname, sizeof(sun.sun_path));
+ sun.sun_path[sizeof(sun.sun_path) - 1] = '\0';
+
+ ret = connect(fd, (struct sockaddr *) &sun, sizeof(sun));
+ if (ret < 0) {
+ /*
+ * Don't print message on connect error, because connect is used in
+ * normal execution to detect if sessiond is alive.
+ */
+ goto error_connect;
+ }
+
+ return fd;
+
+error_connect:
+ closeret = close(fd);
+ if (closeret) {
+ perror("close");
+ }
+error:
+ return ret;
+}
+
+/*
+ * Creates a AF_UNIX local socket using pathname bind the socket upon creation
+ * and return the fd.
+ */
+static int create_unix_sock(const char *pathname)
+{
+ struct sockaddr_un sun;
+ int fd;
+ int ret = -1;
+
+ /* Create server socket */
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ goto error;
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strncpy(sun.sun_path, pathname, sizeof(sun.sun_path));
+ sun.sun_path[sizeof(sun.sun_path) - 1] = '\0';
+
+ /* Unlink the old file if present */
+ (void) unlink(pathname);
+ ret = bind(fd, (struct sockaddr *) &sun, sizeof(sun));
+ if (ret < 0) {
+ perror("bind");
+ goto error;
+ }
+
+ return fd;
+
+error:
+ return ret;
+}
+
+/*
+ * Do an accept(2) on the sock and return the new file descriptor. The socket
+ * MUST be bind(2) before.
+ */
+static int accept_unix_sock(int sock)
+{
+ int new_fd;
+ struct sockaddr_un sun;
+ socklen_t len = 0;
+
+ /* Blocking call */
+ new_fd = accept(sock, (struct sockaddr *) &sun, &len);
+ if (new_fd < 0) {
+ perror("accept");
+ }
+
+ return new_fd;
+}
+
+void *thread_recv(void *data)
+{
+ int ret, new_sock, sock, fds[3] = {-1, -1, -1};
+ char buf[4];
+ ssize_t len;
+
+ sock = create_unix_sock(sockpath);
+ if (sock < 0) {
+ fail("Create unix socket at %s", sockpath);
+ goto error;
+ }
+
+ OK(sock >= 0, "Unix socket %d created at %s", sock, sockpath);
+
+ ret = listen(sock, 10);
+ if (ret < 0) {
+ fail("Listen on unix socket %d", sock);
+ goto error;
+ }
+
+ /* Notify we are ready to test. */
+ thread_recv_ready = 1;
+
+ new_sock = accept_unix_sock(sock);
+ if (new_sock < 0) {
+ fail("Accept on unix sock %d", sock);
+ close(sock);
+ goto error;
+ }
+
+ /* First receive a normal message saying "hello" to make sure the recvmsg
+ * call is not borked. */
+ len = recv_unix_sock(new_sock, buf, sizeof(buf));
+ if (len < 0) {
+ fail("Recv normal data failed");
+ goto error;
+ }
+ OK(len == sizeof(buf) &&
+ strncmp(buf, "hello", sizeof(buf)) == 0,
+ "Data received successfully");
+
+ len = recv_fds_unix_sock(new_sock, fds, 3);
+ if (len < 0) {
+ /* This is suppose to fail with a errno set to EACCESS. */
+ OK(errno == EACCES,
+ "Passing INET socket denied.");
+ } else {
+ fail("Received INET socket through the unix socket");
+ }
+
+ close(sock);
+ close(new_sock);
+ close(fds[0]);
+ close(fds[1]);
+ close(fds[2]);
+
+error:
+ thread_recv_ready = -1;
+ return NULL;
+}
+
+void *thread_send(void *data)
+{
+ int sock, fds[3], pipe_fds[2];
+ ssize_t len;
+
+ sock = connect_unix_sock(sockpath);
+ if (sock < 0) {
+ fail("Unable to connect to unix socket at %s", sockpath);
+ goto error;
+ }
+
+ if (pipe(pipe_fds) < 0) {
+ fail("Unable to create pipe");
+ goto error;
+ }
+
+ /* First send regular data. */
+ len = send_unix_sock(sock, "hello", 4);
+ if (len < 0) {
+ fail("Sending regular data.");
+ goto error;
+ }
+
+ /*
+ * We are going to pass 3 fds, two of them are pipse in position 0 and 2
+ * and the inet socket is at position 1.
+ */
+ fds[0] = pipe_fds[0];
+ fds[1] = *((int *)data);
+ fds[2] = pipe_fds[1];
+
+ len = send_fds_unix_sock(sock, fds, 3);
+ if (len < 0) {
+ fail("Send inet socket through Unix sock");
+ goto error;
+ }
+ OK(len == 1, "Inet socket %d sent successfully.", fds[1]);
+
+error:
+ if (sock >= 0) {
+ close(sock);
+ }
+ if (pipe_fds[0] >= 0) {
+ close(pipe_fds[0]);
+ }
+ if (pipe_fds[1] >= 0) {
+ close(pipe_fds[1]);
+ }
+ return NULL;
+}
+
+/*
+ * This test will spawn two thread, one accepting a Unix socket connection
+ * which will recv the fd(s). The second thread will connect and send the fds.
+ * Usually this is between processes but for the sake of the test threads are
+ * enough.
+ */
+static void test_inet_socket(void)
+{
+ int ret, i, inet_sock = -1;
+ void *status;
+ pthread_t th[2];
+ struct sockaddr_in addr;
+ const char *ip = "93.95.227.222";
+
+ /*
+ * First of all, we are going to try to create an inet socket to a public
+ * known IP being www.torproject.org --> 93.95.227.222.
+ */
+ inet_sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (inet_sock < 0) {
+ fail("Creating inet socket");
+ goto error;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(443);
+ inet_pton(addr.sin_family, ip, &addr.sin_addr);
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+
+ ret = connect(inet_sock, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret < 0) {
+ fail("Unable to connect inet socket");
+ goto error;
+ }
+
+ OK(!ret, "Inet socket %d created connected to %s", inet_sock, ip);
+
+ ret = pthread_create(&th[0], NULL, thread_recv, NULL);
+ if (ret < 0) {
+ fail("pthread_create thread recv");
+ goto error;
+ }
+
+ /* Active wait for the thread recv to be ready. */
+ while (thread_recv_ready == 0) {
+ continue;
+ }
+
+ if (thread_recv_ready == -1) {
+ goto error;
+ }
+
+ ret = pthread_create(&th[1], NULL, thread_send, (void *) &inet_sock);
+ if (ret < 0) {
+ fail("pthread_create thread send");
+ goto error;
+ }
+
+ for (i = 0; i < 2; i++) {
+ ret = pthread_join(th[i], &status);
+ if (ret < 0) {
+ perror("pthread_join");
+ }
+ }
+
+error:
+ if (inet_sock >= 0) {
+ close(inet_sock);
+ }
+ unlink(sockpath);
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ /* Libtap call for the number of tests planned. */
+ plan_tests(NUM_TESTS);
+
+ test_inet_socket();
+
+ return 0;
+}
diff --git a/tests/test_list b/tests/test_list
index bb812b1..fb320db 100644
--- a/tests/test_list
+++ b/tests/test_list
@@ -1,5 +1,6 @@
./test_connect
./test_dns
+./test_fd_passing
./test_socket
./unit/test_onion
./unit/test_connection
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits