From e8cc839e41adc4975a61fee62abe7f6664fd0c0e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 6 Nov 2015 13:12:44 -0500 Subject: 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 --- src/common/compat.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/common/compat.h | 10 +++++- 2 files changed, 105 insertions(+), 2 deletions(-) (limited to 'src/common') diff --git a/src/common/compat.c b/src/common/compat.c index 7d72b4b7fd..655193499e 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -71,6 +71,9 @@ #ifdef HAVE_SYS_STATVFS_H #include #endif +#ifdef HAVE_SYS_CAPABILITY_H +#include +#endif #ifdef _WIN32 #include @@ -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 user 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 c7c468c754..b245d7d1bd 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 -- cgit v1.2.3-54-g00ecf From 405a8d3fb4884d5e5c5f32881a1a810b733a5aad Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 26 Nov 2015 11:03:35 -0500 Subject: Update KeepCapabilities based on comments from asn * The option is now KeepBindCapabilities * We now warn if the user specifically asked for KeepBindCapabilities and we can't deliver. * The unit tests are willing to start. * Fewer unused-variable warnings. * More documentation, fewer misspellings. --- changes/feature8195 | 2 +- doc/tor.1.txt | 4 ++-- src/common/compat.c | 13 ++++++++++++- src/common/compat.h | 5 ++++- src/or/config.c | 25 ++++++++++++++----------- src/or/or.h | 2 +- 6 files changed, 34 insertions(+), 17 deletions(-) (limited to 'src/common') diff --git a/changes/feature8195 b/changes/feature8195 index 0c366b595c..cb81f2e1d0 100644 --- a/changes/feature8195 +++ b/changes/feature8195 @@ -3,4 +3,4 @@ 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. + the new option KeepBindCapabilities. Closes ticket 8195. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index a7bf28bbd5..58313d8a49 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -601,9 +601,9 @@ GENERAL OPTIONS [[User]] **User** __UID__:: On startup, setuid to this user and setgid to their primary group. -[[KeepCapabilities]] **KeepCapabilities** **0**|**1**|**auto**:: +[[KeepBindCapabilities]] **KeepBindCapabilities** **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 + 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. diff --git a/src/common/compat.c b/src/common/compat.c index 655193499e..217bc00315 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -1997,8 +1997,11 @@ drop_capabilities(int pre_setuid) /** Call setuid and setgid to run as user 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 + * 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, const unsigned flags) @@ -2009,6 +2012,7 @@ switch_id(const char *user, const unsigned flags) 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); @@ -2033,10 +2037,17 @@ switch_id(const char *user, const unsigned flags) } #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 */ diff --git a/src/common/compat.h b/src/common/compat.h index b245d7d1bd..df95636200 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -632,7 +632,10 @@ int tor_disable_debugger_attach(void); int have_capability_support(void); -#define SWITCH_ID_KEEP_BINDLOW 1 +/** 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); diff --git a/src/or/config.c b/src/or/config.c index 5060b1b5be..0b95f95d25 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -308,7 +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"), + V(KeepBindCapabilities, AUTOBOOL, "auto"), VAR("Log", LINELIST, Logs, NULL), V(LogMessageDomains, BOOL, "0"), V(LogTimeGranularity, MSEC_INTERVAL, "1 second"), @@ -1183,11 +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) { + tor_assert(have_low_ports != -1); unsigned switch_id_flags = 0; - if (options->KeepCapabilities == 1 || - (options->KeepCapabilities == -1 && have_low_ports)) { + 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) { @@ -4008,8 +4011,8 @@ 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 " + if (old->KeepBindCapabilities != new_val->KeepBindCapabilities) { + *msg = tor_strdup("While Tor is running, changing KeepBindCapabilities is " "not allowed."); return -1; } @@ -6612,8 +6615,8 @@ parse_ports(or_options_t *options, int validate_only, } /** Given a list of port_cfg_t in ports, check them for internal - * consistency and warn as appropriate. Set *n_low_port to the number - * of sub-1024 ports we will be binding. */ + * consistency and warn as appropriate. Set *n_low_ports_out 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, @@ -6681,10 +6684,10 @@ check_server_ports(const smartlist_t *ports, } if (n_low_port && options->AccountingMax && - (!have_capability_support() || options->KeepCapabilities == 0)) { + (!have_capability_support() || options->KeepBindCapabilities == 0)) { const char *extra = ""; - if (options->KeepCapabilities == 0 && have_capability_support()) - extra = ", and you have disabled KeepCapabilities."; + 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%s." diff --git a/src/or/or.h b/src/or/or.h index b07130325f..54c5cb0985 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4319,7 +4319,7 @@ typedef struct { char *master_key_fname; /** Autobool: Do we try to retain capabilities if we can? */ - int KeepCapabilities; + int KeepBindCapabilities; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ -- cgit v1.2.3-54-g00ecf