Coverage Report

Created: 2026-04-08 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}