Merge remote-tracking branch 'andrea/ticket12585_v3'

This commit is contained in:
Nick Mathewson 2015-01-13 12:50:55 -05:00
commit d8b7dcca8d
8 changed files with 268 additions and 42 deletions

9
changes/bug12585 Normal file
View File

@ -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.

View File

@ -294,7 +294,7 @@ GENERAL OPTIONS
[[ControlSocket]] **ControlSocket** __Path__::
Like ControlPort, but listens on a Unix domain socket, rather than a TCP
socket. (Unix and Unix-like systems only.)
socket. '0' disables ControlSocket (Unix and Unix-like systems only.)
[[ControlSocketsGroupWritable]] **ControlSocketsGroupWritable** **0**|**1**::
If this option is set to 0, don't allow the filesystem group to read and
@ -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. '0' disables SocksSocket (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

View File

@ -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:

View File

@ -200,6 +200,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),
@ -1047,6 +1049,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. */
@ -6034,22 +6050,87 @@ parse_port_config(smartlist_t *out,
/** Parse a list of config_line_t for an AF_UNIX unix socket listener option
* from <b>cfg</b> and add them to <b>out</b>. No fancy options are
* supported: the line contains nothing but the path to the AF_UNIX socket. */
* supported: the line contains nothing but the path to the AF_UNIX socket.
* We support a *Socket 0 syntax to explicitly disable if we enable by
* default. To use this, pass a non-NULL list containing the default
* paths into this function as the 2nd parameter, and if no config lines at all
* are present they will be added to the output list. If the only config line
* present is '0' the input list will be unmodified.
*/
static int
parse_unix_socket_config(smartlist_t *out, const config_line_t *cfg,
int listener_type)
parse_unix_socket_config(smartlist_t *out, smartlist_t *defaults,
const config_line_t *cfg, int listener_type)
{
/* We can say things like SocksSocket 0 or ControlSocket 0 to explicitly
* disable this feature; use this to track if we've seen a disable line
*/
int unix_socket_disable = 0;
size_t len;
smartlist_t *ports_to_add = NULL;
if (!out)
return 0;
ports_to_add = smartlist_new();
for ( ; cfg; cfg = cfg->next) {
size_t len = strlen(cfg->value);
if (strcmp(cfg->value, "0") != 0) {
/* We have a non-disable; add it */
len = strlen(cfg->value);
port_cfg_t *port = tor_malloc_zero(sizeof(port_cfg_t) + len + 1);
port->is_unix_addr = 1;
memcpy(port->unix_addr, cfg->value, len+1);
port->type = listener_type;
if (listener_type == CONN_TYPE_AP_LISTENER) {
/* Some more bits to twiddle for this case
*
* XXX this should support parsing the same options
* parse_port_config() does, and probably that code should be
* factored out into a function we can call from here. For
* now, some reasonable defaults.
*/
port->ipv4_traffic = 1;
port->ipv6_traffic = 1;
port->cache_ipv4_answers = 1;
port->cache_ipv6_answers = 1;
}
smartlist_add(ports_to_add, port);
} else {
/* Keep track that we've seen a disable */
unix_socket_disable = 1;
}
}
if (unix_socket_disable) {
if (smartlist_len(ports_to_add) > 0) {
/* We saw a disable line and a path; bad news */
SMARTLIST_FOREACH(ports_to_add, port_cfg_t *, port, tor_free(port));
smartlist_free(ports_to_add);
return -1;
}
/* else we have a disable and nothing else, so add nothing to out */
} else {
/* No disable; do we have any ports to add that we parsed? */
if (smartlist_len(ports_to_add) > 0) {
SMARTLIST_FOREACH_BEGIN(ports_to_add, port_cfg_t *, port) {
smartlist_add(out, port);
} SMARTLIST_FOREACH_END(port);
} else if (defaults != NULL && smartlist_len(defaults) > 0) {
/* No, but we have some defaults to copy */
SMARTLIST_FOREACH_BEGIN(defaults, const port_cfg_t *, defport) {
tor_assert(defport->is_unix_addr);
tor_assert(defport->unix_addr);
len = sizeof(port_cfg_t) + strlen(defport->unix_addr) + 1;
port_cfg_t *port = tor_malloc_zero(len);
memcpy(port, defport, len);
smartlist_add(out, port);
} SMARTLIST_FOREACH_END(defport);
}
/* Free the temporary smartlist we used */
smartlist_free(ports_to_add);
}
return 0;
@ -6143,12 +6224,19 @@ parse_ports(or_options_t *options, int validate_only,
"configuration");
goto err;
}
if (parse_unix_socket_config(ports,
if (parse_unix_socket_config(ports, NULL,
options->ControlSocket,
CONN_TYPE_CONTROL_LISTENER) < 0) {
*msg = tor_strdup("Invalid ControlSocket configuration");
goto err;
}
if (parse_unix_socket_config(ports, NULL,
options->SocksSocket,
CONN_TYPE_AP_LISTENER) < 0) {
*msg = tor_strdup("Invalid SocksSocket configuration");
goto err;
}
}
if (! options->ClientOnly) {
if (parse_port_config(ports,
@ -6192,6 +6280,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 =

View File

@ -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,
@ -915,13 +918,57 @@ warn_too_many_conns(void)
}
#ifdef HAVE_SYS_UN_H
#define UNIX_SOCKET_PURPOSE_CONTROL_SOCKET 0
#define UNIX_SOCKET_PURPOSE_SOCKS_SOCKET 1
/** Check if the purpose isn't one of the ones we know what to do with */
static int
is_valid_unix_socket_purpose(int purpose)
{
int valid = 0;
switch (purpose) {
case UNIX_SOCKET_PURPOSE_CONTROL_SOCKET:
case UNIX_SOCKET_PURPOSE_SOCKS_SOCKET:
valid = 1;
break;
}
return valid;
}
/** Return a string description of a unix socket purpose */
static const char *
unix_socket_purpose_to_string(int purpose)
{
const char *s = "unknown-purpose socket";
switch (purpose) {
case UNIX_SOCKET_PURPOSE_CONTROL_SOCKET:
s = "control socket";
break;
case UNIX_SOCKET_PURPOSE_SOCKS_SOCKET:
s = "SOCKS socket";
break;
}
return s;
}
/** 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_unix_socket(const or_options_t *options, const char *path)
check_location_for_unix_socket(const or_options_t *options, const char *path,
int purpose)
{
int r = -1;
char *p = tor_strdup(path);
char *p = NULL;
tor_assert(is_valid_unix_socket_purpose(purpose));
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 "
@ -929,18 +976,23 @@ check_location_for_unix_socket(const or_options_t *options, const char *path)
goto done;
}
if (options->ControlSocketsGroupWritable)
if ((purpose == UNIX_SOCKET_PURPOSE_CONTROL_SOCKET &&
options->ControlSocketsGroupWritable) ||
(purpose == UNIX_SOCKET_PURPOSE_SOCKS_SOCKET &&
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 control socket 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,
log_warn(LD_GENERAL, "Before Tor can create a %s 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.)",
unix_socket_purpose_to_string(purpose), escpath, escdir,
options->ControlSocketsGroupWritable ? " and group" : "");
tor_free(escpath);
tor_free(escdir);
@ -1030,8 +1082,8 @@ connection_listener_new(const struct sockaddr *listensockaddr,
if (listensockaddr->sa_family == AF_INET ||
listensockaddr->sa_family == AF_INET6) {
int is_tcp = (type != CONN_TYPE_AP_DNS_LISTENER);
if (is_tcp)
int is_stream = (type != CONN_TYPE_AP_DNS_LISTENER);
if (is_stream)
start_reading = 1;
tor_addr_from_sockaddr(&addr, listensockaddr, &usePort);
@ -1040,10 +1092,10 @@ connection_listener_new(const struct sockaddr *listensockaddr,
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);
is_stream ? SOCK_STREAM : SOCK_DGRAM,
is_stream ? IPPROTO_TCP: IPPROTO_UDP);
if (!SOCKET_OK(s)) {
log_warn(LD_NET,"Socket creation failed: %s",
log_warn(LD_NET, "Socket creation failed: %s",
tor_socket_strerror(tor_socket_errno(-1)));
goto err;
}
@ -1100,7 +1152,7 @@ connection_listener_new(const struct sockaddr *listensockaddr,
goto err;
}
if (is_tcp) {
if (is_stream) {
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)));
@ -1123,15 +1175,25 @@ connection_listener_new(const struct sockaddr *listensockaddr,
tor_addr_from_sockaddr(&addr2, (struct sockaddr*)&ss, &gotPort);
}
#ifdef HAVE_SYS_UN_H
/*
* AF_UNIX generic setup stuff (this covers both CONN_TYPE_CONTROL_LISTENER
* and CONN_TYPE_AP_LISTENER cases)
*/
} else if (listensockaddr->sa_family == AF_UNIX) {
/* We want to start reading for both AF_UNIX cases */
start_reading = 1;
/* For now only control ports can be Unix domain sockets
/* For now only control ports or SOCKS ports can be Unix domain sockets
* and listeners at the same time */
tor_assert(type == CONN_TYPE_CONTROL_LISTENER);
tor_assert(type == CONN_TYPE_CONTROL_LISTENER ||
type == CONN_TYPE_AP_LISTENER);
if (check_location_for_unix_socket(options, address) < 0)
if (check_location_for_unix_socket(options, address,
(type == CONN_TYPE_CONTROL_LISTENER) ?
UNIX_SOCKET_PURPOSE_CONTROL_SOCKET :
UNIX_SOCKET_PURPOSE_SOCKS_SOCKET) < 0) {
goto err;
}
log_notice(LD_NET, "Opening %s on %s",
conn_type_to_string(type), address);
@ -1143,17 +1205,20 @@ connection_listener_new(const struct sockaddr *listensockaddr,
strerror(errno));
goto err;
}
s = tor_open_socket_nonblocking(AF_UNIX, SOCK_STREAM, 0);
if (! SOCKET_OK(s)) {
log_warn(LD_NET,"Socket creation failed: %s.", strerror(errno));
goto err;
}
if (bind(s, listensockaddr, (socklen_t)sizeof(struct sockaddr_un)) == -1) {
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 = tor_getpwnam(options->User);
@ -1168,13 +1233,27 @@ connection_listener_new(const struct sockaddr *listensockaddr,
}
}
#endif
if (options->ControlSocketsGroupWritable) {
if ((type == CONN_TYPE_CONTROL_LISTENER &&
options->ControlSocketsGroupWritable) ||
(type == CONN_TYPE_AP_LISTENER &&
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;
}
} else if ((type == CONN_TYPE_CONTROL_LISTENER &&
!(options->ControlSocketsGroupWritable)) ||
(type == CONN_TYPE_AP_LISTENER &&
!(options->SocksSocketsGroupWritable))) {
/* We need to use chmod; fchmod doesn't work on sockets on all
* platforms. */
if (chmod(address, 0600) < 0) {
log_warn(LD_FS,"Unable to make %s group-writable.", address);
goto err;
}
}
if (listen(s, SOMAXCONN) < 0) {
@ -1182,8 +1261,6 @@ connection_listener_new(const struct sockaddr *listensockaddr,
tor_socket_strerror(tor_socket_errno(s)));
goto err;
}
#else
(void)options;
#endif /* HAVE_SYS_UN_H */
} else {
log_err(LD_BUG, "Got unexpected address family %d.",
@ -1294,6 +1371,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 +1457,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 +1499,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_info(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,9 +1517,7 @@ connection_handle_listener_read(connection_t *conn, int new_type)
fmt_and_decorate_addr(&addr));
}
} else if (conn->socket_family == AF_UNIX) {
/* For now only control ports can be Unix domain sockets
* and listeners at the same time */
} else if (conn->socket_family == AF_UNIX && conn->type != CONN_TYPE_AP) {
tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER);
tor_assert(new_type == CONN_TYPE_CONTROL);
log_notice(LD_CONTROL, "New control connection opened.");
@ -2392,6 +2479,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

View File

@ -391,6 +391,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_info(LD_NET, "Closing SOCKS SocksSocket connection");
}
control_event_conn_bandwidth(conn);
tor_assert(conn->conn_array_index >= 0);

View File

@ -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;

View File

@ -1327,8 +1327,8 @@ 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))) {
log_fn(LOG_PROTOCOL_WARN, LD_APP,
"Got a connected cell to %s with unsupported address family."
" Closing.", fmt_addr(&addr));