Coverage Report

Created: 2025-10-13 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/plugins/sudoers/toke_util.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 1996, 1998-2005, 2007-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 <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <regex.h>
30
31
#include <sudoers.h>
32
#include <toke.h>
33
#include <gram.h>
34
35
static size_t arg_len = 0;
36
static size_t arg_size = 0;
37
38
/*
39
 * Copy the string and collapse any escaped characters.
40
 * Requires that dst have at least len + 1 bytes free.
41
 */
42
static void
43
copy_string(char *dst, const char *src, size_t len)
44
88.4k
{
45
88.4k
    const char *end = src + len;
46
88.4k
    debug_decl(copy_string, SUDOERS_DEBUG_PARSER);
47
48
1.26M
    while (src < end) {
49
1.17M
  int ch = *src++;
50
1.17M
  if (ch == '\\' && src < end) {
51
37
      if (*src == 'x' && src + 3 <= end && (ch = sudo_hexchar(src + 1)) != -1) {
52
    /* Hex character, skip remaining part of src. */
53
0
    src += 3;
54
37
      } else {
55
    /* Escaped regular character. */
56
37
    ch = *src++;
57
37
      }
58
37
  }
59
1.17M
  *dst++ = (char)ch;
60
1.17M
    }
61
88.4k
    *dst = '\0';
62
63
88.4k
    debug_return;
64
88.4k
}
65
66
bool
67
fill(const char *src, int ilen)
68
88.4k
{
69
88.4k
    const size_t len = (size_t)ilen;
70
88.4k
    char *dst;
71
88.4k
    debug_decl(fill, SUDOERS_DEBUG_PARSER);
72
73
88.4k
    dst = malloc(len + 1);
74
88.4k
    if (dst == NULL) {
75
0
  sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
76
0
  sudoerserror(NULL);
77
0
  debug_return_bool(false);
78
0
    }
79
88.4k
    parser_leak_add(LEAK_PTR, dst);
80
88.4k
    copy_string(dst, src, len);
81
88.4k
    sudoerslval.string = dst;
82
83
88.4k
    debug_return_bool(true);
84
88.4k
}
85
86
bool
87
append(const char *src, int ilen)
88
0
{
89
0
    const size_t len = (size_t)ilen;
90
0
    size_t olen = 0;
91
0
    char *dst;
92
0
    debug_decl(append, SUDOERS_DEBUG_PARSER);
93
94
0
    if (sudoerslval.string != NULL) {
95
0
  olen = strlen(sudoerslval.string);
96
0
  parser_leak_remove(LEAK_PTR, sudoerslval.string);
97
0
    }
98
99
0
    dst = realloc(sudoerslval.string, olen + len + 1);
100
0
    if (dst == NULL) {
101
  /* realloc failure, avoid leaking original */
102
0
  free(sudoerslval.string);
103
0
  sudoerslval.string = NULL;
104
0
  sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
105
0
  sudoerserror(NULL);
106
0
  debug_return_bool(false);
107
0
    }
108
0
    parser_leak_add(LEAK_PTR, dst);
109
0
    copy_string(dst + olen, src, len);
110
0
    sudoerslval.string = dst;
111
112
0
    debug_return_bool(true);
113
0
}
114
115
#define SPECIAL(c) \
116
0
    ((c) == ',' || (c) == ':' || (c) == '=' || (c) == ' ' || (c) == '\t' || (c) == '#')
