Coverage Report

Created: 2026-06-15 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/restrict-access.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#define _GNU_SOURCE /* setresgid() */
4
#include <stdio.h> /* for AIX */
5
#include <sys/types.h>
6
#include <unistd.h>
7
8
#include "lib.h"
9
#include "str.h"
10
#include "restrict-access.h"
11
#include "env-util.h"
12
#include "ipwd.h"
13
14
#include <time.h>
15
#ifdef HAVE_PR_SET_DUMPABLE
16
#  include <sys/prctl.h>
17
#endif
18
19
static gid_t process_primary_gid = (gid_t)-1;
20
static gid_t process_privileged_gid = (gid_t)-1;
21
static bool process_using_priv_gid = FALSE;
22
static char *chroot_dir = NULL;
23
24
void restrict_access_init(struct restrict_access_settings *set)
25
0
{
26
0
  i_zero(set);
27
28
0
  set->uid = (uid_t)-1;
29
0
  set->gid = (gid_t)-1;
30
0
  set->privileged_gid = (gid_t)-1;
31
0
}
32
33
static const char *get_uid_str(uid_t uid)
34
0
{
35
0
  struct passwd pw;
36
0
  const char *ret;
37
0
  int old_errno = errno;
38
39
0
  if (i_getpwuid(uid, &pw) <= 0)
40
0
    ret = dec2str(uid);
41
0
  else
42
0
    ret = t_strdup_printf("%s(%s)", dec2str(uid), pw.pw_name);
43
0
  errno = old_errno;
44
0
  return ret;
45
0
}
46
47
static const char *get_gid_str(gid_t gid)
48
0
{
49
0
  struct group group;
50
0
  const char *ret;
51
0
  int old_errno = errno;
52
53
0
  if (i_getgrgid(gid, &group) <= 0)
54
0
    ret = dec2str(gid);
55
0
  else
56
0
    ret = t_strdup_printf("%s(%s)", dec2str(gid), group.gr_name);
57
0
  errno = old_errno;
58
0
  return ret;
59
0
}
60
61
static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid,
62
         const char *gid_source)
63
0
{
64
0
  string_t *str;
65
66
0
  if (privileged_gid == (gid_t)-1) {
67
0
    if (primary_gid == getgid() && primary_gid == getegid()) {
68
      /* everything is already set */
69
0
      return;
70
0
    }
71
72
0
    if (setgid(primary_gid) == 0)
73
0
      return;
74
75
0
    str = t_str_new(128);
76
0
    str_printfa(str, "setgid(%s", get_gid_str(primary_gid));
77
0
    if (gid_source != NULL)
78
0
      str_printfa(str, " from %s", gid_source);
79
0
    str_printfa(str, ") failed with euid=%s, gid=%s, egid=%s: %m "
80
0
          "(This binary should probably be called with "
81
0
          "process group set to %s instead of %s)",
82
0
          get_uid_str(geteuid()),
83
0
          get_gid_str(getgid()), get_gid_str(getegid()),
84
0
          get_gid_str(primary_gid), get_gid_str(getegid()));
85
0
    i_fatal("%s", str_c(str));
86
0
  }
87
88
0
  if (getegid() != 0 && primary_gid == getgid() &&
89
0
      primary_gid == getegid()) {
90
    /* privileged_gid is hopefully in saved ID. if not,
91
       there's nothing we can do about it. */
92
0
    return;
93
0
  }
94
95
0
#ifdef HAVE_SETRESGID
96
0
  if (setresgid(primary_gid, primary_gid, privileged_gid) != 0) {
97
0
    i_fatal("setresgid(%s,%s,%s) failed with euid=%s: %m",
98
0
      get_gid_str(primary_gid), get_gid_str(primary_gid),
99
0
      get_gid_str(privileged_gid), get_uid_str(geteuid()));
100
0
  }
101
#else
102
  if (geteuid() == 0) {
103
    /* real, effective, saved -> privileged_gid */
104
    if (setgid(privileged_gid) < 0) {
105
      i_fatal("setgid(%s) failed: %m",
106
        get_gid_str(privileged_gid));
107
    }
108
  }
109
  /* real, effective -> primary_gid
110
     saved -> keep */
111
  if (setregid(primary_gid, primary_gid) != 0) {
112
    i_fatal("setregid(%s,%s) failed with euid=%s: %m",
113
      get_gid_str(primary_gid), get_gid_str(privileged_gid),
114
      get_uid_str(geteuid()));
115
  }
116
#endif
117
0
}
118
119
gid_t *restrict_get_groups_list(unsigned int *gid_count_r)
120
0
{
121
0
  gid_t *gid_list;
122
0
  int ret, gid_count;
123
124
0
  if ((gid_count = getgroups(0, NULL)) < 0)
125
0
    i_fatal("getgroups() failed: %m");
126
127
  /* @UNSAFE */
128
0
  gid_list = t_new(gid_t, gid_count+1); /* +1 in case gid_count=0 */
129
0
  if ((ret = getgroups(gid_count, gid_list)) < 0)
130
0
    i_fatal("getgroups() failed: %m");
131
132
0
  *gid_count_r = ret;
133
0
  return gid_list;
134
0
}
135
136
static void drop_restricted_groups(const struct restrict_access_settings *set,
137
           gid_t *gid_list, unsigned int *gid_count,
138
           bool *have_root_group)
