Coverage Report

Created: 2025-07-11 06:58

/src/sudo/lib/util/getgrouplist.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2010, 2011, 2013-2021
5
 *  Todd C. Miller <Todd.Miller@sudo.ws>
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
/*
21
 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
22
 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
23
 */
24
25
#include <config.h>
26
27
#include <stdlib.h>
28
#include <string.h>
29
#include <grp.h>
30
#include <limits.h>
31
#include <unistd.h>
32
#ifdef HAVE_NSS_SEARCH
33
# include <errno.h>
34
# include <limits.h>
35
# include <nsswitch.h>
36
# ifdef HAVE_NSS_DBDEFS_H
37
#  include <nss_dbdefs.h>
38
# else
39
#  include <compat/nss_dbdefs.h>
40
# endif
41
#endif
42
43
#include <sudo_compat.h>
44
#include <sudo_debug.h>
45
#include <sudo_util.h>
46
47
#ifndef HAVE_GETGROUPLIST
48
int
49
sudo_getgrouplist(const char *name, GETGROUPS_T basegid, GETGROUPS_T *groups,
50
    int *ngroupsp)
51
{
52
    return sudo_getgrouplist2(name, basegid, &groups, ngroupsp);
53
}
54
#endif /* HAVE_GETGROUPLIST */
55
56
#if defined(HAVE_GETGROUPLIST)
57
58
#if defined(HAVE_GETGROUPLIST_2) && !HAVE_DECL_GETGROUPLIST_2
59
int getgrouplist_2(const char *name, GETGROUPS_T basegid, GETGROUPS_T **groups);
60
#endif /* HAVE_GETGROUPLIST_2 && !HAVE_DECL_GETGROUPLIST_2 */
61
62
/*
63
 * Extended getgrouplist(3) using getgrouplist(3) and getgrouplist_2(3)
64
 */
65
int
66
sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
67
    GETGROUPS_T **groupsp, int *ngroupsp)
68
27.3k
{
69
#ifdef __APPLE__
70
    int *groups = (int *)*groupsp;
71
#else
72
27.3k
    GETGROUPS_T *groups = *groupsp;
73
27.3k
#endif
74
27.3k
    int ngroups;
75
27.3k
#ifndef HAVE_GETGROUPLIST_2
76
27.3k
    long grpsize;
77
27.3k
    int tries;
78
27.3k
#endif
79
27.3k
    debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
80
81
    /* For static group vector, just use getgrouplist(3). */
82
27.3k
    if (groups != NULL)
83
27.3k
  debug_return_int(getgrouplist(name, basegid, groups, ngroupsp));
84
85
#ifdef HAVE_GETGROUPLIST_2
86
    if ((ngroups = getgrouplist_2(name, basegid, groupsp)) == -1)
87
  debug_return_int(-1);
88
    *ngroupsp = ngroups;
89
    debug_return_int(0);
90
#else
91
0
    grpsize = sysconf(_SC_NGROUPS_MAX);
92
0
    if (grpsize < 0 || grpsize > INT_MAX)
93
0
  grpsize = NGROUPS_MAX;
94
0
    grpsize++;  /* include space for the primary gid */
95
    /*
96
     * It is possible to belong to more groups in the group database
97
     * than NGROUPS_MAX.
98
     */
99
0
    for (tries = 0; tries < 10; tries++) {
100
0
  free(groups);
101
0
  groups = reallocarray(NULL, (size_t)grpsize, sizeof(*groups));
102
0
  if (groups == NULL)
103
0
      debug_return_int(-1);
104
0
  ngroups = (int)grpsize;
105
0
  if (getgrouplist(name, basegid, groups, &ngroups) != -1) {
106
0
      *groupsp = groups;
107
0
      *ngroupsp = ngroups;
108
0
      debug_return_int(0);
109
0
  }
110
0
  if (ngroups == grpsize) {
111
      /* Failed for some reason other than ngroups too small. */
112
0
      break;
113
0
  }
114
  /* getgrouplist(3) set ngroups to the required length, use it. */
115
0
  grpsize = ngroups;
116
0
    }
117
0
    free(groups);
118
0
    debug_return_int(-1);
119
0
#endif /* HAVE_GETGROUPLIST_2 */
120
0
}
121
122
#elif defined(HAVE_GETGRSET)
123
124
/*
125
 * Extended getgrouplist(3) using AIX getgrset(3)
126
 */
