LCOV - code coverage report
Current view: top level - lib/process - setuid.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 5 119 4.2 %
Date: 2021-11-24 03:28:48 Functions: 1 4 25.0 %

          Line data    Source code
       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           0 : 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           0 :   uid_t ruid, euid, suid;
      57             :   /* Read, effective and saved GIDs */
      58           0 :   gid_t rgid, egid, sgid;
      59             :   /* Supplementary groups */
      60           0 :   gid_t *sup_gids = NULL;
      61           0 :   int sup_gids_size;
      62             :   /* Number of supplementary groups */
      63           0 :   int ngids;
      64             : 
      65             :   /* log UIDs */
      66             : #ifdef HAVE_GETRESUID
      67           0 :   if (getresuid(&ruid, &euid, &suid) != 0) {
      68           0 :     log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
      69           0 :     return -1;
      70             :   } else {
      71           0 :     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           0 :   if (getresgid(&rgid, &egid, &sgid) != 0) {
      89           0 :     log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
      90           0 :     return -1;
      91             :   } else {
      92           0 :     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           0 :   sup_gids_size = 64;
     108           0 :   sup_gids = tor_calloc(64, sizeof(gid_t));
     109           0 :   while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
     110           0 :          errno == EINVAL &&
     111             :          sup_gids_size < NGROUPS_MAX) {
     112           0 :     sup_gids_size *= 2;
     113           0 :     sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
     114             :   }
     115             : 
     116           0 :   if (ngids < 0) {
     117           0 :     log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
     118             :              strerror(errno));
     119           0 :     tor_free(sup_gids);
     120           0 :     return -1;
     121             :   } else {
     122           0 :     int i, retval = 0;
     123           0 :     char *s = NULL;
     124           0 :     smartlist_t *elts = smartlist_new();
     125             : 
     126           0 :     for (i = 0; i<ngids; i++) {
     127           0 :       smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
     128             :     }
     129             : 
     130           0 :     s = smartlist_join_strings(elts, " ", 0, NULL);
     131             : 
     132           0 :     log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
     133             : 
     134           0 :     tor_free(s);
     135           0 :     SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
     136           0 :     smartlist_free(elts);
     137           0 :     tor_free(sup_gids);
     138             : 
     139           0 :     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           6 : have_capability_support(void)
     150             : {
     151             : #ifdef HAVE_LINUX_CAPABILITIES
     152           6 :   cap_t caps = cap_get_proc();
     153           6 :   if (caps == NULL)
     154             :     return 0;
     155           6 :   cap_free(caps);
     156           6 :   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           0 : 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           0 :   const cap_value_t caplist[] = {
     181             :     CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
     182             :   };
     183           0 :   const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
     184           0 :   const int n_effective = pre_setuid ? 3 : 1;
     185           0 :   const int n_permitted = pre_setuid ? 3 : 1;
     186           0 :   const int n_inheritable = 1;
     187           0 :   const int keepcaps = pre_setuid ? 1 : 0;
     188             : 
     189             :   /* Sets whether we keep capabilities across a setuid. */
     190           0 :   if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
     191           0 :     log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
     192             :              where, strerror(errno));
     193           0 :     return -1;
     194             :   }
     195             : 
     196           0 :   cap_t caps = cap_get_proc();
     197           0 :   if (!caps) {
     198           0 :     log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
     199             :              where, strerror(errno));
     200           0 :     return -1;
     201             :   }
     202           0 :   cap_clear(caps);
     203             : 
     204           0 :   cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
     205           0 :   cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
     206           0 :   cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
     207             : 
     208           0 :   int r = cap_set_proc(caps);
     209           0 :   cap_free(caps);
     210           0 :   if (r < 0) {
     211           0 :     log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
     212             :              where, strerror(errno));
     213           0 :     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           0 : switch_id(const char *user, const unsigned flags)
     231             : {
     232             : #ifndef _WIN32
     233           0 :   const struct passwd *pw = NULL;
     234           0 :   uid_t old_uid;
     235           0 :   gid_t old_gid;
     236           0 :   static int have_already_switched_id = 0;
     237           0 :   const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
     238           0 :   const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
     239             : 
     240           0 :   tor_assert(user);
     241             : 
     242           0 :   if (have_already_switched_id)
     243             :     return 0;
     244             : 
     245             :   /* Log the initial credential state */
     246           0 :   if (log_credential_status())
     247             :     return -1;
     248             : 
     249           0 :   log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
     250             : 
     251             :   /* Get old UID/GID to check if we changed correctly */
     252           0 :   old_uid = getuid();
     253           0 :   old_gid = getgid();
     254             : 
     255             :   /* Lookup the user and group information, if we have a problem, bail out. */
     256           0 :   pw = tor_getpwnam(user);
     257           0 :   if (pw == NULL) {
     258           0 :     log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
     259           0 :     return -1;
     260             :   }
     261             : 
     262             : #ifdef HAVE_LINUX_CAPABILITIES
     263           0 :   (void) warn_if_no_caps;
     264           0 :   if (keep_bindlow) {
     265           0 :     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           0 :   if (setgroups(1, &pw->pw_gid)) {
     278           0 :     log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
     279             :              (int)pw->pw_gid, strerror(errno));
     280           0 :     if (old_uid == pw->pw_uid) {
     281           0 :       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           0 :       log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
     288             :                " as root.");
     289             :     }
     290           0 :     return -1;
     291             :   }
     292             : 
     293           0 :   if (setegid(pw->pw_gid)) {
     294           0 :     log_warn(LD_GENERAL, "Error setting egid to %d: %s",
     295             :              (int)pw->pw_gid, strerror(errno));
     296           0 :     return -1;
     297             :   }
     298             : 
     299           0 :   if (setgid(pw->pw_gid)) {
     300           0 :     log_warn(LD_GENERAL, "Error setting gid to %d: %s",
     301             :              (int)pw->pw_gid, strerror(errno));
     302           0 :     return -1;
     303             :   }
     304             : 
     305           0 :   if (setuid(pw->pw_uid)) {
     306           0 :     log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
     307             :              user, (int)pw->pw_uid, strerror(errno));
     308           0 :     return -1;
     309             :   }
     310             : 
     311           0 :   if (seteuid(pw->pw_uid)) {
     312           0 :     log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
     313             :              user, (int)pw->pw_uid, strerror(errno));
     314           0 :     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           0 :   if (keep_bindlow) {
     331           0 :     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           0 :   if (pw->pw_uid) {
     342             :     /* Try changing GID/EGID */
     343           0 :     if (pw->pw_gid != old_gid &&
     344           0 :         (setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
     345           0 :       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           0 :       return -1;
     348             :     }
     349             : 
     350             :     /* Try changing UID/EUID */
     351           0 :     if (pw->pw_uid != old_uid &&
     352           0 :         (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
     353           0 :       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           0 :       return -1;
     356             :     }
     357             :   }
     358             : #endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
     359             : 
     360             :   /* Check what really happened */
     361           0 :   if (log_credential_status()) {
     362             :     return -1;
     363             :   }
     364             : 
     365           0 :   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           0 :   if (pw->pw_uid) {
     370             :     /* Re-enable core dumps if we're not running as root. */
     371           0 :     log_info(LD_CONFIG, "Re-enabling coredumps");
     372           0 :     if (prctl(PR_SET_DUMPABLE, 1)) {
     373           0 :       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             : }

Generated by: LCOV version 1.14