139
0
{
140
  /* @UNSAFE */
141
0
  unsigned int i, used;
142
143
0
  for (i = 0, used = 0; i < *gid_count; i++) {
144
0
    if (gid_list[i] >= set->first_valid_gid &&
145
0
        (set->last_valid_gid == 0 ||
146
0
         gid_list[i] <= set->last_valid_gid)) {
147
0
      if (gid_list[i] == 0)
148
0
        *have_root_group = TRUE;
149
0
      gid_list[used++] = gid_list[i];
150
0
    }
151
0
  }
152
0
  *gid_count = used;
153
0
}
154
155
static gid_t get_group_id(const char *name)
156
0
{
157
0
  struct group group;
158
0
  gid_t gid;
159
160
0
  if (str_to_gid(name, &gid) == 0)
161
0
    return gid;
162
163
0
  switch (i_getgrnam(name, &group)) {
164
0
  case -1:
165
0
    i_fatal("getgrnam(%s) failed: %m", name);
166
0
  case 0:
167
0
    i_fatal("unknown group name in extra_groups: %s", name);
168
0
  default:
169
0
    return group.gr_gid;
170
0
  }
171
0
}
172
173
static void fix_groups_list(const struct restrict_access_settings *set,
174
          bool preserve_existing, bool *have_root_group)
175
0
{
176
0
  gid_t gid, *gid_list, *gid_list2;
177
0
  const char *const *tmp, *empty = NULL;
178
0
  unsigned int i, gid_count;
179
0
  bool add_primary_gid;
180
181
  /* if we're using a privileged GID, we can temporarily drop our
182
     effective GID. we still want to be able to use its privileges,
183
     so add it to supplementary groups. */
184
0
  add_primary_gid = process_privileged_gid != (gid_t)-1;
185
186
0
  tmp = set->extra_groups == NULL ? &empty :
187
0
    t_strsplit_spaces(set->extra_groups, ", ");
188
189
0
  if (preserve_existing) {
190
0
    gid_list = restrict_get_groups_list(&gid_count);
191
0
    drop_restricted_groups(set, gid_list, &gid_count,
192
0
               have_root_group);
193
    /* see if the list already contains the primary GID */
194
0
    for (i = 0; i < gid_count; i++) {
195
0
      if (gid_list[i] == process_primary_gid) {
196
0
        add_primary_gid = FALSE;
197
0
        break;
198
0
      }
199
0
    }
200
0
  } else {
201
0
    gid_list = NULL;
202
0
    gid_count = 0;
203
0
  }
204
0
  if (gid_count == 0) {
205
    /* Some OSes don't like an empty groups list,
206
       so use the primary GID as the only one. */
207
0
    gid_list = t_new(gid_t, 2);
208
0
    gid_list[0] = process_primary_gid;
209
0
    gid_count = 1;
210
0
    add_primary_gid = FALSE;
211
0
  }
212
213
0
  if (*tmp != NULL || add_primary_gid) {
214
    /* @UNSAFE: add extra groups and/or primary GID to gids list */
215
0
    gid_list2 = t_new(gid_t, gid_count + str_array_length(tmp) + 1);
216
0
    memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t));
217
0
    for (; *tmp != NULL; tmp++) {
218
0
      gid = get_group_id(*tmp);
219
0
      if (gid != process_primary_gid)
220
0
        gid_list2[gid_count++] = gid;
221
0
    }
222
0
    if (add_primary_gid)
223
0
      gid_list2[gid_count++] = process_primary_gid;
224
0
    gid_list = gid_list2;
225
0
  }
