Merge branch 'feature8195_small_squashed'

This commit is contained in:
Nick Mathewson 2015-12-15 13:11:06 -05:00
commit aba39ea390
11 changed files with 414 additions and 14 deletions

2
.gitignore vendored
View File

@ -172,6 +172,7 @@ cscope.*
/src/test/test-child
/src/test/test-memwipe
/src/test/test-ntor-cl
/src/test/test-switch-id
/src/test/test_workqueue
/src/test/test.exe
/src/test/test-slow.exe
@ -179,6 +180,7 @@ cscope.*
/src/test/test-child.exe
/src/test/test-ntor-cl.exe
/src/test/test-memwipe.exe
/src/test/test-switch-id.exe
/src/test/test_workqueue.exe
/src/test/test_zero_length_keys.sh
/src/test/test_ntor.sh

6
changes/feature8195 Normal file
View File

@ -0,0 +1,6 @@
o Major features:
- When Tor is started as root on Linux and told to switch user ID, it
can now retain the capabilitity to bind to low ports. By default,
Tor will do this only when it's switching user ID and some low
ports have been configured. You can change this behavior with
the new option KeepBindCapabilities. Closes ticket 8195.

View File

@ -712,6 +712,19 @@ else
fi
AC_SUBST(TOR_ZLIB_LIBS)
dnl ----------------------------------------------------------------------
dnl Check if libcap is available for capabilities.
tor_cap_pkg_debian="libcap2"
tor_cap_pkg_redhat="libcap"
tor_cap_devpkg_debian="libcap-dev"
tor_cap_devpkg_redhat="libcap-devel"
AC_CHECK_LIB([cap], [cap_init], [],
AC_MSG_NOTICE([Libcap was not found. Capabilities will not be usable.])
)
AC_CHECK_FUNCS(cap_set_proc)
dnl ---------------------------------------------------------------------
dnl Now that we know about our major libraries, we can check for compiler
dnl and linker hardening options. We need to do this with the libraries known,
@ -719,7 +732,7 @@ dnl since sometimes the linker will like an option but not be willing to
dnl use it with a build of a library.
all_ldflags_for_check="$TOR_LDFLAGS_zlib $TOR_LDFLAGS_openssl $TOR_LDFLAGS_libevent"
all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI"
all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI $TOR_CAP_LIBS"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
#if !defined(__clang__)
@ -912,6 +925,7 @@ AC_CHECK_HEADERS(
fcntl.h \
signal.h \
string.h \
sys/capability.h \
sys/fcntl.h \
sys/stat.h \
sys/time.h \

View File

@ -619,6 +619,14 @@ GENERAL OPTIONS
[[User]] **User** __UID__::
On startup, setuid to this user and setgid to their primary group.
[[KeepBindCapabilities]] **KeepBindCapabilities** **0**|**1**|**auto**::
On Linux, when we are started as root and we switch our identity using
the **User** option, the **KeepBindCapabilities** option tells us whether to
try to retain our ability to bind to low ports. If this value is 1, we
try to keep the capability; if it is 0 we do not; and if it is **auto**,
we keep the capability only if we are configured to listen on a low port.
(Default: auto.)
[[HardwareAccel]] **HardwareAccel** **0**|**1**::
If non-zero, try to use built-in (static) crypto hardware acceleration when
available. (Default: 0)

View File

@ -71,6 +71,9 @@
#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif
#ifdef _WIN32
#include <conio.h>
@ -1966,17 +1969,99 @@ tor_getpwuid(uid_t uid)
}
#endif
/** Return true iff we were compiled with capability support, and capabilities
* seem to work. **/
int
have_capability_support(void)
{
#ifdef HAVE_LINUX_CAPABILITIES
cap_t caps = cap_get_proc();
if (caps == NULL)
return 0;
cap_free(caps);
return 1;
#else
return 0;
#endif
}
#ifdef HAVE_LINUX_CAPABILITIES
/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
* appropriate.
*
* If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
* CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
* setuid().
*
* If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
* PR_KEEPCAPS.
*
* Return 0 on success, and -1 on failure.
*/
static int
drop_capabilities(int pre_setuid)
{
/* We keep these three capabilities, and these only, as we setuid.
* After we setuid, we drop all but the first. */
const cap_value_t caplist[] = {
CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
};
const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
const int n_effective = pre_setuid ? 3 : 1;
const int n_permitted = pre_setuid ? 3 : 1;
const int n_inheritable = 1;
const int keepcaps = pre_setuid ? 1 : 0;
/* Sets whether we keep capabilities across a setuid. */
if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
where, strerror(errno));
return -1;
}
cap_t caps = cap_get_proc();
if (!caps) {
log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
where, strerror(errno));
return -1;
}
cap_clear(caps);
cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
int r = cap_set_proc(caps);
cap_free(caps);
if (r < 0) {
log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
where, strerror(errno));
return -1;
}
return 0;
}
#endif
/** Call setuid and setgid to run as <b>user</b> and switch to their
* primary group. Return 0 on success. On failure, log and return -1.
*
* If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
* system to retain the abilitity to bind low ports.
*
* If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
* don't have capability support.
*/
int
switch_id(const char *user)
switch_id(const char *user, const unsigned flags)
{
#ifndef _WIN32
const struct passwd *pw = NULL;
uid_t old_uid;
gid_t old_gid;
static int have_already_switched_id = 0;
const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
tor_assert(user);
@ -2000,6 +2085,20 @@ switch_id(const char *user)
return -1;
}
#ifdef HAVE_LINUX_CAPABILITIES
(void) warn_if_no_caps;
if (keep_bindlow) {
if (drop_capabilities(1))
return -1;
}
#else
(void) keep_bindlow;
if (warn_if_no_caps) {
log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
"on this system.");
}
#endif
/* Properly switch egid,gid,euid,uid here or bail out */
if (setgroups(1, &pw->pw_gid)) {
log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
@ -2053,6 +2152,12 @@ switch_id(const char *user)
/* We've properly switched egid, gid, euid, uid, and supplementary groups if
* we're here. */
#ifdef HAVE_LINUX_CAPABILITIES
if (keep_bindlow) {
if (drop_capabilities(0))
return -1;
}
#endif
#if !defined(CYGWIN) && !defined(__CYGWIN__)
/* If we tried to drop privilege to a group/user other than root, attempt to
@ -2100,6 +2205,7 @@ switch_id(const char *user)
#else
(void)user;
(void)flags;
log_warn(LD_CONFIG,
"User specified but switching users is unsupported on your OS.");

View File

@ -625,7 +625,18 @@ typedef unsigned long rlim_t;
int get_max_sockets(void);
int set_max_file_descriptors(rlim_t limit, int *max);
int tor_disable_debugger_attach(void);
int switch_id(const char *user);
#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
#define HAVE_LINUX_CAPABILITIES
#endif
int have_capability_support(void);
/** Flag for switch_id; see switch_id() for documentation */
#define SWITCH_ID_KEEP_BINDLOW (1<<0)
/** Flag for switch_id; see switch_id() for documentation */
#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
int switch_id(const char *user, unsigned flags);
#ifdef HAVE_PWD_H
char *get_user_homedir(const char *username);
#endif

View File

@ -310,6 +310,7 @@ static config_var_t option_vars_[] = {
V(Socks5ProxyUsername, STRING, NULL),
V(Socks5ProxyPassword, STRING, NULL),
V(KeepalivePeriod, INTERVAL, "5 minutes"),
V(KeepBindCapabilities, AUTOBOOL, "auto"),
VAR("Log", LINELIST, Logs, NULL),
V(LogMessageDomains, BOOL, "0"),
V(LogTimeGranularity, MSEC_INTERVAL, "1 second"),
@ -606,7 +607,8 @@ static int parse_ports(or_options_t *options, int validate_only,
char **msg_out, int *n_ports_out,
int *world_writable_control_socket);
static int check_server_ports(const smartlist_t *ports,
const or_options_t *options);
const or_options_t *options,
int *num_low_ports_out);
static int validate_data_directory(or_options_t *options);
static int write_configuration_file(const char *fname,
@ -1085,6 +1087,9 @@ consider_adding_dir_servers(const or_options_t *options,
return 0;
}
/* Helps determine flags to pass to switch_id. */
static int have_low_ports = -1;
/** Fetch the active option list, and take actions based on it. All of the
* things we do should survive being done repeatedly. If present,
* <b>old_options</b> contains the previous value of the options.
@ -1219,7 +1224,16 @@ options_act_reversible(const or_options_t *old_options, char **msg)
/* Setuid/setgid as appropriate */
if (options->User) {
if (switch_id(options->User) != 0) {
tor_assert(have_low_ports != -1);
unsigned switch_id_flags = 0;
if (options->KeepBindCapabilities == 1) {
switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
switch_id_flags |= SWITCH_ID_WARN_IF_NO_CAPS;
}
if (options->KeepBindCapabilities == -1 && have_low_ports) {
switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
}
if (switch_id(options->User, switch_id_flags) != 0) {
/* No need to roll back, since you can't change the value. */
*msg = tor_strdup("Problem with User value. See logs for details.");
goto done;
@ -4094,6 +4108,12 @@ options_transition_allowed(const or_options_t *old,
return -1;
}
if (old->KeepBindCapabilities != new_val->KeepBindCapabilities) {
*msg = tor_strdup("While Tor is running, changing KeepBindCapabilities is "
"not allowed.");
return -1;
}
if (!opt_streq(old->SyslogIdentityTag, new_val->SyslogIdentityTag)) {
*msg = tor_strdup("While Tor is running, changing "
"SyslogIdentityTag is not allowed.");
@ -6632,10 +6652,13 @@ parse_ports(or_options_t *options, int validate_only,
}
}
if (check_server_ports(ports, options) < 0) {
int n_low_ports = 0;
if (check_server_ports(ports, options, &n_low_ports) < 0) {
*msg = tor_strdup("Misconfigured server ports");
goto err;
}
if (have_low_ports < 0)
have_low_ports = (n_low_ports > 0);
*n_ports_out = smartlist_len(ports);
@ -6689,10 +6712,12 @@ parse_ports(or_options_t *options, int validate_only,
}
/** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal
* consistency and warn as appropriate. */
* consistency and warn as appropriate. Set *<b>n_low_ports_out</b> to the
* number of sub-1024 ports we will be binding. */
static int
check_server_ports(const smartlist_t *ports,
const or_options_t *options)
const or_options_t *options,
int *n_low_ports_out)
{
int n_orport_advertised = 0;
int n_orport_advertised_ipv4 = 0;
@ -6755,16 +6780,24 @@ check_server_ports(const smartlist_t *ports,
r = -1;
}
if (n_low_port && options->AccountingMax) {
if (n_low_port && options->AccountingMax &&
(!have_capability_support() || options->KeepBindCapabilities == 0)) {
const char *extra = "";
if (options->KeepBindCapabilities == 0 && have_capability_support())
extra = ", and you have disabled KeepBindCapabilities.";
log_warn(LD_CONFIG,
"You have set AccountingMax to use hibernation. You have also "
"chosen a low DirPort or OrPort. This combination can make Tor stop "
"chosen a low DirPort or OrPort%s."
"This combination can make Tor stop "
"working when it tries to re-attach the port after a period of "
"hibernation. Please choose a different port or turn off "
"hibernation unless you know this combination will work on your "
"platform.");
"platform.", extra);
}
if (n_low_ports_out)
*n_low_ports_out = n_low_port;
return r;
}

View File

@ -4432,6 +4432,9 @@ typedef struct {
int keygen_passphrase_fd;
int change_key_passphrase;
char *master_key_fname;
/** Autobool: Do we try to retain capabilities if we can? */
int KeepBindCapabilities;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */

View File

@ -8,14 +8,16 @@ TESTS_ENVIRONMENT = \
export builddir="$(builddir)"; \
export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)";
TESTSCRIPTS = src/test/test_zero_length_keys.sh
TESTSCRIPTS = src/test/test_zero_length_keys.sh \
src/test/test_switch_id.sh
if USEPYTHON
TESTSCRIPTS += src/test/test_ntor.sh src/test/test_bt.sh
endif
TESTS += src/test/test src/test/test-slow src/test/test-memwipe \
src/test/test_workqueue src/test/test_keygen.sh $(TESTSCRIPTS)
src/test/test_workqueue src/test/test_keygen.sh \
$(TESTSCRIPTS)
# These flavors are run using automake's test-driver and test-network.sh
TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-min bridges+hs
@ -37,7 +39,8 @@ noinst_PROGRAMS+= \
src/test/test-slow \
src/test/test-memwipe \
src/test/test-child \
src/test/test_workqueue
src/test/test_workqueue \
src/test/test-switch-id
endif
src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \
@ -136,6 +139,14 @@ src_test_test_workqueue_SOURCES = \
src_test_test_workqueue_CPPFLAGS= $(src_test_AM_CPPFLAGS)
src_test_test_workqueue_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
src_test_test_switch_id_SOURCES = \
src/test/test_switch_id.c
src_test_test_switch_id_CPPFLAGS= $(src_test_AM_CPPFLAGS)
src_test_test_switch_id_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
src_test_test_switch_id_LDADD = \
src/common/libor-testing.a \
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@
src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
@TOR_LDFLAGS_libevent@
src_test_test_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \

181
src/test/test_switch_id.c Normal file
View File

@ -0,0 +1,181 @@
/* Copyright (c) 2015, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "or.h"
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif
#define TEST_BUILT_WITH_CAPS 0
#define TEST_HAVE_CAPS 1
#define TEST_ROOT_CAN_BIND_LOW 2
#define TEST_SETUID 3
#define TEST_SETUID_KEEPCAPS 4
#define TEST_SETUID_STRICT 5
static const char *username;
static const struct {
const char *name;
int test_id;
} which_test[] = {
{ "built-with-caps", TEST_BUILT_WITH_CAPS },
{ "have-caps", TEST_HAVE_CAPS },
{ "root-bind-low", TEST_ROOT_CAN_BIND_LOW },
{ "setuid", TEST_SETUID },
{ "setuid-keepcaps", TEST_SETUID_KEEPCAPS },
{ "setuid-strict", TEST_SETUID_STRICT },
{ NULL, 0 }
};
/* 0 on no, 1 on yes, -1 on failure. */
static int
check_can_bind_low_ports(void)
{
int port;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
for (port = 600; port < 1024; ++port) {
sin.sin_port = htons(port);
tor_socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (! SOCKET_OK(fd)) {
perror("socket");
return -1;
}
int one = 1;
if (setsockopt(fd, SOL_SOCKET,SO_REUSEADDR, &one, sizeof(one))) {
perror("setsockopt");
tor_close_socket_simple(fd);
return -1;
}
int res = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
tor_close_socket_simple(fd);
if (res == 0) {
/* bind was successful */
return 1;
} else if (errno == EACCES || errno == EPERM) {
/* Got a permission-denied error. */
return 0;
} else if (errno == EADDRINUSE) {
/* Huh; somebody is using that port. */
} else {
perror("bind");
}
}
return -1;
}
int
main(int argc, char **argv)
{
const char *testname;
if (argc != 3) {
fprintf(stderr, "I want 2 arguments: a username and a command.\n");
return 1;
}
if (getuid() != 0) {
fprintf(stderr, "This test only works when it's run as root.\n");
return 1;
}
username = argv[1];
testname = argv[2];
int test_id = -1;
int i;
for (i = 0; which_test[i].name; ++i) {
if (!strcmp(which_test[i].name, testname)) {
test_id = which_test[i].test_id;
break;
}
}
if (test_id == -1) {
fprintf(stderr, "Unrecognized test '%s'\n", testname);
return 1;
}
#ifdef HAVE_LINUX_CAPABILITIES
const int have_cap_support = 1;
#else
const int have_cap_support = 0;
#endif
int okay;
init_logging(1);
log_severity_list_t sev;
memset(&sev, 0, sizeof(sev));
set_log_severity_config(LOG_WARN, LOG_ERR, &sev);
add_stream_log(&sev, "", fileno(stderr));
switch (test_id)
{
case TEST_BUILT_WITH_CAPS:
/* Succeed if we were built with capability support. */
okay = have_cap_support;
break;
case TEST_HAVE_CAPS:
/* Succeed if "capabilities work" == "we were built with capability
* support." */
okay = have_cap_support == have_capability_support();
break;
case TEST_ROOT_CAN_BIND_LOW:
/* Succeed if root can bind low ports. */
okay = check_can_bind_low_ports() == 1;
break;
case TEST_SETUID:
/* Succeed if we can do a setuid with no capability retention, and doing
* so makes us lose the ability to bind low ports */
case TEST_SETUID_KEEPCAPS:
/* Succeed if we can do a setuid with capability retention, and doing so
* does not make us lose the ability to bind low ports */
{
int keepcaps = (test_id == TEST_SETUID_KEEPCAPS);
okay = switch_id(username, keepcaps ? SWITCH_ID_KEEP_BINDLOW : 0) == 0;
if (okay) {
okay = check_can_bind_low_ports() == keepcaps;
}
break;
}
case TEST_SETUID_STRICT:
/* Succeed if, after a setuid, we cannot setuid back, and we cannot
* re-grab any capabilities. */
okay = switch_id(username, SWITCH_ID_KEEP_BINDLOW) == 0;
if (okay) {
/* We'd better not be able to setuid back! */
if (setuid(0) == 0 || errno != EPERM) {
okay = 0;
}
}
#ifdef HAVE_LINUX_CAPABILITIES
if (okay) {
cap_t caps = cap_get_proc();
const cap_value_t caplist[] = {
CAP_SETUID,
};
cap_set_flag(caps, CAP_PERMITTED, 1, caplist, CAP_SET);
if (cap_set_proc(caps) == 0 || errno != EPERM) {
okay = 0;
}
cap_free(caps);
}
#endif
break;
default:
fprintf(stderr, "Unsupported test '%s'\n", testname);
okay = 0;
break;
}
if (!okay) {
fprintf(stderr, "Test %s failed!\n", testname);
}
return (okay ? 0 : 1);
}

25
src/test/test_switch_id.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
if test "`id -u`" != '0'; then
echo "This test only works when run as root. Skipping." >&2
exit 77
fi
if test "`id -u nobody`" = ""; then
echo "This test requires that your system have a 'nobody' user. Sorry." >&2
exit 1
fi
"${builddir:-.}/src/test/test-switch-id" nobody setuid || exit 1
"${builddir:-.}/src/test/test-switch-id" nobody root-bind-low || exit 1
"${builddir:-.}/src/test/test-switch-id" nobody setuid-strict || exit 1
"${builddir:-.}/src/test/test-switch-id" nobody built-with-caps || exit 0
# ... Go beyond this point only if we were built with capability support.
"${builddir:-.}/src/test/test-switch-id" nobody have-caps || exit 1
"${builddir:-.}/src/test/test-switch-id" nobody setuid-keepcaps || exit 1
echo "All okay"
exit 0