Parcourir la source

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
Nick Mathewson il y a 8 ans
Parent
commit
e8cc839e41
7 fichiers modifiés avec 175 ajouts et 11 suppressions
  1. 6 0
      changes/feature8195
  2. 15 1
      configure.ac
  3. 8 0
      doc/tor.1.txt
  4. 96 1
      src/common/compat.c
  5. 9 1
      src/common/compat.h
  6. 38 8
      src/or/config.c
  7. 3 0
      src/or/or.h

+ 6 - 0
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.

+ 15 - 1
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 \

+ 8 - 0
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)

+ 96 - 1
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.");

+ 9 - 1
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

+ 38 - 8
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;
 }
 

+ 3 - 0
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. */