Coverage Report

Created: 2026-01-16 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/plugins/sudoers/alias.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2004-2005, 2007-2021, 2023
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
#include <config.h>
21
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <errno.h>
26
27
#include <sudoers.h>
28
#include <redblack.h>
29
#include <gram.h>
30
31
/*
32
 * Comparison function for the red-black tree.
33
 * Aliases are sorted by name with the type used as a tie-breaker.
34
 */
35
static int
36
alias_compare(const void *v1, const void *v2)
37
0
{
38
0
    const struct alias *a1 = (const struct alias *)v1;
39
0
    const struct alias *a2 = (const struct alias *)v2;
40
0
    int res;
41
0
    debug_decl(alias_compare, SUDOERS_DEBUG_ALIAS);
42
43
0
    if (a1 == NULL)
44
0
  res = -1;
45
0
    else if (a2 == NULL)
46
0
  res = 1;
47
0
    else if ((res = strcmp(a1->name, a2->name)) == 0)
48
0
  res = a1->type - a2->type;
49
0
    debug_return_int(res);
50
0
}
51
52
/*
53
 * Search the tree for an alias with the specified name and type.
54
 * Returns a pointer to the alias structure or NULL if not found.
55
 * Caller is responsible for calling alias_put() on the returned
56
 * alias to mark it as unused.
57
 */
58
struct alias *
59
alias_get(const struct sudoers_parse_tree *parse_tree, const char *name,
60
    short type)
61
0
{
62
0
    struct alias key;
63
0
    struct rbnode *node;
64
0
    struct alias *a = NULL;
65
0
    debug_decl(alias_get, SUDOERS_DEBUG_ALIAS);
66
67
0
    if (parse_tree->aliases == NULL)
68
0
  debug_return_ptr(NULL);
69
70
0
    key.name = (char *)name;
71
0
    key.type = type;
72
0
    if ((node = rbfind(parse_tree->aliases, &key)) != NULL) {
73
  /*
74
   * Check whether this alias is already in use.
75
   * If so, we've detected a loop.  If not, set the flag,
76
   * which the caller should clear with a call to alias_put().
77
   */
78
0
  a = node->data;
79
0
  if (a->used) {
80
0
      errno = ELOOP;
81
0
      debug_return_ptr(NULL);
82
0
  }
83
0
  a->used = true;
84
0
    } else {
85
0
  errno = ENOENT;
86
0
    }
87
0
    debug_return_ptr(a);
88
0
}
89
90
/*
91
 * Clear the "used" flag in an alias once the caller is done with it.
92
 */
93
void
94
alias_put(struct alias *a)
95
0
{
96
0
    debug_decl(alias_put, SUDOERS_DEBUG_ALIAS);
97
0
    a->used = false;
98
0
    debug_return;
99
0
}
100
101
/*
102
 * Add an alias to the aliases redblack tree.
103
 * Note that "file" must be a reference-counted string.
104
 * Returns true on success and false on failure, setting errno.
105
 */
106
bool
107
alias_add(struct sudoers_parse_tree *parse_tree, char *name,
108
    short type, char *file, int line, int column,
109
    struct member *members)
110
0
{
111
0
    struct alias *a;
112
0
    debug_decl(alias_add, SUDOERS_DEBUG_ALIAS);
113
114
0
    if (parse_tree->aliases == NULL) {
115
0
  if ((parse_tree->aliases = alloc_aliases()) == NULL)
116
0
      debug_return_bool(false);
117
0
    }
118
119
0
    a = calloc(1, sizeof(*a));
120
0
    if (a == NULL)
121
0
  debug_return_bool(false);
122
123
    /* Only set elements used by alias_compare() in case there is a dupe. */
124
0
    a->name = name;
125
0
    a->type = type;
126
0
    switch (rbinsert(parse_tree->aliases, a, NULL)) {
127
0
    case 1:
128
0
  free(a);
129
0
  errno = EEXIST;
130
0
  debug_return_bool(false);
131
0
    case -1:
132
0
  free(a);
133
0
  debug_return_bool(false);
134
0
    }
135
136
    /*
137
     * It is now safe to fill in the rest of the alias.  We do this last
138
     * since it modifies "file" (adds a ref) and "members" (tailq conversion).
139
     */
140
    /* a->used = false; */
141
0
    a->file = sudo_rcstr_addref(file);
142
0
    a->line = line;
143
0
    a->column = column;
144
0
    HLTQ_TO_TAILQ(&a->members, members, entries);
145
0
    debug_return_bool(true);
146
0
}
147
148
/*
149
 * Closure to adapt 2-arg rbapply() to 3-arg alias_apply().
150
 */
