Commit aba39ea3 authored by Nick Mathewson's avatar Nick Mathewson 🤹
Browse files

Merge branch 'feature8195_small_squashed'

parents 744958e0 405a8d3f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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

changes/feature8195

0 → 100644
+6 −0
Original line number Diff line number Diff line
  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.
+15 −1
Original line number Diff line number Diff line
@@ -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 \
+8 −0
Original line number Diff line number Diff line
@@ -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)
+107 −1
Original line number Diff line number Diff line
@@ -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.");
Loading