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
log_credential_status(void)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
have_capability_support(void)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
drop_capabilities(int pre_setuid)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
switch_id(const char * user,const unsigned flags)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 }
387