151
struct alias_apply_closure {
152
    struct sudoers_parse_tree *parse_tree;
153
    int (*func)(struct sudoers_parse_tree *, struct alias *, void *);
154
    void *cookie;
155
};
156
157
/* Adapt rbapply() to alias_apply() calling convention. */
158
static int
159
alias_apply_func(void *v1, void *v2)
160
0
{
161
0
    struct alias *a = v1;
162
0
    struct alias_apply_closure *closure = v2;
163
164
0
    return closure->func(closure->parse_tree, a, closure->cookie);
165
0
}
166
167
/*
168
 * Apply a function to each alias entry and pass in a cookie.
169
 */
170
bool
171
alias_apply(struct sudoers_parse_tree *parse_tree,
172
    int (*func)(struct sudoers_parse_tree *, struct alias *, void *),
173
    void *cookie)
174
0
{
175
0
    struct alias_apply_closure closure;
176
0
    bool ret = true;
177
0
    debug_decl(alias_apply, SUDOERS_DEBUG_ALIAS);
178
179
0
    if (parse_tree->aliases != NULL) {
180
0
  closure.parse_tree = parse_tree;
181
0
  closure.func = func;
182
0
  closure.cookie = cookie;
183
0
  if (rbapply(parse_tree->aliases, alias_apply_func, &closure, inorder) != 0)
184
0
      ret = false;
185
0
    }
186
187
0
    debug_return_bool(ret);
188
0
}
189
190
/*
191
 * Returns true if there are no aliases in the parse_tree, else false.
192
 */
193
bool
194
no_aliases(const struct sudoers_parse_tree *parse_tree)
195
0
{
196
0
    debug_decl(no_aliases, SUDOERS_DEBUG_ALIAS);
197
0
    debug_return_bool(parse_tree->aliases == NULL ||
198
0
  rbisempty(parse_tree->aliases));
199
0
}
200
201
/*
202
 * Free memory used by an alias struct and its members.
203
 */
204
void
205
alias_free(void *v)
206
0
{
207
0
    struct alias *a = (struct alias *)v;
208
0
    debug_decl(alias_free, SUDOERS_DEBUG_ALIAS);
209
210
0
    if (a != NULL) {
211
0
  free(a->name);
212
0
  sudo_rcstr_delref(a->file);
213
0
  free_members(&a->members);
214
0
  free(a);
215
0
    }
216
217
0
    debug_return;
218
0
}
219
220
/*
221
 * Find the named alias, remove it from the tree and return it.
222
 */
223
struct alias *
224
alias_remove(struct sudoers_parse_tree *parse_tree, const char *name,
225
    short type)
226
0
{
227
0
    struct rbnode *node;
228
0
    struct alias key;
229
0
    debug_decl(alias_remove, SUDOERS_DEBUG_ALIAS);
230
231
0
    if (parse_tree->aliases != NULL) {
232
0
  key.name = (char *)name;
233
0
  key.type = type;
234
0
  if ((node = rbfind(parse_tree->aliases, &key)) != NULL)
235
0
      debug_return_ptr(rbdelete(parse_tree->aliases, node));
236
0
    }
237
0
    errno = ENOENT;
238
0
    debug_return_ptr(NULL);
239
0
}
240
241
struct rbtree *
242
alloc_aliases(void)
243
0
{
244
0
    debug_decl(alloc_aliases, SUDOERS_DEBUG_ALIAS);
245
246
0
    debug_return_ptr(rbcreate(alias_compare));
247
0
}
248
249
void
250
free_aliases(struct rbtree *aliases)
251
44.4k
{
252
44.4k
    debug_decl(free_aliases, SUDOERS_DEBUG_ALIAS);
253
254
44.4k
    if (aliases != NULL)
255
0
  rbdestroy(aliases, alias_free);
256
44.4k
}
257
258
const char *
259
alias_type_to_string(short alias_type)
260
0
{
261
0
    return alias_type == HOSTALIAS ? "Host_Alias" :
262
0
  alias_type == CMNDALIAS ? "Cmnd_Alias" :
263
0
  alias_type == USERALIAS ? "User_Alias" :
264
0
  alias_type == RUNASALIAS ? "Runas_Alias" :
265
0
  "Invalid_Alias";
266
0
}
267
268
/*
269
 * Remove the alias of the specified type as well as any other aliases
270
 * referenced by that alias.  Stores removed aliases in a freelist.
271
 */
