Coverage Report

Created: 2025-07-11 06:58

/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
0
#define EXPAND(p, f)  p ## _ ## f
49
0
#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
0
# define CALL(x)  x
55
0
# define PREFIX(x)  WRAP(sudo, x)
56
#endif
57
58
0
#define FIELD_SIZE(src, name, size)     \
59
0
do {             \
60
0
  if (src->name) {       \
61
0
    size = strlen(src->name) + 1;   \
62
0
    total += size;        \
63
0
  } else {                                        \
64
0
    size = 0;       \
65
0
  }                                               \
66
0
} while (0)
67
68
0
#define FIELD_COPY(src, dst, name, size)    \
69
0
do {             \
70
0
  if (src->name) {       \
71
0
    memcpy(cp, src->name, size);    \
72
0
    dst->name = cp;       \
73
0
    cp += size;       \
74
0
  }            \
75
0
} 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
0
{
87
0
    char *cp;
88
0
    const char *pw_shell;
89
0
    size_t nsize, psize, gsize, dsize, ssize, total;
90
#ifdef HAVE_LOGIN_CAP_H
91
    size_t csize;
92
#endif
93
0
    struct cache_item_pw *pwitem;
94
0
    struct passwd *pw, *newpw;
95
0
    debug_decl(sudo_make_pwitem, SUDOERS_DEBUG_NSS);
96
97
    /* Look up by name or uid. */
98
0
    pw = name ? CALL(getpwnam)(name) : CALL(getpwuid)(uid);
99
0
    if (pw == NULL) {
100
0
  errno = ENOENT;
101
0
  debug_return_ptr(NULL);
102
0
    }
103
104
    /* If shell field is empty, expand to _PATH_BSHELL. */
105
0
    pw_shell = (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
106
0
  ? _PATH_BSHELL : pw->pw_shell;
107
108
    /* Allocate in one big chunk for easy freeing. */
109
0
    total = sizeof(*pwitem);
110
0
    FIELD_SIZE(pw, pw_name, nsize);
111
0
    FIELD_SIZE(pw, pw_passwd, psize);
112
#ifdef HAVE_LOGIN_CAP_H
113
    FIELD_SIZE(pw, pw_class, csize);
114
#endif
115
0
    FIELD_SIZE(pw, pw_gecos, gsize);
116
0
    FIELD_SIZE(pw, pw_dir, dsize);
117
    /* Treat shell specially since we expand "" -> _PATH_BSHELL */
118
0
    ssize = strlen(pw_shell) + 1;
119
0
    total += ssize;
120
0
    if (name != NULL)
121
0
  total += strlen(name) + 1;
122
123
    /* Allocate space for struct item, struct passwd and the strings. */
124
0
    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
0
    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
0
    memcpy(newpw, pw, sizeof(*pw));
136
0
    cp = (char *)(pwitem + 1);
137
0
    FIELD_COPY(pw, newpw, pw_name, nsize);
138
0
    FIELD_COPY(pw, newpw, pw_passwd, psize);
139
#ifdef HAVE_LOGIN_CAP_H
140
    FIELD_COPY(pw, newpw, pw_class, csize);
141
#endif
142
0
    FIELD_COPY(pw, newpw, pw_gecos, gsize);
143
0
    FIELD_COPY(pw, newpw, pw_dir, dsize);
144
    /* Treat shell specially since we expand "" -> _PATH_BSHELL */
145
0
    memcpy(cp, pw_shell, ssize);
146
0
    newpw->pw_shell = cp;
147
0
    cp += ssize;
148
149
    /* Set key and datum. */
150
0
    if (name != NULL) {
151
0
  memcpy(cp, name, strlen(name) + 1);
152
0
  pwitem->cache.k.name = cp;
153
0
    } else {
154
0
  pwitem->cache.k.uid = pw->pw_uid;
155
0
    }
156
0
    pwitem->cache.d.pw = newpw;
157
0
    pwitem->cache.refcnt = 1;
158
159
0
    debug_return_ptr(&pwitem->cache);
160
0
}
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
0
{
172
0
    char *cp;
173
0
    size_t nsize, psize, total, len, nmem = 0;
174
0
    struct cache_item_gr *gritem;
175
0
    struct group *gr, *newgr;
176
0
    debug_decl(sudo_make_gritem, SUDOERS_DEBUG_NSS);
177
178
    /* Look up by name or gid. */
179
0
    gr = name ? CALL(getgrnam)(name) : CALL(getgrgid)(gid);
180
0
    if (gr == NULL) {
181
0
  errno = ENOENT;
182
0
  debug_return_ptr(NULL);
183
0
    }
184
185
    /* Allocate in one big chunk for easy freeing. */
186
0
    total = sizeof(*gritem);
187
0
    FIELD_SIZE(gr, gr_name, nsize);
188
0
    FIELD_SIZE(gr, gr_passwd, psize);
189
0
    if (gr->gr_mem) {
190
0
  for (nmem = 0; gr->gr_mem[nmem] != NULL; nmem++)
191
0
      total += strlen(gr->gr_mem[nmem]) + 1;
192
0
  nmem++;
193
0
  total += sizeof(char *) * nmem;
194
0
    }
195
0
    if (name != NULL)
196
0
  total += strlen(name) + 1;
197
198
0
    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
0
    newgr = &gritem->gr;
210
0
    memcpy(newgr, gr, sizeof(*gr));
211
0
    cp = (char *)(gritem + 1);
212
0
    if (gr->gr_mem) {
213
0
  newgr->gr_mem = (char **)cp;
214
0
  cp += sizeof(char *) * nmem;
215
0
  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
0
  newgr->gr_mem[nmem] = NULL;
222
0
    }
223
0
    FIELD_COPY(gr, newgr, gr_passwd, psize);
224
0
    FIELD_COPY(gr, newgr, gr_name, nsize);
225
226
    /* Set key and datum. */
227
0
    if (name != NULL) {
228
0
  memcpy(cp, name, strlen(name) + 1);
229
0
  gritem->cache.k.name = cp;
230
0
    } else {
231
0
  gritem->cache.k.gid = gr->gr_gid;
232
0
    }
233
0
    gritem->cache.d.gr = newgr;
234
0
    gritem->cache.refcnt = 1;
235
236
0
    debug_return_ptr(&gritem->cache);
237
0
}
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
18
{
246
18
    char *cp;
247
18
    size_t nsize, total;
248
18
    struct cache_item_gidlist *glitem;
249
18
    struct gid_list *gidlist;
250
18
    int i;
251
18
    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
18
    if (type != ENTRY_TYPE_QUERIED && (gids != NULL || gidstrs != NULL)) {
257
18
  if (gids == NULL) {
258
      /* Convert the supplied gids list from string format to gid_t. */
259
18
      ngids = 1;
260
66
      for (i = 0; gidstrs[i] != NULL; i++)
261
48
    ngids++;
262
18
      gids = reallocarray(NULL, (size_t)ngids, sizeof(GETGROUPS_T));
263
18
      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
18
      ngids = 1;
269
18
      gids[0] = pw->pw_gid;
270
66
      for (i = 0; gidstrs[i] != NULL; i++) {
271
48
    const char *errstr;
272
48
    GETGROUPS_T gid = (gid_t) sudo_strtoid(gidstrs[i], &errstr);
273
48
    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
48
    if (gid != gids[0])
279
30
        gids[ngids++] = gid;
280
48
      }
281
18
  }
282
18
  type = ENTRY_TYPE_FRONTEND;
283
18
    } else {
284
0
  type = ENTRY_TYPE_QUERIED;
285
0
  ngids = sudo_pwutil_get_max_groups();
286
0
  if (ngids > 0) {
287
0
      gids = reallocarray(NULL, (size_t)ngids, sizeof(GETGROUPS_T));
288
0
      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
0
      if (PREFIX(getgrouplist2)(pw->pw_name, pw->pw_gid, &gids, &ngids) == -1)
295
0
    ngids = sudo_pwutil_get_max_groups();
296
0
  } 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
0
    }
305
18
    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
18
    nsize = strlen(pw->pw_name) + 1;
313
18
    total = sizeof(*glitem) + nsize;
314
18
    total += sizeof(gid_t *) * (size_t)ngids;
315
316
18
    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
18
    gidlist = &glitem->gidlist;
329
18
    cp = (char *)(glitem + 1);
330
18
    gidlist->gids = (gid_t *)cp;
331
18
    cp += sizeof(gid_t) * (size_t)ngids;
332
333
    /* Set key and datum. */
334
18
    memcpy(cp, pw->pw_name, nsize);
335
18
    glitem->cache.k.name = cp;
336
18
    glitem->cache.d.gidlist = gidlist;
337
18
    glitem->cache.refcnt = 1;
338
18
    glitem->cache.type = type;
339
340
    /*
341
     * Store group IDs.
342
     */
343
66
    for (i = 0; i < ngids; i++)
344
48
  gidlist->gids[i] = gids[i];
345
18
    gidlist->ngids = ngids;
346
18
    free(gids);
347
348
18
    debug_return_ptr(&glitem->cache);
349
18
}
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
}