127
int
128
sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
129
    GETGROUPS_T **groupsp, int *ngroupsp)
130
{
131
    GETGROUPS_T *groups = *groupsp;
132
    char *cp, *last, *grset = NULL;
133
    const char *errstr;
134
    int ngroups = 1;
135
    int grpsize = *ngroupsp;
136
    int ret = -1;
137
    gid_t gid;
138
    debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
139
140
#ifdef HAVE_SETAUTHDB
141
    aix_setauthdb((char *) name, NULL);
142
#endif
143
    if ((grset = getgrset(name)) == NULL)
144
  goto done;
145
146
    if (groups == NULL) {
147
  /* Dynamically-sized group vector, count groups and alloc. */
148
  grpsize = 1;  /* reserve one for basegid */
149
  if (*grset != '\0') {
150
      grpsize++;  /* at least one supplementary group */
151
      for (cp = grset; *cp != '\0'; cp++) {
152
    if (*cp == ',')
153
        grpsize++;
154
      }
155
  }
156
  groups = reallocarray(NULL, grpsize, sizeof(*groups));
157
  if (groups == NULL)
158
      debug_return_int(-1);
159
    } else {
160
  /* Static group vector. */
161
  if (grpsize < 1)
162
      debug_return_int(-1);
163
    }
164
165
    /* We support BSD semantics where the first element is the base gid */
166
    groups[0] = basegid;
167
168
    for (cp = strtok_r(grset, ",", &last); cp != NULL; cp = strtok_r(NULL, ",", &last)) {
169
  gid = sudo_strtoid(cp, &errstr);
170
  if (errstr == NULL && gid != basegid) {
171
      if (ngroups == grpsize)
172
    goto done;
173
      groups[ngroups++] = gid;
174
  }
175
    }
176
    ret = 0;
177
178
done:
179
    free(grset);
180
#ifdef HAVE_SETAUTHDB
181
    aix_restoreauthdb();
182
#endif
183
    *groupsp = groups;
184
    *ngroupsp = ngroups;
185
186
    debug_return_int(ret);
187
}
188
189
#elif defined(HAVE_NSS_SEARCH)
190
191
#ifndef ALIGNBYTES
192
# define ALIGNBYTES (sizeof(long) - 1L)
193
#endif
194
#ifndef ALIGN
195
# define ALIGN(p) (((unsigned long)(p) + ALIGNBYTES) & ~ALIGNBYTES)
196
#endif
197
198
#if defined(HAVE__NSS_INITF_GROUP) || defined(HAVE___NSS_INITF_GROUP)
199
extern void _nss_initf_group(nss_db_params_t *params);
200
#else
201
static void
202
_nss_initf_group(nss_db_params_t *params)
203
{
204
    params->name = NSS_DBNAM_GROUP;
205
    params->default_config = NSS_DEFCONF_GROUP;
206
}
207
#endif
208
209
/*
210
 * Convert a groups file string (instr) to a struct group (ent) using
211
 * buf for storage.  
212
 */
