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 }