Coverage Report

Created: 2025-07-11 06:58

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