213
static int
214
str2grp(const char *instr, int inlen, void *ent, char *buf, int buflen)
215
{
216
    struct group *grp = ent;
217
    char *cp, *fieldsep = buf;
218
    char **gr_mem, **gr_end;
219
    const char *errstr;
220
    int yp = 0;
221
    id_t id;
222
    debug_decl(str2grp, SUDO_DEBUG_UTIL);
223
224
    /* Must at least have space to copy instr -> buf. */
225
    if (inlen >= buflen)
226
  debug_return_int(NSS_STR_PARSE_ERANGE);
227
    
228
    /* Paranoia: buf and instr should be distinct. */
229
    if (buf != instr) {
230
  memmove(buf, instr, inlen);
231
  buf[inlen] = '\0';
232
    }
233
234
    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
235
  debug_return_int(NSS_STR_PARSE_PARSE);
236
    *fieldsep++ = '\0';
237
    grp->gr_name = cp;
238
239
    /* Check for YP inclusion/exclusion entries. */
240
    if (*cp == '+' || *cp == '-') {
241
  /* Only the name is required for YP inclusion/exclusion entries. */
242
  grp->gr_passwd = (char *)"";
243
  grp->gr_gid = 0;
244
  grp->gr_mem = NULL;
245
  yp = 1;
246
    }
247
248
    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
249
  debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
250
    *fieldsep++ = '\0';
251
    grp->gr_passwd = cp;
252
253
    if ((fieldsep = strchr(cp = fieldsep, ':')) == NULL)
254
  debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
255
    *fieldsep++ = '\0';
256
    id = sudo_strtoid(cp, &errstr);
257
    if (errstr != NULL) {
258
  /*
259
   * A range error is always a fatal error, but ignore garbage
260
   * at the end of YP entries since it has no meaning.
261
   */
262
  if (errno == ERANGE)
263
      debug_return_int(NSS_STR_PARSE_ERANGE);
264
  debug_return_int(yp ? NSS_STR_PARSE_SUCCESS : NSS_STR_PARSE_PARSE);
265
    }
266
#ifdef GID_NOBODY
267
    /* Negative gids get mapped to nobody on Solaris. */
268
    if (*cp == '-' && id != 0)
269
  grp->gr_gid = GID_NOBODY;
270
    else
271
#endif
272
  grp->gr_gid = (gid_t)id;
273
274
    /* Store group members, taking care to use proper alignment. */
275
    grp->gr_mem = NULL;
276
    if (*fieldsep != '\0') {
277
  grp->gr_mem = gr_mem = (char **)ALIGN(buf + inlen + 1);
278
  gr_end = (char **)((unsigned long)(buf + buflen) & ~ALIGNBYTES) - 1;
279
  for (;;) {
280
      if (gr_mem >= gr_end)
281
    debug_return_int(NSS_STR_PARSE_ERANGE); /* out of space! */
282
      *gr_mem++ = cp;
283
      if (fieldsep == NULL)
284
    break;
285
      if ((fieldsep = strchr(cp = fieldsep, ',')) != NULL)
286
    *fieldsep++ = '\0';
287
  }
288
  *gr_mem = NULL;
289
    }
290
    debug_return_int(NSS_STR_PARSE_SUCCESS);
291
}
292
293
static nss_status_t
294
process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm,
295
    int dynamic)
296
{
297
    const char *user = gbm->username;
298
    nss_status_t ret = NSS_NOTFOUND;
299
    nss_XbyY_buf_t *buf;
300
    struct group *grp;
301
    char **gr_mem;
302
    int error, i;
303
    debug_decl(process_cstr, SUDO_DEBUG_UTIL);
304
305
    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: parsing %.*s", __func__,
306
  inlen, instr);
307
308
    /* Hack to let us check whether the query was handled by nscd or us. */
309
    if (gbm->force_slow_way != 0)
310
  gbm->force_slow_way = 2;
311
312
    buf = _nss_XbyY_buf_alloc(sizeof(struct group), NSS_BUFLEN_GROUP);
313
    if (buf == NULL)
314
  debug_return_int(NSS_UNAVAIL);
315
316
    /* Parse groups file string -> struct group. */
317
    grp = buf->result;
318
    error = (*gbm->str2ent)(instr, inlen, grp, buf->buffer, buf->buflen);
