Tor 0.4.9.0-alpha-dev
setuid.c
Go to the documentation of this file.
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
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 */
50static int
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. **/
148int
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 */
175static int
176drop_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 */
229int
230switch_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 */
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}
Headers for log.c.
#define log_fn(severity, domain, args,...)
Definition: log.h:283
#define LD_GENERAL
Definition: log.h:62
#define LD_CONFIG
Definition: log.h:68
Headers for util_malloc.c.
#define tor_free(p)
Definition: malloc.h:56
static int log_credential_status(void)
Definition: setuid.c:51
int have_capability_support(void)
Definition: setuid.c:149
int switch_id(const char *user, const unsigned flags)
Definition: setuid.c:230
Header for setuid.c.
#define SWITCH_ID_WARN_IF_NO_CAPS
Definition: setuid.h:19
#define SWITCH_ID_KEEP_BINDLOW
Definition: setuid.h:17
void smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern,...)
Definition: smartlist.c:36
char * smartlist_join_strings(smartlist_t *sl, const char *join, int terminate, size_t *len_out)
Definition: smartlist.c:279
Header for smartlist.c.
smartlist_t * smartlist_new(void)
#define SMARTLIST_FOREACH(sl, type, var, cmd)
const struct passwd * tor_getpwnam(const char *username)
Definition: userdb.c:70
Header for userdb.c.
Macros to manage assertions, fatal and non-fatal.
#define tor_assert(expr)
Definition: util_bug.h:103