[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