272
static bool
273
alias_remove_recursive(struct sudoers_parse_tree *parse_tree, char *name,
274
    short type, struct rbtree *freelist)
275
0
{
276
0
    struct member *m;
277
0
    struct alias *a;
278
0
    bool ret = true;
279
0
    debug_decl(alias_remove_recursive, SUDOERS_DEBUG_ALIAS);
280
281
0
    if ((a = alias_remove(parse_tree, name, type)) != NULL) {
282
0
  TAILQ_FOREACH(m, &a->members, entries) {
283
0
      if (m->type == ALIAS) {
284
0
    if (!alias_remove_recursive(parse_tree, m->name, type, freelist))
285
0
        ret = false;
286
0
      }
287
0
  }
288
0
  if (rbinsert(freelist, a, NULL) != 0)
289
0
      ret = false;
290
0
    }
291
0
    debug_return_bool(ret);
292
0
}
293
294
static int
295
alias_find_used_members(struct sudoers_parse_tree *parse_tree,
296
    struct member_list *members, short atype, struct rbtree *used_aliases)
297
0
{
298
0
    struct member *m;
299
0
    int errors = 0;
300
0
    debug_decl(alias_find_used_members, SUDOERS_DEBUG_ALIAS);
301
302
0
    if (members != NULL) {
303
0
  TAILQ_FOREACH(m, members, entries) {
304
0
      if (m->type != ALIAS)
305
0
    continue;
306
0
      if (!alias_remove_recursive(parse_tree, m->name, atype, used_aliases))
307
0
    errors++;
308
0
  }
309
0
    }
310
311
0
    debug_return_int(errors);
312
0
}
313
314
/*
315
 * Move all aliases referenced by userspecs to used_aliases.
316
 */
317
bool
318
alias_find_used(struct sudoers_parse_tree *parse_tree, struct rbtree *used_aliases)
319
0
{
320
0
    struct privilege *priv;
321
0
    struct userspec *us;
322
0
    struct cmndspec *cs;
323
0
    struct defaults *d;
324
0
    struct member *m;
325
0
    int errors = 0;
326
0
    debug_decl(alias_find_used, SUDOERS_DEBUG_ALIAS);
327
328
    /* Move referenced aliases to used_aliases. */
329
0
    TAILQ_FOREACH(us, &parse_tree->userspecs, entries) {
330
0
  errors += alias_find_used_members(parse_tree, &us->users,
331
0
      USERALIAS, used_aliases);
332
0
  TAILQ_FOREACH(priv, &us->privileges, entries) {
333
0
      errors += alias_find_used_members(parse_tree, &priv->hostlist,
334
0
    HOSTALIAS, used_aliases);
335
0
      TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
336
0
    errors += alias_find_used_members(parse_tree, cs->runasuserlist,
337
0
        RUNASALIAS, used_aliases);
338
0
    errors += alias_find_used_members(parse_tree, cs->runasgrouplist,
339
0
        RUNASALIAS, used_aliases);
340
0
    if ((m = cs->cmnd)->type == ALIAS) {
341
0
        if (!alias_remove_recursive(parse_tree, m->name, CMNDALIAS,
342
0
      used_aliases))
343
0
      errors++;
344
0
    }
345
0
      }
346
0
  }
347
0
    }
348
0
    TAILQ_FOREACH(d, &parse_tree->defaults, entries) {
349
0
  switch (d->type) {
350
0
      case DEFAULTS_HOST:
351
0
    errors += alias_find_used_members(parse_tree,
352
0
        &d->binding->members, HOSTALIAS, used_aliases);
353
0
    break;
354
0
      case DEFAULTS_USER:
355
0
    errors += alias_find_used_members(parse_tree,
356
0
        &d->binding->members, USERALIAS, used_aliases);
357
0
    break;
358
0
      case DEFAULTS_RUNAS:
359
0
    errors += alias_find_used_members(parse_tree,
360
0
        &d->binding->members, RUNASALIAS, used_aliases);
361
0
    break;
362
0
      case DEFAULTS_CMND:
363
0
    errors += alias_find_used_members(parse_tree,
364
0
        &d->binding->members, CMNDALIAS, used_aliases);
365
0
    break;
366
0
      default:
367
0
    break;
368
0
  }
369
0
    }
370
371
0
    debug_return_bool(errors ? false : true);
372
0
}