[tor-commits] [tor/master] Add ability to keep the CAP_NET_BIND_SERVICE capability on Linux
nickm at torproject.org
nickm at torproject.org
Tue Dec 15 18:18:11 UTC 2015
commit e8cc839e41adc4975a61fee62abe7f6664fd0c0e
Author: Nick Mathewson <nickm at torproject.org>
Date: Fri Nov 6 13:12:44 2015 -0500
Add ability to keep the CAP_NET_BIND_SERVICE capability on Linux
This feature allows us to bind low ports when starting as root and
switching UIDs.
Based on code by David Goulet.
Implement feature 8195
---
changes/feature8195 | 6 ++++
configure.ac | 16 ++++++++-
doc/tor.1.txt | 8 +++++
src/common/compat.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++-
src/common/compat.h | 10 +++++-
src/or/config.c | 46 +++++++++++++++++++-----
src/or/or.h | 3 ++
7 files changed, 175 insertions(+), 11 deletions(-)
diff --git a/changes/feature8195 b/changes/feature8195
new file mode 100644
index 0000000..0c366b5
--- /dev/null
+++ b/changes/feature8195
@@ -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 KeepCapabilities. Closes ticket 8195.
diff --git a/configure.ac b/configure.ac
index 3bf2f47..eb7f2c2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -698,6 +698,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,
@@ -705,7 +718,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__)
@@ -898,6 +911,7 @@ AC_CHECK_HEADERS(
fcntl.h \
signal.h \
string.h \
+ sys/capability.h \
sys/fcntl.h \
sys/stat.h \
sys/time.h \
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index 916433b..a7bf28b 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -601,6 +601,14 @@ GENERAL OPTIONS
[[User]] **User** __UID__::
On startup, setuid to this user and setgid to their primary group.
+[[KeepCapabilities]] **KeepCapabilities** **0**|**1**|**auto**::
+ On Linux, when we are started as root and we switch our identity using
+ the **User** option, the **KeepCapabilities** 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)
diff --git a/src/common/compat.c b/src/common/compat.c
index 7d72b4b..6551934 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -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>
@@ -1917,17 +1920,95 @@ 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 capabilitity
+ * system to retain the abilitity to bind low ports.
*/
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);
tor_assert(user);
@@ -1951,6 +2032,13 @@ switch_id(const char *user)
return -1;
}
+#ifdef HAVE_LINUX_CAPABILITIES
+ if (keep_bindlow) {
+ if (drop_capabilities(1))
+ return -1;
+ }
+#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\".",
@@ -2004,6 +2092,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
@@ -2051,6 +2145,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.");
diff --git a/src/common/compat.h b/src/common/compat.h
index c7c468c..b245d7d 100644
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@ -625,7 +625,15 @@ 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);
+
+#define SWITCH_ID_KEEP_BINDLOW 1
+int switch_id(const char *user, unsigned flags);
#ifdef HAVE_PWD_H
char *get_user_homedir(const char *username);
#endif
diff --git a/src/or/config.c b/src/or/config.c
index 22039b4..5060b1b 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -308,6 +308,7 @@ static config_var_t option_vars_[] = {
V(Socks5ProxyUsername, STRING, NULL),
V(Socks5ProxyPassword, STRING, NULL),
V(KeepalivePeriod, INTERVAL, "5 minutes"),
+ V(KeepCapabilities, AUTOBOOL, "auto"),
VAR("Log", LINELIST, Logs, NULL),
V(LogMessageDomains, BOOL, "0"),
V(LogTimeGranularity, MSEC_INTERVAL, "1 second"),
@@ -567,7 +568,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,
@@ -1045,6 +1047,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.
@@ -1178,8 +1183,14 @@ options_act_reversible(const or_options_t *old_options, char **msg)
}
/* Setuid/setgid as appropriate */
+ tor_assert(have_low_ports != -1);
if (options->User) {
- if (switch_id(options->User) != 0) {
+ unsigned switch_id_flags = 0;
+ if (options->KeepCapabilities == 1 ||
+ (options->KeepCapabilities == -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;
@@ -3997,6 +4008,12 @@ options_transition_allowed(const or_options_t *old,
return -1;
}
+ if (old->KeepCapabilities != new_val->KeepCapabilities) {
+ *msg = tor_strdup("While Tor is running, changing KeepCapabilities 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.");
@@ -6535,10 +6552,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);
@@ -6592,10 +6612,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_port</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;
@@ -6658,16 +6680,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->KeepCapabilities == 0)) {
+ const char *extra = "";
+ if (options->KeepCapabilities == 0 && have_capability_support())
+ extra = ", and you have disabled KeepCapabilities.";
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;
}
diff --git a/src/or/or.h b/src/or/or.h
index 651d8be..b071303 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -4317,6 +4317,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 KeepCapabilities;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
More information about the tor-commits
mailing list