tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

setuid.c (11431B)


      1 /* Copyright (c) 2003, Roger Dingledine
      2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      3 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      4 /* See LICENSE for licensing information */
      5 
      6 /**
      7 * \file setuid.c
      8 * \brief Change the user ID after Tor has started (Unix only)
      9 **/
     10 
     11 #include "orconfig.h"
     12 #include "lib/process/setuid.h"
     13 
     14 #if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
     15 #define HAVE_LINUX_CAPABILITIES
     16 #endif
     17 
     18 #include "lib/container/smartlist.h"
     19 #include "lib/fs/userdb.h"
     20 #include "lib/log/log.h"
     21 #include "lib/log/util_bug.h"
     22 #include "lib/malloc/malloc.h"
     23 
     24 #ifdef HAVE_SYS_TYPES_H
     25 #include <sys/types.h>
     26 #endif
     27 #ifdef HAVE_UNISTD_H
     28 #include <unistd.h>
     29 #endif
     30 #ifdef HAVE_GRP_H
     31 #include <grp.h>
     32 #endif
     33 #ifdef HAVE_PWD_H
     34 #include <pwd.h>
     35 #endif
     36 #ifdef HAVE_SYS_CAPABILITY_H
     37 #include <sys/capability.h>
     38 #endif
     39 #ifdef HAVE_SYS_PRCTL_H
     40 #include <sys/prctl.h>
     41 #endif
     42 
     43 #include <errno.h>
     44 #include <string.h>
     45 
     46 #ifndef _WIN32
     47 /** Log details of current user and group credentials. Return 0 on
     48 * success. Logs and return -1 on failure.
     49 */
     50 static int
     51 log_credential_status(void)
     52 {
     53 /** Log level to use when describing non-error UID/GID status. */
     54 #define CREDENTIAL_LOG_LEVEL LOG_INFO
     55  /* Real, effective and saved UIDs */
     56  uid_t ruid, euid, suid;
     57  /* Read, effective and saved GIDs */
     58  gid_t rgid, egid, sgid;
     59  /* Supplementary groups */
     60  gid_t *sup_gids = NULL;
     61  int sup_gids_size;
     62  /* Number of supplementary groups */
     63  int ngids;
     64 
     65  /* log UIDs */
     66 #ifdef HAVE_GETRESUID
     67  if (getresuid(&ruid, &euid, &suid) != 0) {
     68    log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
     69    return -1;
     70  } else {
     71    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
     72           "UID is %u (real), %u (effective), %u (saved)",
     73           (unsigned)ruid, (unsigned)euid, (unsigned)suid);
     74  }
     75 #else /* !defined(HAVE_GETRESUID) */
     76  /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
     77  ruid = getuid();
     78  euid = geteuid();
     79  (void)suid;
     80 
     81  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
     82         "UID is %u (real), %u (effective), unknown (saved)",
     83         (unsigned)ruid, (unsigned)euid);
     84 #endif /* defined(HAVE_GETRESUID) */
     85 
     86  /* log GIDs */
     87 #ifdef HAVE_GETRESGID
     88  if (getresgid(&rgid, &egid, &sgid) != 0) {
     89    log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
     90    return -1;
     91  } else {
     92    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
     93           "GID is %u (real), %u (effective), %u (saved)",
     94           (unsigned)rgid, (unsigned)egid, (unsigned)sgid);
     95  }
     96 #else /* !defined(HAVE_GETRESGID) */
     97  /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
     98  rgid = getgid();
     99  egid = getegid();
    100  (void)sgid;
    101  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
    102         "GID is %u (real), %u (effective), unknown (saved)",
    103         (unsigned)rgid, (unsigned)egid);
    104 #endif /* defined(HAVE_GETRESGID) */
    105 
    106  /* log supplementary groups */
    107  sup_gids_size = 64;
    108  sup_gids = tor_calloc(64, sizeof(gid_t));
    109  while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
    110         errno == EINVAL &&
    111         sup_gids_size < NGROUPS_MAX) {
    112    sup_gids_size *= 2;
    113    sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
    114  }
    115 
    116  if (ngids < 0) {
    117    log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
    118             strerror(errno));
    119    tor_free(sup_gids);
    120    return -1;
    121  } else {
    122    int i, retval = 0;
    123    char *s = NULL;
    124    smartlist_t *elts = smartlist_new();
    125 
    126    for (i = 0; i<ngids; i++) {
    127      smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
    128    }
    129 
    130    s = smartlist_join_strings(elts, " ", 0, NULL);
    131 
    132    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
    133 
    134    tor_free(s);
    135    SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
    136    smartlist_free(elts);
    137    tor_free(sup_gids);
    138 
    139    return retval;
    140  }
    141 
    142  return 0;
    143 }
    144 #endif /* !defined(_WIN32) */
    145 
    146 /** Return true iff we were compiled with capability support, and capabilities
    147 * seem to work. **/
    148 int
    149 have_capability_support(void)
    150 {
    151 #ifdef HAVE_LINUX_CAPABILITIES
    152  cap_t caps = cap_get_proc();
    153  if (caps == NULL)
    154    return 0;
    155  cap_free(caps);
    156  return 1;
    157 #else /* !defined(HAVE_LINUX_CAPABILITIES) */
    158  return 0;
    159 #endif /* defined(HAVE_LINUX_CAPABILITIES) */
    160 }
    161 
    162 #ifdef HAVE_LINUX_CAPABILITIES
    163 /** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
    164 * appropriate.
    165 *
    166 * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
    167 * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
    168 * setuid().
    169 *
    170 * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
    171 * PR_KEEPCAPS.
    172 *
    173 * Return 0 on success, and -1 on failure.
    174 */
    175 static int
    176 drop_capabilities(int pre_setuid)
    177 {
    178  /* We keep these three capabilities, and these only, as we setuid.
    179   * After we setuid, we drop all but the first. */
    180  const cap_value_t caplist[] = {
    181    CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
    182  };
    183  const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
    184  const int n_effective = pre_setuid ? 3 : 1;
    185  const int n_permitted = pre_setuid ? 3 : 1;
    186  const int n_inheritable = 1;
    187  const int keepcaps = pre_setuid ? 1 : 0;
    188 
    189  /* Sets whether we keep capabilities across a setuid. */
    190  if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
    191    log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
    192             where, strerror(errno));
    193    return -1;
    194  }
    195 
    196  cap_t caps = cap_get_proc();
    197  if (!caps) {
    198    log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
    199             where, strerror(errno));
    200    return -1;
    201  }
    202  cap_clear(caps);
    203 
    204  cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
    205  cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
    206  cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
    207 
    208  int r = cap_set_proc(caps);
    209  cap_free(caps);
    210  if (r < 0) {
    211    log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
    212             where, strerror(errno));
    213    return -1;
    214  }
    215 
    216  return 0;
    217 }
    218 #endif /* defined(HAVE_LINUX_CAPABILITIES) */
    219 
    220 /** Call setuid and setgid to run as <b>user</b> and switch to their
    221 * primary group.  Return 0 on success.  On failure, log and return -1.
    222 *
    223 * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
    224 * system to retain the abilitity to bind low ports.
    225 *
    226 * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
    227 * don't have capability support.
    228 */
    229 int
    230 switch_id(const char *user, const unsigned flags)
    231 {
    232 #ifndef _WIN32
    233  const struct passwd *pw = NULL;
    234  uid_t old_uid;
    235  gid_t old_gid;
    236  static int have_already_switched_id = 0;
    237  const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
    238  const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
    239 
    240  tor_assert(user);
    241 
    242  if (have_already_switched_id)
    243    return 0;
    244 
    245  /* Log the initial credential state */
    246  if (log_credential_status())
    247    return -1;
    248 
    249  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
    250 
    251  /* Get old UID/GID to check if we changed correctly */
    252  old_uid = getuid();
    253  old_gid = getgid();
    254 
    255  /* Lookup the user and group information, if we have a problem, bail out. */
    256  pw = tor_getpwnam(user);
    257  if (pw == NULL) {
    258    log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
    259    return -1;
    260  }
    261 
    262 #ifdef HAVE_LINUX_CAPABILITIES
    263  (void) warn_if_no_caps;
    264  if (keep_bindlow) {
    265    if (drop_capabilities(1))
    266      return -1;
    267  }
    268 #else /* !defined(HAVE_LINUX_CAPABILITIES) */
    269  (void) keep_bindlow;
    270  if (warn_if_no_caps) {
    271    log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
    272             "on this system.");
    273  }
    274 #endif /* defined(HAVE_LINUX_CAPABILITIES) */
    275 
    276  /* Properly switch egid,gid,euid,uid here or bail out */
    277  if (setgroups(1, &pw->pw_gid)) {
    278    log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
    279             (int)pw->pw_gid, strerror(errno));
    280    if (old_uid == pw->pw_uid) {
    281      log_warn(LD_GENERAL, "Tor is already running as %s.  You do not need "
    282               "the \"User\" option if you are already running as the user "
    283               "you want to be.  (If you did not set the User option in your "
    284               "torrc, check whether it was specified on the command line "
    285               "by a startup script.)", user);
    286    } else {
    287      log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
    288               " as root.");
    289    }
    290    return -1;
    291  }
    292 
    293  if (setegid(pw->pw_gid)) {
    294    log_warn(LD_GENERAL, "Error setting egid to %d: %s",
    295             (int)pw->pw_gid, strerror(errno));
    296    return -1;
    297  }
    298 
    299  if (setgid(pw->pw_gid)) {
    300    log_warn(LD_GENERAL, "Error setting gid to %d: %s",
    301             (int)pw->pw_gid, strerror(errno));
    302    return -1;
    303  }
    304 
    305  if (setuid(pw->pw_uid)) {
    306    log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
    307             user, (int)pw->pw_uid, strerror(errno));
    308    return -1;
    309  }
    310 
    311  if (seteuid(pw->pw_uid)) {
    312    log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
    313             user, (int)pw->pw_uid, strerror(errno));
    314    return -1;
    315  }
    316 
    317  /* This is how OpenBSD rolls:
    318  if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
    319      setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
    320      setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
    321    log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
    322    strerror(errno));
    323    return -1;
    324  }
    325  */
    326 
    327  /* We've properly switched egid, gid, euid, uid, and supplementary groups if
    328   * we're here. */
    329 #ifdef HAVE_LINUX_CAPABILITIES
    330  if (keep_bindlow) {
    331    if (drop_capabilities(0))
    332      return -1;
    333  }
    334 #endif /* defined(HAVE_LINUX_CAPABILITIES) */
    335 
    336 #if !defined(CYGWIN) && !defined(__CYGWIN__)
    337  /* If we tried to drop privilege to a group/user other than root, attempt to
    338   * restore root (E)(U|G)ID, and abort if the operation succeeds */
    339 
    340  /* Only check for privilege dropping if we were asked to be non-root */
    341  if (pw->pw_uid) {
    342    /* Try changing GID/EGID */
    343    if (pw->pw_gid != old_gid &&
    344        (setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
    345      log_warn(LD_GENERAL, "Was able to restore group credentials even after "
    346               "switching GID: this means that the setgid code didn't work.");
    347      return -1;
    348    }
    349 
    350    /* Try changing UID/EUID */
    351    if (pw->pw_uid != old_uid &&
    352        (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
    353      log_warn(LD_GENERAL, "Was able to restore user credentials even after "
    354               "switching UID: this means that the setuid code didn't work.");
    355      return -1;
    356    }
    357  }
    358 #endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
    359 
    360  /* Check what really happened */
    361  if (log_credential_status()) {
    362    return -1;
    363  }
    364 
    365  have_already_switched_id = 1; /* mark success so we never try again */
    366 
    367 #if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
    368  defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
    369  if (pw->pw_uid) {
    370    /* Re-enable core dumps if we're not running as root. */
    371    log_info(LD_CONFIG, "Re-enabling coredumps");
    372    if (prctl(PR_SET_DUMPABLE, 1)) {
    373      log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
    374    }
    375  }
    376 #endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */
    377  return 0;
    378 
    379 #else /* defined(_WIN32) */
    380  (void)user;
    381  (void)flags;
    382 
    383  log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
    384  return -1;
    385 #endif /* !defined(_WIN32) */
    386 }