226
227
0
  if (setgroups(gid_count, gid_list) < 0) {
228
0
    if (errno == EINVAL) {
229
0
      i_fatal("setgroups(%s) failed: Too many extra groups",
230
0
        set->extra_groups == NULL ? "" :
231
0
        set->extra_groups);
232
0
    } else {
233
0
      i_fatal("setgroups() failed: %m");
234
0
    }
235
0
  }
236
0
}
237
238
static const char *
239
get_setuid_error_str(const struct restrict_access_settings *set, uid_t target_uid)
240
0
{
241
0
  string_t *str = t_str_new(128);
242
243
0
  str_printfa(str, "setuid(%s", get_uid_str(target_uid));
244
0
  if (set->uid_source != NULL)
245
0
    str_printfa(str, " from %s", set->uid_source);
246
0
  str_printfa(str, ") failed with euid=%s: %m ",
247
0
        get_uid_str(geteuid()));
248
0
  if (errno == EAGAIN) {
249
0
    str_append(str, "(ulimit -u reached)");
250
0
  } else {
251
0
    str_printfa(str, "(This binary should probably be called with "
252
0
          "process user set to %s instead of %s)",
253
0
          get_uid_str(target_uid), get_uid_str(geteuid()));
254
0
  }
255
0
  return str_c(str);
256
0
}
257
258
void restrict_access(const struct restrict_access_settings *set,
259
         enum restrict_access_flags flags, const char *home)
260
0
{
261
0
  bool is_root, have_root_group, preserve_groups = FALSE;
262
0
  bool allow_root_gid;
263
0
  bool allow_root = (flags & RESTRICT_ACCESS_FLAG_ALLOW_ROOT) != 0;
264
0
  uid_t target_uid = set->uid;
265
266
0
  is_root = geteuid() == 0;
267
268
0
  if (!is_root &&
269
0
      !set->allow_setuid_root &&
270
0
      getuid() == 0) {
271
    /* recover current effective UID */
272
0
    if (target_uid == (uid_t)-1)
273
0
      target_uid = geteuid();
274
0
    else
275
0
      i_assert(target_uid > 0);
276
    /* try to elevate to root */
277
0
    if (seteuid(0) < 0)
278
0
      i_fatal("seteuid(0) failed: %m");
279
0
    is_root = TRUE;
280
0
  }
281
282
  /* set the primary/privileged group */
283
0
  process_primary_gid = set->gid;
284
0
  process_privileged_gid = set->privileged_gid;
285
0
  if (process_privileged_gid == process_primary_gid) {
286
    /* a pointless configuration, ignore it */
287
0
    process_privileged_gid = (gid_t)-1;
288
0
  }
289
290
0
  have_root_group = process_primary_gid == 0;
291
0
  if (process_primary_gid != (gid_t)-1 ||
292
0
      process_privileged_gid != (gid_t)-1) {
293
0
    if (process_primary_gid == (gid_t)-1)
294
0
      process_primary_gid = getegid();
295
0
    restrict_init_groups(process_primary_gid,
296
0
             process_privileged_gid, set->gid_source);
297
0
  } else {
298
0
    if (process_primary_gid == (gid_t)-1)
299
0
      process_primary_gid = getegid();
300
0
  }
301
302
  /* set system user's groups */
303
0
  if (set->system_groups_user != NULL && is_root) {
304
0
    if (initgroups(set->system_groups_user,
305
0
             process_primary_gid) < 0) {
306
0
      i_fatal("initgroups(%s, %s) failed: %m",
307
0
        set->system_groups_user,
308
0
        get_gid_str(process_primary_gid));
309
0
    }
310
0
    preserve_groups = TRUE;
311
0
  }
312
313
  /* add extra groups. if we set system user's groups, drop the
314
     restricted groups at the same time. */
315
0
  if (is_root) T_BEGIN {
316
0
    fix_groups_list(set, preserve_groups,
317
0
        &have_root_group);
318
0
  } T_END;
319
320
  /* chrooting */
321
0
  if (set->chroot_dir != NULL) {
322
    /* kludge: localtime() must be called before chroot(),
323
       or the timezone isn't known */
324
0
    time_t t = 0;
325
0
    (void)localtime(&t);
326
327
0
    if (chroot(set->chroot_dir) != 0)
328
0
      i_fatal("chroot(%s) failed: %m", set->chroot_dir);
329
    /* makes static analyzers happy, and is more secure */
330
0
    if (chdir("/") != 0)
331
0
      i_fatal("chdir(/) failed: %m");
332
333
0
    chroot_dir = i_strdup(set->chroot_dir);
334
335
0
    if (home != NULL) {
336
0
      if (chdir(home) < 0) {
337
0
        i_error("chdir(%s) failed: %m", home);
338
0
      }
339
0
    }
340
0
  }
341
342
  /* uid last */
343
0
  if (target_uid != (uid_t)-1) {
344
0
    if (setuid(target_uid) != 0)
345
0
      i_fatal("%s", get_setuid_error_str(set, target_uid));
346
0
  }
347
348
  /* verify that we actually dropped the privileges */
349
0
  if ((target_uid != (uid_t)-1 && target_uid != 0) || !allow_root) {
350
0
    if (setuid(0) == 0) {
351
0
      if (!allow_root &&
352
0
          (target_uid == 0 || target_uid == (uid_t)-1))
353
0
        i_fatal("This process must not be run as root");
354
355
0
      i_fatal("We couldn't drop root privileges");
356
0
    }
357
0
  }
358
359
0
  if (set->first_valid_gid != 0)
360
0
    allow_root_gid = FALSE;
361
0
  else if (process_primary_gid == 0 || process_privileged_gid == 0)
362
0
    allow_root_gid = TRUE;
363
0
  else
364
0
    allow_root_gid = FALSE;
365
366
0
  if (!allow_root_gid && target_uid != 0 &&
367
0
      (target_uid != (uid_t)-1 || !is_root)) {
368
0
    if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) {
369
0
      if (process_primary_gid == 0)
370
0
        i_fatal("GID 0 isn't permitted");
371
0
      i_fatal("We couldn't drop root group privileges "
372
0
        "(wanted=%s, gid=%s, egid=%s)",
373
0
        get_gid_str(process_primary_gid),
374
0
        get_gid_str(getgid()), get_gid_str(getegid()));
375
0
    }
376
0
  }
