Coverage Report

Created: 2025-10-13 06:08

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