319
    if (error != NSS_STR_PARSE_SUCCESS || grp->gr_mem == NULL)
320
  goto done;
321
322
    for (gr_mem = grp->gr_mem; *gr_mem != NULL; gr_mem++) {
323
  if (strcmp(*gr_mem, user) == 0) {
324
      const int numgids = MIN(gbm->numgids, gbm->maxgids);
325
326
      /* Append to gid_array unless gr_gid is a dupe. */
327
      for (i = 0; i < numgids; i++) {
328
    if (gbm->gid_array[i] == grp->gr_gid)
329
        goto done;      /* already present */
330
      }
331
      if (i == gbm->maxgids && dynamic) {
332
    GETGROUPS_T *tmp = reallocarray(gbm->gid_array, gbm->maxgids,
333
        2 * sizeof(GETGROUPS_T));
334
    if (tmp == NULL) {
335
        /* Out of memory, just return what we have. */
336
        dynamic = 0;
337
    } else {
338
        gbm->gid_array = tmp;
339
        gbm->maxgids <<= 1;
340
    }
341
      }
342
      /* Store gid if there is space. */
343
      if (i < gbm->maxgids)
344
    gbm->gid_array[i] = grp->gr_gid;
345
      /* Always increment numgids so we can detect when out of space. */
346
      gbm->numgids++;
347
      goto done;
348
  }
349
    }
350
done:
351
    _nss_XbyY_buf_free(buf);
352
    debug_return_int(ret);
353
}
354
355
static nss_status_t
356
process_cstr_static(const char *instr, int inlen, struct nss_groupsbymem *gbm)
357
{
358
    return process_cstr(instr, inlen, gbm, 0);
359
}
360
361
static nss_status_t
362
process_cstr_dynamic(const char *instr, int inlen, struct nss_groupsbymem *gbm)
363
{
364
    return process_cstr(instr, inlen, gbm, 1);
365
}
366
367
/*
368
 * Extended getgrouplist(3) using nss_search(3)
369
 */
370
int
371
sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
372
    GETGROUPS_T **groupsp, int *ngroupsp)
373
{
374
    struct nss_groupsbymem gbm;
375
    static DEFINE_NSS_DB_ROOT(db_root);
376
    debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
377
378
    memset(&gbm, 0, sizeof(gbm));
379
    gbm.username = name;
380
    gbm.gid_array = *groupsp;
381
    gbm.maxgids = *ngroupsp;
382
    gbm.numgids = 1; /* for basegid */
383
    gbm.force_slow_way = 1;
384
    gbm.str2ent = str2grp;
385
386
    if (gbm.gid_array == NULL) {
387
  /* Dynamically-sized group vector. */
388
  gbm.maxgids = (int)sysconf(_SC_NGROUPS_MAX);
389
  if (gbm.maxgids < 0)
390
      gbm.maxgids = NGROUPS_MAX;
391
  gbm.gid_array = reallocarray(NULL, gbm.maxgids, 4 * sizeof(GETGROUPS_T));
392
  if (gbm.gid_array == NULL)
393
      debug_return_int(-1);
394
  gbm.maxgids <<= 2;
395
  gbm.process_cstr = process_cstr_dynamic;
396
    } else {
397
  /* Static group vector. */
398
  if (gbm.maxgids <= 0)
399
      debug_return_int(-1);
400
  gbm.process_cstr = process_cstr_static;
401
    }
402
403
    /* We support BSD semantics where the first element is the base gid */
404
    gbm.gid_array[0] = basegid;
405
406
    /*
407
     * Can't use nss_search return value since it may return NSS_UNAVAIL
408
     * when no nsswitch.conf entry (e.g. compat mode).
409
     */
410
    for (;;) {
411
  GETGROUPS_T *tmp;
412
413
  (void)nss_search(&db_root, _nss_initf_group, NSS_DBOP_GROUP_BYMEMBER,
414
      &gbm);
415
416
  /*
417
   * If this was a statically-sized group vector or nscd was not used
418
   * we are done.
419
   */
420
  if (gbm.process_cstr != process_cstr_dynamic || gbm.force_slow_way == 2)
421
      break;
422
423
  /*
424
   * If gid_array is full and the query was handled by nscd, there
425
   * may be more data, so double gid_array and try again.
426
   */
427
  if (gbm.numgids != gbm.maxgids)
428
      break;
429
430
  tmp = reallocarray(gbm.gid_array, gbm.maxgids, 2 * sizeof(GETGROUPS_T));
431
  if (tmp == NULL) {
432
      free(gbm.gid_array);
433
      debug_return_int(-1);
434
  }
435
  gbm.gid_array = tmp;
436
  gbm.maxgids <<= 1;
437
    }
438
439
    /* Note: we can only detect a too-small group list if nscd is not used. */
440
    *groupsp = gbm.gid_array;
441
    if (gbm.numgids <= gbm.maxgids) {
442
        *ngroupsp = gbm.numgids;
443
  debug_return_int(0);
444
    }
445
    *ngroupsp = gbm.maxgids;
446
    debug_return_int(-1);
447
}
448
449
#else /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */
450
451
/*
452
 * Extended getgrouplist(3) using getgrent(3)
453
 */