377
0
}
378
379
void restrict_access_set_env(const struct restrict_access_settings *set)
380
0
{
381
0
  if (set->system_groups_user != NULL &&
382
0
      *set->system_groups_user != '\0')
383
0
    env_put("RESTRICT_USER", set->system_groups_user);
384
0
  if (set->chroot_dir != NULL && *set->chroot_dir != '\0')
385
0
    env_put("RESTRICT_CHROOT", set->chroot_dir);
386
387
0
  if (set->uid != (uid_t)-1)
388
0
    env_put("RESTRICT_SETUID", dec2str(set->uid));
389
0
  if (set->gid != (gid_t)-1)
390
0
    env_put("RESTRICT_SETGID", dec2str(set->gid));
391
0
  if (set->privileged_gid != (gid_t)-1)
392
0
    env_put("RESTRICT_SETGID_PRIV", dec2str(set->privileged_gid));
393
0
  if (set->extra_groups != NULL && *set->extra_groups != '\0')
394
0
    env_put("RESTRICT_SETEXTRAGROUPS", set->extra_groups);
395
396
0
  if (set->first_valid_gid != 0)
397
0
    env_put("RESTRICT_GID_FIRST", dec2str(set->first_valid_gid));
398
0
  if (set->last_valid_gid != 0)
399
0
    env_put("RESTRICT_GID_LAST", dec2str(set->last_valid_gid));
400
0
}
401
402
static const char *null_if_empty(const char *str)
403
0
{
404
0
  return str == NULL || *str == '\0' ? NULL : str;
405
0
}
406
407
void restrict_access_get_env(struct restrict_access_settings *set_r)
408
0
{
409
0
  const char *value;
410
411
0
  restrict_access_init(set_r);
412
0
  if ((value = getenv("RESTRICT_SETUID")) != NULL) {
413
0
    if (str_to_uid(value, &set_r->uid) < 0)
414
0
      i_fatal("Invalid uid: %s", value);
415
0
  }
416
0
  if ((value = getenv("RESTRICT_SETGID")) != NULL) {
417
0
    if (str_to_gid(value, &set_r->gid) < 0)
418
0
      i_fatal("Invalid gid: %s", value);
419
0
  }
420
0
  if ((value = getenv("RESTRICT_SETGID_PRIV")) != NULL) {
421
0
    if (str_to_gid(value, &set_r->privileged_gid) < 0)
422
0
      i_fatal("Invalid privileged_gid: %s", value);
423
0
  }
424
0
  if ((value = getenv("RESTRICT_GID_FIRST")) != NULL) {
425
0
    if (str_to_gid(value, &set_r->first_valid_gid) < 0)
426
0
      i_fatal("Invalid first_valid_gid: %s", value);
427
0
  }
428
0
  if ((value = getenv("RESTRICT_GID_LAST")) != NULL) {
429
0
    if (str_to_gid(value, &set_r->last_valid_gid) < 0)
430
0
      i_fatal("Invalid last_value_gid: %s", value);
431
0
  }
432
433
0
  set_r->extra_groups = null_if_empty(getenv("RESTRICT_SETEXTRAGROUPS"));
434
0
  set_r->system_groups_user = null_if_empty(getenv("RESTRICT_USER"));
435
0
  set_r->chroot_dir = null_if_empty(getenv("RESTRICT_CHROOT"));
436
0
}
437
438
void restrict_access_by_env(enum restrict_access_flags flags, const char *home)
439
0
{
440
0
  struct restrict_access_settings set;
441
442
0
  restrict_access_get_env(&set);
443
0
  restrict_access(&set, flags, home);
444
445
  /* clear the environment, so we don't fail if we get back here */
446
0
  env_remove("RESTRICT_SETUID");
447
0
  if (process_privileged_gid == (gid_t)-1) {
448
    /* if we're dropping privileges before executing and
449
       a privileged group is set, the groups must be fixed
450
       after exec */
451
0
    env_remove("RESTRICT_SETGID");
452
0
    env_remove("RESTRICT_SETGID_PRIV");
453
0
  }
454
0
  env_remove("RESTRICT_GID_FIRST");
455
0
  env_remove("RESTRICT_GID_LAST");
456
0
  if (getuid() != 0)
457
0
    env_remove("RESTRICT_SETEXTRAGROUPS");
458
0
  else {
459
    /* Preserve RESTRICT_SETEXTRAGROUPS, so if we're again dropping
460
       more privileges we'll still preserve the extra groups. This
461
       mainly means preserving service { extra_groups } for lmtp
462
       and doveadm accesses. */
463
0
  }
464
0
  env_remove("RESTRICT_USER");
465
0
  env_remove("RESTRICT_CHROOT");
466
0
}
467
468
const char *restrict_access_get_current_chroot(void)
469
0
{
470
0
  return chroot_dir;
471
0
}
472
473
void restrict_access_set_dumpable(bool allow ATTR_UNUSED)
474
0
{
475
0
#ifdef HAVE_PR_SET_DUMPABLE
476
0
  if (prctl(PR_SET_DUMPABLE, allow ? 1 : 0, 0, 0, 0) < 0)
477
0
    i_error("prctl(PR_SET_DUMPABLE) failed: %m");
478
0
#endif
479
0
}
480
481
bool restrict_access_get_dumpable(void)
482
0
{
483
0
#ifdef HAVE_PR_SET_DUMPABLE
484
0
  bool allow = FALSE;
485
0
  if (prctl(PR_GET_DUMPABLE, &allow, 0, 0, 0) < 0)
486
0
    i_error("prctl(PR_GET_DUMPABLE) failed: %m");
487
0
  return allow;
488
0
#endif
489
0
  return TRUE;
490
0
}
491
492
void restrict_access_allow_coredumps(bool allow)
493
0
{
494
0
  if (getenv("PR_SET_DUMPABLE") != NULL) {
495
0
    restrict_access_set_dumpable(allow);
496
0
  }
497
0
}
498
499
int restrict_access_use_priv_gid(void)
500
0
{
501
0
  i_assert(!process_using_priv_gid);
502
503
0
  if (process_privileged_gid == (gid_t)-1)
504
0
    return 0;
505
0
  if (setegid(process_privileged_gid) < 0) {
506
0
    i_error("setegid(privileged) failed: %m");
507
0
    return -1;
508
0
  }
509
0
  process_using_priv_gid = TRUE;
510
0
  return 0;
511
0
}
512
513
void restrict_access_drop_priv_gid(void)
514
0
{
515
0
  if (!process_using_priv_gid)
516
0
    return;
517
518
0
  if (setegid(process_primary_gid) < 0)
519
0
    i_fatal("setegid(primary) failed: %m");
520
0
  process_using_priv_gid = FALSE;
521
0
}
522
523
bool restrict_access_have_priv_gid(void)
524
0
{
525
0
  return process_privileged_gid != (gid_t)-1;
526
0
}
527
528
void restrict_access_deinit(void)
529
0
{
530
  i_free(chroot_dir);
531
0
}