diff options
author | Jacob Appelbaum <jacob@appelbaum.net> | 2014-08-11 12:27:04 -0700 |
---|---|---|
committer | Andrea Shepard <andrea@torproject.org> | 2015-01-07 17:42:57 +0000 |
commit | 8d59ddf3cba541c6578dff121e8f0623a7606bab (patch) | |
tree | a85c724acac38346025042b128c60f20337dda05 | |
parent | 1abd526c75eade83318a6ec6aff84d5f0f079a3b (diff) | |
download | tor-8d59ddf3cba541c6578dff121e8f0623a7606bab.tar.gz tor-8d59ddf3cba541c6578dff121e8f0623a7606bab.zip |
Commit second draft of Jake's SOCKS5-over-AF_UNIX patch. See ticket #12585.
Signed-off-by: Andrea Shepard <andrea@torproject.org>
-rw-r--r-- | changes/bug12585 | 9 | ||||
-rw-r--r-- | doc/tor.1.txt | 9 | ||||
-rw-r--r-- | src/common/address.c | 18 | ||||
-rw-r--r-- | src/or/config.c | 24 | ||||
-rw-r--r-- | src/or/connection.c | 246 | ||||
-rw-r--r-- | src/or/connection_edge.c | 6 | ||||
-rw-r--r-- | src/or/main.c | 4 | ||||
-rw-r--r-- | src/or/or.h | 8 | ||||
-rw-r--r-- | src/or/relay.c | 5 |
9 files changed, 275 insertions, 54 deletions
diff --git a/changes/bug12585 b/changes/bug12585 new file mode 100644 index 0000000000..ccdcd17e6c --- /dev/null +++ b/changes/bug12585 @@ -0,0 +1,9 @@ + o Major features (security) + - Implementation of SocksSocket option - SocksSocket implements a SOCKS + proxy reachable by Unix Domain Socket. This allows client applications to + communicate with Tor without having the ability to create AF_INET or + AF_INET6 family sockets. If an application has permission to create a socket + with AF_UNIX, it may directly communicate with Tor as if it were an other + SOCKS proxy. This should allow high risk applications to be entirely prevented + from connecting directly with TCP/IP, they will be able to only connect to the + internet through AF_UNIX and only through Tor. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 9e86a67359..43aade9d9e 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -483,6 +483,15 @@ GENERAL OPTIONS in accordance to RFC 1929. Both username and password must be between 1 and 255 characters. +[[SocksSocket]] **SocksSocket** __Path__:: + Like SocksPort, but listens on a Unix domain socket, rather than a TCP + socket. (Unix and Unix-like systems only.) + +[[SocksSocketsGroupWritable]] **SocksSocketsGroupWritable** **0**|**1**:: + If this option is set to 0, don't allow the filesystem group to read and + write unix sockets (e.g. SocksSocket). If the option is set to 1, make + the SocksSocket socket readable and writable by the default GID. (Default: 0) + [[KeepalivePeriod]] **KeepalivePeriod** __NUM__:: To keep firewalls from expiring connections, send a padding keepalive cell every NUM seconds on open connections that are in use. If the connection diff --git a/src/common/address.c b/src/common/address.c index a80926049a..1c3777fa82 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -121,6 +121,15 @@ tor_addr_to_sockaddr(const tor_addr_t *a, } } +/** Set address <b>a</b> to zero. This address belongs to + * the AF_UNIX family. */ +static void +tor_addr_make_af_unix(tor_addr_t *a) +{ + memset(a, 0, sizeof(*a)); + a->family = AF_UNIX; +} + /** Set the tor_addr_t in <b>a</b> to contain the socket address contained in * <b>sa</b>. */ int @@ -142,6 +151,9 @@ tor_addr_from_sockaddr(tor_addr_t *a, const struct sockaddr *sa, tor_addr_from_in6(a, &sin6->sin6_addr); if (port_out) *port_out = ntohs(sin6->sin6_port); + } else if (sa->sa_family == AF_UNIX) { + tor_addr_make_af_unix(a); + return 0; } else { tor_addr_make_unspec(a); return -1; @@ -421,6 +433,10 @@ tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate) ptr = dest; } break; + case AF_UNIX: + tor_snprintf(dest, len, "AF_UNIX"); + ptr = dest; + break; default: return NULL; } @@ -816,6 +832,8 @@ tor_addr_is_null(const tor_addr_t *addr) } case AF_INET: return (tor_addr_to_ipv4n(addr) == 0); + case AF_UNIX: + return 1; case AF_UNSPEC: return 1; default: diff --git a/src/or/config.c b/src/or/config.c index 2fa077e146..e080e7749b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -190,6 +190,8 @@ static config_var_t option_vars_[] = { V(ControlPortWriteToFile, FILENAME, NULL), V(ControlSocket, LINELIST, NULL), V(ControlSocketsGroupWritable, BOOL, "0"), + V(SocksSocket, LINELIST, NULL), + V(SocksSocketsGroupWritable, BOOL, "0"), V(CookieAuthentication, BOOL, "0"), V(CookieAuthFileGroupReadable, BOOL, "0"), V(CookieAuthFile, STRING, NULL), @@ -1030,6 +1032,20 @@ options_act_reversible(const or_options_t *old_options, char **msg) } #endif +#ifndef HAVE_SYS_UN_H + if (options->SocksSocket || options->SocksSocketsGroupWritable) { + *msg = tor_strdup("Unix domain sockets (SocksSocket) not supported " + "on this OS/with this build."); + goto rollback; + } +#else + if (options->SocksSocketsGroupWritable && !options->SocksSocket) { + *msg = tor_strdup("Setting SocksSocketGroupWritable without setting" + "a SocksSocket makes no sense."); + goto rollback; + } +#endif + if (running_tor) { int n_ports=0; /* We need to set the connection limit before we can open the listeners. */ @@ -6120,6 +6136,12 @@ parse_ports(or_options_t *options, int validate_only, *msg = tor_strdup("Invalid ControlSocket configuration"); goto err; } + if (parse_unix_socket_config(ports, + options->SocksSocket, + CONN_TYPE_AP_LISTENER) < 0) { + *msg = tor_strdup("Invalid SocksSocket configuration"); + goto err; + } } if (! options->ClientOnly) { if (parse_port_config(ports, @@ -6163,6 +6185,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER); options->SocksPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER); + options->SocksSocket_set = + !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER); options->TransPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER); options->NATDPort_set = diff --git a/src/or/connection.c b/src/or/connection.c index c67cc3c111..ad7e6de9b6 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -308,6 +308,8 @@ entry_connection_new(int type, int socket_family) entry_conn->ipv4_traffic_ok = 1; else if (socket_family == AF_INET6) entry_conn->ipv6_traffic_ok = 1; + else if (socket_family == AF_UNIX) + entry_conn->is_socks_socket = 1; return entry_conn; } @@ -516,9 +518,10 @@ connection_free_(connection_t *conn) buf_free(conn->outbuf); } else { if (conn->socket_family == AF_UNIX) { - /* For now only control ports can be Unix domain sockets + /* For now only control and SOCKS ports can be Unix domain sockets * and listeners at the same time */ - tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); + tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER || + conn->type == CONN_TYPE_AP_LISTENER); if (unlink(conn->address) < 0 && errno != ENOENT) { log_warn(LD_NET, "Could not unlink %s: %s", conn->address, @@ -954,6 +957,47 @@ check_location_for_unix_socket(const or_options_t *options, const char *path) } #endif +#ifdef HAVE_SYS_UN_H +/** Check whether we should be willing to open an AF_UNIX socket in + * <b>path</b>. Return 0 if we should go ahead and -1 if we shouldn't. */ +static int +check_location_for_socks_unix_socket(const or_options_t *options, + const char *path) +{ + int r = -1; + char *p = tor_strdup(path); + cpd_check_t flags = CPD_CHECK_MODE_ONLY; + if (get_parent_directory(p)<0 || p[0] != '/') { + log_warn(LD_GENERAL, "Bad unix socket address '%s'. Tor does not support " + "relative paths for unix sockets.", path); + goto done; + } + + if (options->SocksSocketsGroupWritable) + flags |= CPD_GROUP_OK; + + if (check_private_dir(p, flags, options->User) < 0) { + char *escpath, *escdir; + escpath = esc_for_log(path); + escdir = esc_for_log(p); + log_warn(LD_GENERAL, "Before Tor can create a SocksSocket in %s, the " + "directory %s needs to exist, and to be accessible only by the " + "user%s account that is running Tor. (On some Unix systems, " + "anybody who can list a socket can connect to it, so Tor is " + "being careful.)", escpath, escdir, + options->SocksSocketsGroupWritable ? " and group" : ""); + tor_free(escpath); + tor_free(escdir); + goto done; + } + + r = 0; + done: + tor_free(p); + return r; +} +#endif + /** Tell the TCP stack that it shouldn't wait for a long time after * <b>sock</b> has closed before reusing its port. Return 0 on success, * -1 on failure. */ @@ -1029,30 +1073,103 @@ connection_listener_new(const struct sockaddr *listensockaddr, } if (listensockaddr->sa_family == AF_INET || - listensockaddr->sa_family == AF_INET6) { + listensockaddr->sa_family == AF_INET6 || + (listensockaddr->sa_family == AF_UNIX && + type != CONN_TYPE_CONTROL_LISTENER)) { int is_tcp = (type != CONN_TYPE_AP_DNS_LISTENER); if (is_tcp) start_reading = 1; - tor_addr_from_sockaddr(&addr, listensockaddr, &usePort); + if ( listensockaddr->sa_family == AF_INET || + listensockaddr->sa_family == AF_INET6) { - log_notice(LD_NET, "Opening %s on %s", - conn_type_to_string(type), fmt_addrport(&addr, usePort)); - - s = tor_open_socket_nonblocking(tor_addr_family(&addr), - is_tcp ? SOCK_STREAM : SOCK_DGRAM, - is_tcp ? IPPROTO_TCP: IPPROTO_UDP); - if (!SOCKET_OK(s)) { - log_warn(LD_NET,"Socket creation failed: %s", - tor_socket_strerror(tor_socket_errno(-1))); - goto err; + tor_addr_from_sockaddr(&addr, listensockaddr, &usePort); + + log_notice(LD_NET, "Opening %s on %s", + conn_type_to_string(type), fmt_addrport(&addr, usePort)); + + s = tor_open_socket_nonblocking(tor_addr_family(&addr), + is_tcp ? SOCK_STREAM : SOCK_DGRAM, + is_tcp ? IPPROTO_TCP: IPPROTO_UDP); + if (!SOCKET_OK(s)) { + log_warn(LD_NET, "Socket creation failed: %s", + tor_socket_strerror(tor_socket_errno(-1))); + goto err; + } + + if (make_socket_reuseable(s) < 0) { + log_warn(LD_NET, "Error setting SO_REUSEADDR flag on %s: %s", + conn_type_to_string(type), + tor_socket_strerror(errno)); + } } - if (make_socket_reuseable(s) < 0) { - log_warn(LD_NET, "Error setting SO_REUSEADDR flag on %s: %s", - conn_type_to_string(type), - tor_socket_strerror(errno)); +#ifdef HAVE_SYS_UN_H + if (listensockaddr->sa_family == AF_UNIX && + type != CONN_TYPE_CONTROL_LISTENER) { + tor_assert(listensockaddr->sa_family == AF_UNIX); + + if (check_location_for_socks_unix_socket(options, address) < 0) + goto err; + + log_notice(LD_NET, "Opening SocksSocket %s on %s", + conn_type_to_string(type), address); + + tor_addr_make_unspec(&addr); + + if (unlink(address) < 0 && errno != ENOENT) { + log_warn(LD_NET, "Could not unlink %s: %s", address, + strerror(errno)); + goto err; + } + + s = tor_open_socket_nonblocking(AF_UNIX, SOCK_STREAM, 0); + if (! SOCKET_OK(s)) { + log_warn(LD_NET, "SocksSocket socket creation failed: %s.", + strerror(errno)); + goto err; + } + + if (bind(s, listensockaddr, + (socklen_t)sizeof(struct sockaddr_un)) == -1) { + log_warn(LD_NET, "Bind to %s failed: %s.", address, + tor_socket_strerror(tor_socket_errno(s))); + goto err; + } +#ifdef HAVE_PWD_H + if (options->User) { + pw = getpwnam(options->User); + if (pw == NULL) { + log_warn(LD_NET, + "Unable to chown() %s socket: user %s not found.", + address, options->User); + goto err; + } else if (chown(address, pw->pw_uid, pw->pw_gid) < 0) { + log_warn(LD_NET, "Unable to chown() %s socket: %s.", + address, strerror(errno)); + goto err; + } + } +#endif + + if (options->SocksSocketsGroupWritable) { + /* We need to use chmod; fchmod doesn't work on sockets on all + * platforms. */ + if (chmod(address, 0660) < 0) { + log_warn(LD_FS, "Unable to make %s group-writable.", address); + goto err; + } + } + + if (listen(s, SOMAXCONN) < 0) { + log_warn(LD_NET, "Could not listen on %s: %s", address, + tor_socket_strerror(tor_socket_errno(s))); + goto err; + } } +#else + (void)options; +#endif /* HAVE_SYS_UN_H */ #if defined USE_TRANSPARENT && defined(IP_TRANSPARENT) if (options->TransProxyType_parsed == TPT_TPROXY && @@ -1090,48 +1207,53 @@ connection_listener_new(const struct sockaddr *listensockaddr, } #endif - if (bind(s,listensockaddr,socklen) < 0) { - const char *helpfulhint = ""; - int e = tor_socket_errno(s); - if (ERRNO_IS_EADDRINUSE(e)) - helpfulhint = ". Is Tor already running?"; - log_warn(LD_NET, "Could not bind to %s:%u: %s%s", address, usePort, - tor_socket_strerror(e), helpfulhint); - goto err; - } - - if (is_tcp) { - if (tor_listen(s) < 0) { - log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort, - tor_socket_strerror(tor_socket_errno(s))); + if (listensockaddr->sa_family != AF_UNIX) { + if (bind(s,listensockaddr,socklen) < 0) { + const char *helpfulhint = ""; + int e = tor_socket_errno(s); + if (ERRNO_IS_EADDRINUSE(e)) + helpfulhint = ". Is Tor already running?"; + log_warn(LD_NET, "Could not bind to %s:%u: %s%s", address, usePort, + tor_socket_strerror(e), helpfulhint); goto err; } - } - if (usePort != 0) { - gotPort = usePort; - } else { - tor_addr_t addr2; - struct sockaddr_storage ss; - socklen_t ss_len=sizeof(ss); - if (getsockname(s, (struct sockaddr*)&ss, &ss_len)<0) { - log_warn(LD_NET, "getsockname() couldn't learn address for %s: %s", - conn_type_to_string(type), - tor_socket_strerror(tor_socket_errno(s))); - gotPort = 0; + if (is_tcp) { + if (tor_listen(s) < 0) { + log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort, + tor_socket_strerror(tor_socket_errno(s))); + goto err; + } + } + + if (usePort != 0) { + gotPort = usePort; + } else { + tor_addr_t addr2; + struct sockaddr_storage ss; + socklen_t ss_len=sizeof(ss); + if (getsockname(s, (struct sockaddr*)&ss, &ss_len)<0) { + log_warn(LD_NET, "getsockname() couldn't learn address for %s: %s", + conn_type_to_string(type), + tor_socket_strerror(tor_socket_errno(s))); + gotPort = 0; + } + tor_addr_from_sockaddr(&addr2, (struct sockaddr*)&ss, &gotPort); } - tor_addr_from_sockaddr(&addr2, (struct sockaddr*)&ss, &gotPort); } #ifdef HAVE_SYS_UN_H - } else if (listensockaddr->sa_family == AF_UNIX) { + } else if (listensockaddr->sa_family == AF_UNIX && + type != CONN_TYPE_AP_LISTENER) { start_reading = 1; /* For now only control ports can be Unix domain sockets * and listeners at the same time */ tor_assert(type == CONN_TYPE_CONTROL_LISTENER); - if (check_location_for_unix_socket(options, address) < 0) - goto err; + if ( type == CONN_TYPE_CONTROL_LISTENER ) { + if (check_location_for_unix_socket(options, address) < 0) + goto err; + } log_notice(LD_NET, "Opening %s on %s", conn_type_to_string(type), address); @@ -1177,6 +1299,15 @@ connection_listener_new(const struct sockaddr *listensockaddr, } } + if (options->SocksSocketsGroupWritable) { + /* We need to use chmod; fchmod doesn't work on sockets on all + * platforms. */ + if (chmod(address, 0660) < 0) { + log_warn(LD_FS,"Unable to make %s group-writable.", address); + goto err; + } + } + if (listen(s, SOMAXCONN) < 0) { log_warn(LD_NET, "Could not listen on %s: %s", address, tor_socket_strerror(tor_socket_errno(s))); @@ -1294,6 +1425,8 @@ check_sockaddr(const struct sockaddr *sa, int len, int level) "Address for new connection has address/port equal to zero."); ok = 0; } + } else if (sa->sa_family == AF_UNIX) { + ok = 1; } else { ok = 0; } @@ -1378,7 +1511,8 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; } - if (conn->socket_family == AF_INET || conn->socket_family == AF_INET6) { + if (conn->socket_family == AF_INET || conn->socket_family == AF_INET6 || + (conn->socket_family == AF_UNIX && new_type == CONN_TYPE_AP)) { tor_addr_t addr; uint16_t port; if (check_sockaddr(remote, remotelen, LOG_INFO)<0) { @@ -1419,7 +1553,16 @@ connection_handle_listener_read(connection_t *conn, int new_type) newconn->port = port; newconn->address = tor_dup_addr(&addr); - if (new_type == CONN_TYPE_AP) { + if (new_type == CONN_TYPE_AP && conn->socket_family != AF_UNIX) { + log_notice(LD_NET, "New SOCKS connection opened from %s.", + fmt_and_decorate_addr(&addr)); + TO_ENTRY_CONN(newconn)->socks_request->socks_prefer_no_auth = + TO_LISTENER_CONN(conn)->socks_prefer_no_auth; + } + if (new_type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) { + newconn->port = 0; + newconn->address = tor_strdup(conn->address); + log_notice(LD_NET, "New SOCKS SocksSocket connection opened"); TO_ENTRY_CONN(newconn)->socks_request->socks_prefer_no_auth = TO_LISTENER_CONN(conn)->socks_prefer_no_auth; } @@ -1428,7 +1571,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) fmt_and_decorate_addr(&addr)); } - } else if (conn->socket_family == AF_UNIX) { + } else if (conn->socket_family == AF_UNIX && conn->type != CONN_TYPE_AP) { /* For now only control ports can be Unix domain sockets * and listeners at the same time */ tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); @@ -2392,6 +2535,7 @@ connection_is_rate_limited(connection_t *conn) return 0; /* Internal connection */ else if (! options->CountPrivateBandwidth && (tor_addr_family(&conn->addr) == AF_UNSPEC || /* no address */ + tor_addr_family(&conn->addr) == AF_UNIX || /* no address */ tor_addr_is_internal(&conn->addr, 0))) return 0; /* Internal address */ else diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index d8f397bd90..390ad07b83 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1233,7 +1233,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, { tor_addr_t addr; /* XXX Duplicate call to tor_addr_parse. */ - if (tor_addr_parse(&addr, socks->address) >= 0) { + if (tor_addr_parse(&addr, socks->address) >= 0 && + !conn->is_socks_socket) { sa_family_t family = tor_addr_family(&addr); if ((family == AF_INET && ! conn->ipv4_traffic_ok) || (family == AF_INET6 && ! conn->ipv4_traffic_ok)) { @@ -1836,6 +1837,9 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) if (!ap_conn->ipv4_traffic_ok) flags |= BEGIN_FLAG_IPV4_NOT_OK; + if (ap_conn->is_socks_socket) + return 0; + exitnode = node_get_by_id(cpath_layer->extend_info->identity_digest); if (ap_conn->ipv6_traffic_ok && exitnode) { diff --git a/src/or/main.c b/src/or/main.c index c925b7d593..0c49b40314 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -385,6 +385,10 @@ connection_remove(connection_t *conn) (int)conn->s, conn_type_to_string(conn->type), smartlist_len(connection_array)); + if (conn->type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) { + log_notice(LD_NET, "Closing SOCKS SocksSocket connection"); + } + control_event_conn_bandwidth(conn); tor_assert(conn->conn_array_index >= 0); diff --git a/src/or/or.h b/src/or/or.h index 58e2164665..5bb5f5bf71 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1702,6 +1702,9 @@ typedef struct entry_connection_t { * do we prefer IPv6? */ unsigned int prefer_ipv6_virtaddr : 1; + /** Are we a socks SocksSocket listener? */ + unsigned int is_socks_socket:1; + } entry_connection_t; typedef enum { @@ -3528,6 +3531,10 @@ typedef struct { * for control connections. */ int ControlSocketsGroupWritable; /**< Boolean: Are control sockets g+rw? */ + config_line_t *SocksSocket; /**< List of Unix Domain Sockets to listen on + * for SOCKS connections. */ + + int SocksSocketsGroupWritable; /**< Boolean: Are SOCKS sockets g+rw? */ /** Ports to listen on for directory connections. */ config_line_t *DirPort_lines; config_line_t *DNSPort_lines; /**< Ports to listen on for DNS requests. */ @@ -3550,6 +3557,7 @@ typedef struct { */ unsigned int ORPort_set : 1; unsigned int SocksPort_set : 1; + unsigned int SocksSocket_set : 1; unsigned int TransPort_set : 1; unsigned int NATDPort_set : 1; unsigned int ControlPort_set : 1; diff --git a/src/or/relay.c b/src/or/relay.c index 2d11096309..b7ab8081e9 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1327,8 +1327,9 @@ connection_edge_process_relay_cell_not_open( return 0; } - if ((family == AF_INET && ! entry_conn->ipv4_traffic_ok) || - (family == AF_INET6 && ! entry_conn->ipv6_traffic_ok)) { + if (((family == AF_INET && ! entry_conn->ipv4_traffic_ok) || + (family == AF_INET6 && ! entry_conn->ipv6_traffic_ok)) && + (!entry_conn->is_socks_socket)) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a connected cell to %s with unsupported address family." " Closing.", fmt_addr(&addr)); |