/* Copyright (c) 2003, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file setuid.c * \brief Change the user ID after Tor has started (Unix only) **/ #include "orconfig.h" #include "lib/process/setuid.h" #if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC) #define HAVE_LINUX_CAPABILITIES #endif #include "lib/container/smartlist.h" #include "lib/fs/userdb.h" #include "lib/log/torlog.h" #include "lib/log/util_bug.h" #include "lib/malloc/util_malloc.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_SYS_CAPABILITY_H #include #endif #ifdef HAVE_SYS_PRCTL_H #include #endif #include #include #ifndef _WIN32 /** Log details of current user and group credentials. Return 0 on * success. Logs and return -1 on failure. */ static int log_credential_status(void) { /** Log level to use when describing non-error UID/GID status. */ #define CREDENTIAL_LOG_LEVEL LOG_INFO /* Real, effective and saved UIDs */ uid_t ruid, euid, suid; /* Read, effective and saved GIDs */ gid_t rgid, egid, sgid; /* Supplementary groups */ gid_t *sup_gids = NULL; int sup_gids_size; /* Number of supplementary groups */ int ngids; /* log UIDs */ #ifdef HAVE_GETRESUID if (getresuid(&ruid, &euid, &suid) != 0 ) { log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno)); return -1; } else { log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "UID is %u (real), %u (effective), %u (saved)", (unsigned)ruid, (unsigned)euid, (unsigned)suid); } #else /* !(defined(HAVE_GETRESUID)) */ /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */ ruid = getuid(); euid = geteuid(); (void)suid; log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "UID is %u (real), %u (effective), unknown (saved)", (unsigned)ruid, (unsigned)euid); #endif /* defined(HAVE_GETRESUID) */ /* log GIDs */ #ifdef HAVE_GETRESGID if (getresgid(&rgid, &egid, &sgid) != 0 ) { log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno)); return -1; } else { log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "GID is %u (real), %u (effective), %u (saved)", (unsigned)rgid, (unsigned)egid, (unsigned)sgid); } #else /* !(defined(HAVE_GETRESGID)) */ /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */ rgid = getgid(); egid = getegid(); (void)sgid; log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "GID is %u (real), %u (effective), unknown (saved)", (unsigned)rgid, (unsigned)egid); #endif /* defined(HAVE_GETRESGID) */ /* log supplementary groups */ sup_gids_size = 64; sup_gids = tor_calloc(64, sizeof(gid_t)); while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 && errno == EINVAL && sup_gids_size < NGROUPS_MAX) { sup_gids_size *= 2; sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size); } if (ngids < 0) { log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s", strerror(errno)); tor_free(sup_gids); return -1; } else { int i, retval = 0; char *s = NULL; smartlist_t *elts = smartlist_new(); for (i = 0; iuser 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, 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); if (have_already_switched_id) return 0; /* Log the initial credential state */ if (log_credential_status()) return -1; log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups"); /* Get old UID/GID to check if we changed correctly */ old_uid = getuid(); old_gid = getgid(); /* Lookup the user and group information, if we have a problem, bail out. */ pw = tor_getpwnam(user); if (pw == NULL) { log_warn(LD_CONFIG, "Error setting configured user: %s not found", user); return -1; } #ifdef HAVE_LINUX_CAPABILITIES (void) warn_if_no_caps; if (keep_bindlow) { if (drop_capabilities(1)) return -1; } #else /* !(defined(HAVE_LINUX_CAPABILITIES)) */ (void) keep_bindlow; if (warn_if_no_caps) { log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support " "on this system."); } #endif /* defined(HAVE_LINUX_CAPABILITIES) */ /* 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\".", (int)pw->pw_gid, strerror(errno)); if (old_uid == pw->pw_uid) { log_warn(LD_GENERAL, "Tor is already running as %s. You do not need " "the \"User\" option if you are already running as the user " "you want to be. (If you did not set the User option in your " "torrc, check whether it was specified on the command line " "by a startup script.)", user); } else { log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor" " as root."); } return -1; } if (setegid(pw->pw_gid)) { log_warn(LD_GENERAL, "Error setting egid to %d: %s", (int)pw->pw_gid, strerror(errno)); return -1; } if (setgid(pw->pw_gid)) { log_warn(LD_GENERAL, "Error setting gid to %d: %s", (int)pw->pw_gid, strerror(errno)); return -1; } if (setuid(pw->pw_uid)) { log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s", user, (int)pw->pw_uid, strerror(errno)); return -1; } if (seteuid(pw->pw_uid)) { log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s", user, (int)pw->pw_uid, strerror(errno)); return -1; } /* This is how OpenBSD rolls: if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) || setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) { setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) { log_warn(LD_GENERAL, "Error setting configured UID/GID: %s", strerror(errno)); return -1; } */ /* 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 /* defined(HAVE_LINUX_CAPABILITIES) */ #if !defined(CYGWIN) && !defined(__CYGWIN__) /* If we tried to drop privilege to a group/user other than root, attempt to * restore root (E)(U|G)ID, and abort if the operation succeeds */ /* Only check for privilege dropping if we were asked to be non-root */ if (pw->pw_uid) { /* Try changing GID/EGID */ if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1)) { log_warn(LD_GENERAL, "Was able to restore group credentials even after " "switching GID: this means that the setgid code didn't work."); return -1; } /* Try changing UID/EUID */ if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) { log_warn(LD_GENERAL, "Was able to restore user credentials even after " "switching UID: this means that the setuid code didn't work."); return -1; } } #endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */ /* Check what really happened */ if (log_credential_status()) { return -1; } have_already_switched_id = 1; /* mark success so we never try again */ #if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \ defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) if (pw->pw_uid) { /* Re-enable core dumps if we're not running as root. */ log_info(LD_CONFIG, "Re-enabling coredumps"); if (prctl(PR_SET_DUMPABLE, 1)) { log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno)); } } #endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */ return 0; #else /* !(!defined(_WIN32)) */ (void)user; (void)flags; log_warn(LD_CONFIG, "Switching users is unsupported on your OS."); return -1; #endif /* !defined(_WIN32) */ }