/src/tor/src/lib/process/setuid.c
Line | Count | Source |
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 | 0 | { |
53 | | /** Log level to use when describing non-error UID/GID status. */ |
54 | 0 | #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 | 0 | #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 | 0 | } else { |
71 | 0 | log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, |
72 | 0 | "UID is %u (real), %u (effective), %u (saved)", |
73 | 0 | (unsigned)ruid, (unsigned)euid, (unsigned)suid); |
74 | 0 | } |
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 | 0 | #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 | 0 | } else { |
92 | 0 | log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, |
93 | 0 | "GID is %u (real), %u (effective), %u (saved)", |
94 | 0 | (unsigned)rgid, (unsigned)egid, (unsigned)sgid); |
95 | 0 | } |
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 | 0 | 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 | 0 | } |
115 | |
|
116 | 0 | if (ngids < 0) { |
117 | 0 | log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s", |
118 | 0 | strerror(errno)); |
119 | 0 | tor_free(sup_gids); |
120 | 0 | return -1; |
121 | 0 | } 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 | 0 | } |
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 | 0 | } |
141 | | |
142 | 0 | return 0; |
143 | 0 | } |
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 | 0 | { |
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 | 0 | return 0; |
159 | 0 | #endif /* defined(HAVE_LINUX_CAPABILITIES) */ |
160 | 0 | } |
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 | 0 | { |
232 | 0 | #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 | 0 | return 0; |
244 | | |
245 | | /* Log the initial credential state */ |
246 | 0 | if (log_credential_status()) |
247 | 0 | 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 | 0 | } |
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 | 0 | (void) keep_bindlow; |
270 | 0 | if (warn_if_no_caps) { |
271 | 0 | log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support " |
272 | 0 | "on this system."); |
273 | 0 | } |
274 | 0 | #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 | 0 | (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 | 0 | "the \"User\" option if you are already running as the user " |
283 | 0 | "you want to be. (If you did not set the User option in your " |
284 | 0 | "torrc, check whether it was specified on the command line " |
285 | 0 | "by a startup script.)", user); |
286 | 0 | } else { |
287 | 0 | log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor" |
288 | 0 | " as root."); |
289 | 0 | } |
290 | 0 | return -1; |
291 | 0 | } |
292 | | |
293 | 0 | if (setegid(pw->pw_gid)) { |
294 | 0 | log_warn(LD_GENERAL, "Error setting egid to %d: %s", |
295 | 0 | (int)pw->pw_gid, strerror(errno)); |
296 | 0 | return -1; |
297 | 0 | } |
298 | | |
299 | 0 | if (setgid(pw->pw_gid)) { |
300 | 0 | log_warn(LD_GENERAL, "Error setting gid to %d: %s", |
301 | 0 | (int)pw->pw_gid, strerror(errno)); |
302 | 0 | return -1; |
303 | 0 | } |
304 | | |
305 | 0 | if (setuid(pw->pw_uid)) { |
306 | 0 | log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s", |
307 | 0 | user, (int)pw->pw_uid, strerror(errno)); |
308 | 0 | return -1; |
309 | 0 | } |
310 | | |
311 | 0 | if (seteuid(pw->pw_uid)) { |
312 | 0 | log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s", |
313 | 0 | user, (int)pw->pw_uid, strerror(errno)); |
314 | 0 | return -1; |
315 | 0 | } |
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 | 0 | #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 | 0 | "switching GID: this means that the setgid code didn't work."); |
347 | 0 | return -1; |
348 | 0 | } |
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 | 0 | "switching UID: this means that the setuid code didn't work."); |
355 | 0 | return -1; |
356 | 0 | } |
357 | 0 | } |
358 | 0 | #endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */ |
359 | | |
360 | | /* Check what really happened */ |
361 | 0 | if (log_credential_status()) { |
362 | 0 | return -1; |
363 | 0 | } |
364 | | |
365 | 0 | have_already_switched_id = 1; /* mark success so we never try again */ |
366 | |
|
367 | 0 | #if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \ |
368 | 0 | 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 | 0 | } |
375 | 0 | } |
376 | 0 | #endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */ |
377 | 0 | 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 | 0 | } |