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

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
parent af80d472
Loading
Loading
Loading
Loading

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 KeepCapabilities.  Closes ticket 8195.
+15 −1
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
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>
@@ -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
Original line number Diff line number Diff line
@@ -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
Loading