454
int
455
sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid,
456
    GETGROUPS_T **groupsp, int *ngroupsp)
457
{
458
    GETGROUPS_T *groups = *groupsp;
459
    int grpsize = *ngroupsp;
460
    int i, ngroups = 1;
461
    int ret = -1;
462
    struct group *grp;
463
    debug_decl(sudo_getgrouplist2, SUDO_DEBUG_UTIL);
464
465
    if (groups == NULL) {
466
  /* Dynamically-sized group vector. */
467
  grpsize = (int)sysconf(_SC_NGROUPS_MAX);
468
  if (grpsize < 0)
469
      grpsize = NGROUPS_MAX;
470
  groups = reallocarray(NULL, grpsize, 4 * sizeof(*groups));
471
  if (groups == NULL)
472
      debug_return_int(-1);
473
  grpsize <<= 2;
474
    } else {
475
  /* Static group vector. */
476
  if (grpsize < 1)
477
      debug_return_int(-1);
478
    }
479
480
    /* We support BSD semantics where the first element is the base gid */
481
    groups[0] = basegid;
482
483
    setgrent();
484
    while ((grp = getgrent()) != NULL) {
485
  if (grp->gr_gid == basegid || grp->gr_mem == NULL)
486
      continue;
487
488
  for (i = 0; grp->gr_mem[i] != NULL; i++) {
489
      if (strcmp(name, grp->gr_mem[i]) == 0)
490
    break;
491
  }
492
  if (grp->gr_mem[i] == NULL)
493
      continue; /* user not found */
494
495
  /* Only add if it is not the same as an existing gid */
496
  for (i = 0; i < ngroups; i++) {
497
      if (grp->gr_gid == groups[i])
498
    break;
499
  }
500
  if (i == ngroups) {
501
      if (ngroups == grpsize) {
502
    GETGROUPS_T *tmp;
503
504
    if (*groupsp != NULL) {
505
        /* Static group vector. */
506
        goto done;
507
    }
508
    tmp = reallocarray(groups, grpsize, 2 * sizeof(*groups));
509
    if (tmp == NULL) {
510
        free(groups);
511
        groups = NULL;
512
        ngroups = 0;
513
        goto done;
514
    }
515
    groups = tmp;
516
    grpsize <<= 1;
517
      }
518
      groups[ngroups++] = grp->gr_gid;
519
  }
520
    }
521
    ret = 0;
522
523
done:
524
    endgrent();
525
    *groupsp = groups;
526
    *ngroupsp = ngroups;
527
528
    debug_return_int(ret);
529
}
530
#endif /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */