Coverage Report

Created: 2025-07-11 06:57

/src/sudo/plugins/sudoers/pwutil_impl.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 1996, 1998-2005, 2007-2021, 2023-2025
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
 * Sponsored in part by the Defense Advanced Research Projects
20
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
21
 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22
 */
23
24
/*
25
 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
26
 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
27
 */
28
29
#include <config.h>
30
31
#include <stddef.h>
32
#include <stdio.h>
33
#include <stdlib.h>
34
#include <string.h>
35
#include <unistd.h>
36
#include <errno.h>
37
#include <limits.h>
38
#include <pwd.h>
39
#include <grp.h>
40
41
#include <sudoers.h>
42
#include <pwutil.h>
43
44
/*
45
 * For testsudoers and cvtsudoers need to support building with a different
46
 * function prefix and using custom getpwnam/getpwuid/getgrnam/getgrgid.
47
 */
48
27.3k
#define EXPAND(p, f)  p ## _ ## f
49
27.3k
#define WRAP(p, f)  EXPAND(p, f)
50
#ifdef PWUTIL_PREFIX
51
# define CALL(x)  WRAP(PWUTIL_PREFIX, x)
52
# define PREFIX(x)  WRAP(PWUTIL_PREFIX, x)
53
#else
54
40.4k
# define CALL(x)  x
55
27.3k
# define PREFIX(x)  WRAP(sudo, x)
56
#endif
57
58
157k
#define FIELD_SIZE(src, name, size)     \
59
157k
do {             \
60
157k
  if (src->name) {       \
61
157k
    size = strlen(src->name) + 1;   \
62
157k
    total += size;        \
63
157k
  } else {                                        \
64
0
    size = 0;       \
65
0
  }                                               \
66
157k
} while (0)
67
68
157k
#define FIELD_COPY(src, dst, name, size)    \
69
157k
do {             \
70
157k
  if (src->name) {       \
71
157k
    memcpy(cp, src->name, size);    \
72
157k
    dst->name = cp;       \
73
157k
    cp += size;       \
74
157k
  }            \
75
157k
} while (0)
76
77
/*
78
 * Dynamically allocate space for a struct item plus the key and data
79
 * elements.  If name is non-NULL it is used as the key, else the
80
 * uid is the key.  Fills in datum from struct password.
81
 * Returns NULL on calloc error or unknown name/id, setting errno
82
 * to ENOMEM or ENOENT respectively.
83
 */
84
struct cache_item *
85
PREFIX(make_pwitem)(uid_t uid, const char *name)
86
39.7k
{
87
39.7k
    char *cp;
88
39.7k
    const char *pw_shell;
89
39.7k
    size_t nsize, psize, gsize, dsize, ssize, total;
90
#ifdef HAVE_LOGIN_CAP_H
91
    size_t csize;
92
#endif
93
39.7k
    struct cache_item_pw *pwitem;
94
39.7k
    struct passwd *pw, *newpw;
95
39.7k
    debug_decl(sudo_make_pwitem, SUDOERS_DEBUG_NSS);
96
97
    /* Look up by name or uid. */
98
39.7k
    pw = name ? CALL(getpwnam)(name) : CALL(getpwuid)(uid);
99
39.7k
    if (pw == NULL) {
100
380
  errno = ENOENT;
101
380
  debug_return_ptr(NULL);
102
380
    }
103
104
    /* If shell field is empty, expand to _PATH_BSHELL. */
105
39.3k
    pw_shell = (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
106
39.3k
  ? _PATH_BSHELL : pw->pw_shell;
107
108
    /* Allocate in one big chunk for easy freeing. */
109
39.3k
    total = sizeof(*pwitem);
110
39.3k
    FIELD_SIZE(pw, pw_name, nsize);
111
39.3k
    FIELD_SIZE(pw, pw_passwd, psize);
112
#ifdef HAVE_LOGIN_CAP_H
113
    FIELD_SIZE(pw, pw_class, csize);
114
#endif
115
39.3k
    FIELD_SIZE(pw, pw_gecos, gsize);
116
39.3k
    FIELD_SIZE(pw, pw_dir, dsize);
117
    /* Treat shell specially since we expand "" -> _PATH_BSHELL */
118
39.3k
    ssize = strlen(pw_shell) + 1;
119
39.3k
    total += ssize;
120
39.3k
    if (name != NULL)
121
39.1k
  total += strlen(name) + 1;
122
123
    /* Allocate space for struct item, struct passwd and the strings. */
124
39.3k
    if ((pwitem = calloc(1, total)) == NULL) {
125
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
126
0
      "unable to allocate memory");
127
0
  debug_return_ptr(NULL);
128
0
    }
129
39.3k
    newpw = &pwitem->pw;
130
131
    /*
132
     * Copy in passwd contents and make strings relative to space
133
     * at the end of the struct.
134
     */
135
39.3k
    memcpy(newpw, pw, sizeof(*pw));
136
39.3k
    cp = (char *)(pwitem + 1);
137
39.3k
    FIELD_COPY(pw, newpw, pw_name, nsize);
138
39.3k
    FIELD_COPY(pw, newpw, pw_passwd, psize);
139
#ifdef HAVE_LOGIN_CAP_H
140
    FIELD_COPY(pw, newpw, pw_class, csize);
141
#endif
142
39.3k
    FIELD_COPY(pw, newpw, pw_gecos, gsize);
143
39.3k
    FIELD_COPY(pw, newpw, pw_dir, dsize);
144
    /* Treat shell specially since we expand "" -> _PATH_BSHELL */
145
39.3k
    memcpy(cp, pw_shell, ssize);
146
39.3k
    newpw->pw_shell = cp;
147
39.3k
    cp += ssize;
148
149
    /* Set key and datum. */
150
39.3k
    if (name != NULL) {
151
39.1k
  memcpy(cp, name, strlen(name) + 1);
152
39.1k
  pwitem->cache.k.name = cp;
153
39.1k
    } else {
154
250
  pwitem->cache.k.uid = pw->pw_uid;
155
250
    }
156
39.3k
    pwitem->cache.d.pw = newpw;
157
39.3k
    pwitem->cache.refcnt = 1;
158
159
39.3k
    debug_return_ptr(&pwitem->cache);
160
39.3k
}
161
162
/*
163
 * Dynamically allocate space for a struct item plus the key and data
164
 * elements.  If name is non-NULL it is used as the key, else the
165
 * gid is the key.  Fills in datum from struct group.
166
 * Returns NULL on calloc error or unknown name/id, setting errno
167
 * to ENOMEM or ENOENT respectively.
168
 */
169
struct cache_item *
170
PREFIX(make_gritem)(gid_t gid, const char *name)
171
712
{
172
712
    char *cp;
173
712
    size_t nsize, psize, total, len, nmem = 0;
174
712
    struct cache_item_gr *gritem;
175
712
    struct group *gr, *newgr;
176
712
    debug_decl(sudo_make_gritem, SUDOERS_DEBUG_NSS);
177
178
    /* Look up by name or gid. */
179
712
    gr = name ? CALL(getgrnam)(name) : CALL(getgrgid)(gid);
180
712
    if (gr == NULL) {
181
501
  errno = ENOENT;
182
501
  debug_return_ptr(NULL);
183
501
    }
184
185
    /* Allocate in one big chunk for easy freeing. */
186
211
    total = sizeof(*gritem);
187
211
    FIELD_SIZE(gr, gr_name, nsize);
188
211
    FIELD_SIZE(gr, gr_passwd, psize);
189
211
    if (gr->gr_mem) {
190
211
  for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++)
191
0
      total += strlen(gr->gr_mem[nmem]) + 1;
192
211
  nmem++;
193
211
  total += sizeof(char *) * nmem;
194
211
    }
195
211
    if (name != NULL)
196
50
  total += strlen(name) + 1;
197
198
211
    if ((gritem = calloc(1, total)) == NULL) {
199
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
200
0
      "unable to allocate memory");
201
0
  debug_return_ptr(NULL);
202
0
    }
203
204
    /*
205
     * Copy in group contents and make strings relative to space
206
     * at the end of the buffer.  Note that gr_mem must come
207
     * immediately after struct group to guarantee proper alignment.
208
     */
209
211
    newgr = &gritem->gr;
210
211
    memcpy(newgr, gr, sizeof(*gr));
211
211
    cp = (char *)(gritem + 1);
212
211
    if (gr->gr_mem) {
213
211
  newgr->gr_mem = (char **)cp;
214
211
  cp += sizeof(char *) * nmem;
215
211
  for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++) {
216
0
      len = strlen(gr->gr_mem[nmem]) + 1;
217
0
      memcpy(cp, gr->gr_mem[nmem], len);
218
0
      newgr->gr_mem[nmem] = cp;
219
0
      cp += len;
220
0
  }
221
211
  newgr->gr_mem[nmem] = NULL;
222
211
    }
223
211
    FIELD_COPY(gr, newgr, gr_passwd, psize);
224
211
    FIELD_COPY(gr, newgr, gr_name, nsize);
225
226
    /* Set key and datum. */
227
211
    if (name != NULL) {
228
50
  memcpy(cp, name, strlen(name) + 1);
229
50
  gritem->cache.k.name = cp;
230
161
    } else {
231
161
  gritem->cache.k.gid = gr->gr_gid;
232
161
    }
233
211
    gritem->cache.d.gr = newgr;
234
211
    gritem->cache.refcnt = 1;
235
236
211
    debug_return_ptr(&gritem->cache);
237
211
}
238
239
/*
240
 * Dynamically allocate space for a struct item plus the key and data elements.
241
 */
242
struct cache_item *
243
PREFIX(make_gidlist_item)(const struct passwd *pw, int ngids, GETGROUPS_T *gids,
244
    char * const *gidstrs, unsigned int type)
245
28.4k
{
246
28.4k
    char *cp;
247
28.4k
    size_t nsize, total;
248
28.4k
    struct cache_item_gidlist *glitem;
249
28.4k
    struct gid_list *gidlist;
250
28.4k
    int i;
251
28.4k
    debug_decl(sudo_make_gidlist_item, SUDOERS_DEBUG_NSS);
252
253
    /*
254
     * Ignore supplied gids if the entry type says we must query the group db.
255
     */
256
28.4k
    if (type != ENTRY_TYPE_QUERIED && (gids != NULL || gidstrs != NULL)) {
257
1.13k
  if (gids == NULL) {
258
      /* Convert the supplied gids list from string format to gid_t. */
259
0
      ngids = 1;
260
0
      for (i = 0; gidstrs[i] != NULL; i++)
261
0
    ngids++;
262
0
      gids = reallocarray(NULL, (size_t)ngids, sizeof(GETGROUPS_T));
263
0
      if (gids == NULL) {
264
0
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
265
0
        "unable to allocate memory");
266
0
    debug_return_ptr(NULL);
267
0
      }
268
0
      ngids = 1;
269
0
      gids[0] = pw->pw_gid;
270
0
      for (i = 0; gidstrs[i] != NULL; i++) {
271
0
    const char *errstr;
272
0
    GETGROUPS_T gid = (gid_t) sudo_strtoid(gidstrs[i], &errstr);
273
0
    if (errstr != NULL) {
274
0
        sudo_debug_printf(SUDO_DEBUG_DIAG|SUDO_DEBUG_LINENO,
275
0
      "gid %s %s", gidstrs[i], errstr);
276
0
        continue;
277
0
    }
278
0
    if (gid != gids[0])
279
0
        gids[ngids++] = gid;
280
0
      }
281
0
  }
282
1.13k
  type = ENTRY_TYPE_FRONTEND;
283
27.3k
    } else {
284
27.3k
  type = ENTRY_TYPE_QUERIED;
285
27.3k
  ngids = sudo_pwutil_get_max_groups();
286
27.3k
  if (ngids > 0) {
287
27.3k
      gids = reallocarray(NULL, (size_t)ngids, sizeof(GETGROUPS_T));
288
27.3k
      if (gids == NULL) {
289
0
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
290
0
        "unable to allocate memory");
291
0
    debug_return_ptr(NULL);
292
0
      }
293
      /* Clamp to max_groups if insufficient space for all groups. */
294
27.3k
      if (PREFIX(getgrouplist2)(pw->pw_name, pw->pw_gid, &gids, &ngids) == -1)
295
0
    ngids = sudo_pwutil_get_max_groups();
296
27.3k
  } else {
297
0
      gids = NULL;
298
0
      if (PREFIX(getgrouplist2)(pw->pw_name, pw->pw_gid, &gids, &ngids) == -1) {
299
0
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
300
0
        "unable to allocate memory");
301
0
    debug_return_ptr(NULL);
302
0
      }
303
0
  }
304
27.3k
    }
305
28.4k
    if (ngids <= 0) {
306
0
  free(gids);
307
0
  errno = ENOENT;
308
0
  debug_return_ptr(NULL);
309
0
    }
310
311
    /* Allocate in one big chunk for easy freeing. */
312
28.4k
    nsize = strlen(pw->pw_name) + 1;
313
28.4k
    total = sizeof(*glitem) + nsize;
314
28.4k
    total += sizeof(gid_t *) * (size_t)ngids;
315
316
28.4k
    if ((glitem = calloc(1, total)) == NULL) {
317
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
318
0
      "unable to allocate memory");
319
0
  free(gids);
320
0
  debug_return_ptr(NULL);
321
0
    }
322
323
    /*
324
     * Copy in group list and make pointers relative to space
325
     * at the end of the buffer.  Note that the groups array must come
326
     * immediately after struct group to guarantee proper alignment.
327
     */
328
28.4k
    gidlist = &glitem->gidlist;
329
28.4k
    cp = (char *)(glitem + 1);
330
28.4k
    gidlist->gids = (gid_t *)cp;
331
28.4k
    cp += sizeof(gid_t) * (size_t)ngids;
332
333
    /* Set key and datum. */
334
28.4k
    memcpy(cp, pw->pw_name, nsize);
335
28.4k
    glitem->cache.k.name = cp;
336
28.4k
    glitem->cache.d.gidlist = gidlist;
337
28.4k
    glitem->cache.refcnt = 1;
338
28.4k
    glitem->cache.type = type;
339
340
    /*
341
     * Store group IDs.
342
     */
343
855k
    for (i = 0; i < ngids; i++)
344
826k
  gidlist->gids[i] = gids[i];
345
28.4k
    gidlist->ngids = ngids;
346
28.4k
    free(gids);
347
348
28.4k
    debug_return_ptr(&glitem->cache);
349
28.4k
}
350
351
/*
352
 * Dynamically allocate space for a struct item plus the key and data
353
 * elements.  Fills in group names from a call to sudo_get_gidlist().
354
 */
355
struct cache_item *
356
PREFIX(make_grlist_item)(const struct passwd *pw, char * const *unused1)
357
0
{
358
0
    const size_t groupname_len = sudo_login_name_max();
359
0
    size_t len, ngroups, nsize, total;
360
0
    struct cache_item_grlist *grlitem;
361
0
    struct group_list *grlist;
362
0
    struct gid_list *gidlist;
363
0
    struct group *grp = NULL;
364
0
    char *cp;
365
0
    int i;
366
0
    debug_decl(sudo_make_grlist_item, SUDOERS_DEBUG_NSS);
367
368
0
    gidlist = sudo_get_gidlist(pw, ENTRY_TYPE_ANY);
369
0
    if (gidlist == NULL) {
370
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
371
0
      "no gid list for use %s", pw->pw_name);
372
0
  errno = ENOENT;
373
0
  debug_return_ptr(NULL);
374
0
    }
375
376
    /* Allocate in one big chunk for easy freeing. */
377
0
    nsize = strlen(pw->pw_name) + 1;
378
0
    total = sizeof(*grlitem) + nsize;
379
0
    total += sizeof(char *) * (size_t)gidlist->ngids;
380
0
    total += groupname_len * (size_t)gidlist->ngids;
381
382
0
again:
383
0
    if ((grlitem = calloc(1, total)) == NULL) {
384
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
385
0
      "unable to allocate memory");
386
0
  sudo_gidlist_delref(gidlist);
387
0
  debug_return_ptr(NULL);
388
0
    }
389
390
    /*
391
     * Copy in group list and make pointers relative to space
392
     * at the end of the buffer.  Note that the groups array must come
393
     * immediately after struct group to guarantee proper alignment.
394
     */
395
0
    grlist = &grlitem->grlist;
396
0
    cp = (char *)(grlitem + 1);
397
0
    grlist->groups = (char **)cp;
398
0
    cp += sizeof(char *) * (size_t)gidlist->ngids;
399
400
    /* Set key and datum. */
401
0
    memcpy(cp, pw->pw_name, nsize);
402
0
    grlitem->cache.k.name = cp;
403
0
    grlitem->cache.d.grlist = grlist;
404
0
    grlitem->cache.refcnt = 1;
405
0
    cp += nsize;
406
407
    /*
408
     * Resolve and store group names by ID.
409
     */
410
#ifdef HAVE_SETAUTHDB
411
    if (grp == NULL)
412
  aix_setauthdb((char *) pw->pw_name, NULL);
413
#endif
414
0
    ngroups = 0;
415
0
    for (i = 0; i < gidlist->ngids; i++) {
416
0
  if ((grp = sudo_getgrgid(gidlist->gids[i])) != NULL) {
417
0
      len = strlen(grp->gr_name) + 1;
418
0
      if ((size_t)(cp - (char *)grlitem) + len > total) {
419
0
    total += len + groupname_len;
420
0
    free(grlitem);
421
0
    sudo_gr_delref(grp);
422
0
    goto again;
423
0
      }
424
0
      memcpy(cp, grp->gr_name, len);
425
0
      grlist->groups[ngroups++] = cp;
426
0
      cp += len;
427
0
      sudo_gr_delref(grp);
428
0
  }
429
0
    }
430
0
    grlist->ngroups = (int)ngroups;
431
0
    sudo_gidlist_delref(gidlist);
432
433
#ifdef HAVE_SETAUTHDB
434
    aix_restoreauthdb();
435
#endif
436
437
0
    debug_return_ptr(&grlitem->cache);
438
0
}
439
440
/*
441
 * Returns true if the specified shell is allowed by /etc/shells, else false.
442
 */
443
bool
444
PREFIX(valid_shell)(const char *shell)
445
0
{
446
0
    const char *entry;
447
0
    debug_decl(valid_shell, SUDOERS_DEBUG_NSS);
448
449
0
    sudo_debug_printf(SUDO_DEBUG_INFO,
450
0
  "%s: checking /etc/shells for %s", __func__, shell);
451
452
0
    CALL(setusershell)();
453
0
    while ((entry = CALL(getusershell)()) != NULL) {
454
0
  if (strcmp(entry, shell) == 0)
455
0
      debug_return_bool(true);
456
0
    }
457
0
    CALL(endusershell)();
458
459
0
    debug_return_bool(false);
460
0
}