117
118
bool
119
fill_cmnd(const char *src, int ilen)
120
258
{
121
258
    const size_t len = (size_t)ilen;
122
258
    char *dst;
123
258
    size_t i;
124
258
    debug_decl(fill_cmnd, SUDOERS_DEBUG_PARSER);
125
126
258
    arg_len = arg_size = 0;
127
128
258
    dst = sudoerslval.command.cmnd = malloc(len + 1);
129
258
    if (dst == NULL) {
130
0
  sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
131
0
  sudoerserror(NULL);
132
0
  debug_return_bool(false);
133
0
    }
134
258
    sudoerslval.command.args = NULL;
135
136
258
    if (src[0] == '^') {
137
  /* Copy the regular expression, no escaped sudo-specific characters. */
138
3
  memcpy(dst, src, len);
139
3
  dst[len] = '\0';
140
255
    } else {
141
  /* Copy the string and collapse any escaped sudo-specific characters. */
142
1.07k
  for (i = 0; i < len; i++) {
143
816
      if (src[i] == '\\' && i != len - 1 && SPECIAL(src[i + 1]))
144
0
    *dst++ = src[++i];
145
816
      else
146
816
    *dst++ = src[i];
147
816
  }
148
255
  *dst = '\0';
149
150
  /* Check for sudoedit specified as a fully-qualified path. */
151
255
  if ((dst = strrchr(sudoerslval.command.cmnd, '/')) != NULL) { // -V575
152
255
      if (strcmp(dst, "/sudoedit") == 0) {
153
0
    if (sudoers_strict()) {
154
0
        sudoerserror(
155
0
      N_("sudoedit should not be specified with a path"));
156
0
    }
157
0
    free(sudoerslval.command.cmnd);
158
0
    if ((sudoerslval.command.cmnd = strdup("sudoedit")) == NULL) {
159
0
        sudo_warnx(U_("%s: %s"), __func__,
160
0
      U_("unable to allocate memory"));
161
0
        debug_return_bool(false);
162
0
    }
163
0
      }
164
255
  }
165
255
    }
166
167
258
    parser_leak_add(LEAK_PTR, sudoerslval.command.cmnd);
168
258
    debug_return_bool(true);
169
258
}
170
171
bool
172
fill_args(const char *s, int ilen, bool addspace)
173
222k
{
174
222k
    size_t len = (size_t)ilen;
175
222k
    size_t new_len;
176
222k
    char *p;
177
222k
    debug_decl(fill_args, SUDOERS_DEBUG_PARSER);
178
179
222k
    if (arg_size == 0) {
180
3
#ifdef NO_LEAKS
181
3
  if (sudoerslval.command.args != NULL) {
182
0
      sudo_warnx("%s: command.args %p, should be NULL", __func__,
183
0
    sudoerslval.command.args);
184
0
      sudoerslval.command.args = NULL;
185
0
  }
186
3
#endif
187
3
  addspace = 0;
188
3
  new_len = len;
189
222k
    } else {
190
222k
  new_len = arg_len + len + addspace;
191
222k
    }
192
193
222k
    if (new_len >= arg_size) {
194
  /* Allocate in increments of 128 bytes to avoid excessive realloc(). */
195
3.04k
  arg_size = (new_len + 1 + 127) & ~127U;
196
197
3.04k
  parser_leak_remove(LEAK_PTR, sudoerslval.command.args);
198
3.04k
  p = realloc(sudoerslval.command.args, arg_size);
199
3.04k
  if (p == NULL) {
200
0
      sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
201
0
      goto bad;
202
0
  }
203
3.04k
  parser_leak_add(LEAK_PTR, p);
204
3.04k
  sudoerslval.command.args = p;
205
3.04k
    }
206
207
    /* Efficiently append the arg (with a leading space if needed). */
208
222k
    p = sudoerslval.command.args + arg_len;
209
222k
    if (addspace)
210
0
  *p++ = ' ';
211
222k
    len = arg_size - (size_t)(p - sudoerslval.command.args);
212
222k
    if (strlcpy(p, s, len) >= len) {
213
0
  sudo_warnx(U_("internal error, %s overflow"), __func__);
214
0
  parser_leak_remove(LEAK_PTR, sudoerslval.command.args);
215
0
  goto bad;
216
0
    }
217
222k
    arg_len = new_len;
218
222k
    debug_return_bool(true);
219
0
bad:
220
0
    sudoerserror(NULL);
221
0
    free(sudoerslval.command.args);
222
0
    sudoerslval.command.args = NULL;
223
0
    arg_len = arg_size = 0;
224
0
    debug_return_bool(false);
225
0
}
226
227
/*
228
 * Check to make sure an IPv6 address does not contain multiple instances
229
 * of the string "::".  Assumes strlen(s) >= 1.
230
 * Returns true if address is valid else false.
231
 */
232
bool
233
ipv6_valid(const char *s)
234
20
{
235
20
    int nmatch = 0;
236
20
    debug_decl(ipv6_valid, SUDOERS_DEBUG_PARSER);
237
238
102
    for (; *s != '\0'; s++) {
239
82
  if (s[0] == ':' && s[1] == ':') {
240
21
      if (++nmatch > 1)
241
0
    break;
242
21
  }
243
82
  if (s[0] == '/')
244
17
      nmatch = 0;     /* reset if we hit netmask */
245
82
    }
246
247
    debug_return_bool(nmatch <= 1);
248
20
}