Coverage Report

Created: 2025-10-10 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/lib/util/regex.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2023 Todd C. Miller <Todd.Miller@sudo.ws>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <config.h>
20
21
#include <stddef.h>
22
#include <stdlib.h>
23
#include <string.h>
24
#include <ctype.h>
25
#include <limits.h>
26
#include <regex.h>
27
28
#include <sudo_compat.h>
29
#include <sudo_debug.h>
30
#include <sudo_util.h>
31
#include <sudo_gettext.h>
32
33
static char errbuf[1024];
34
35
/*
36
 * Parse a number between 0 and INT_MAX, handling escaped digits.
37
 * Returns the number on success or -1 on error.
38
 * Sets endp to the first non-matching character.
39
 */
40
static int
41
parse_num(const char *str, char **endp)
42
0
{
43
0
    debug_decl(check_pattern, SUDO_DEBUG_UTIL);
44
0
    const int lastval = INT_MAX / 10;
45
0
    const int remainder = INT_MAX % 10;
46
0
    int result = 0;
47
0
    char ch;
48
49
0
    while ((ch = *str++) != '\0') {
50
0
  if (ch == '\\' && isdigit((unsigned char)str[0]))
51
0
      ch = *str++;
52
0
  else if (!isdigit((unsigned char)ch))
53
0
      break;
54
0
  ch -= '0';
55
0
  if (result > lastval || (result == lastval && ch > remainder)) {
56
0
      result = -1;
57
0
      break;
58
0
  }
59
0
  result *= 10;
60
0
  result += ch;
61
0
    }
62
0
    *endp = (char *)(str - 1);
63
64
0
    debug_return_int(result);
65
0
}
66
67
/*
68
 * Check pattern for invalid repetition sequences.
69
 * This is implementation-specific behavior, not all regcomp(3) forbid them.
70
 * Glibc allows it but uses excessive memory for repeated '+' ops.
71
 */
72
static int
73
check_pattern(const char *pattern)
74
0
{
75
0
    debug_decl(check_pattern, SUDO_DEBUG_UTIL);
76
0
    const char *cp = pattern;
77
0
    int b1, b2 = 0;
78
0
    char ch, *ep, prev = '\0';
79
80
0
    while ((ch = *cp++) != '\0') {
81
0
  switch (ch) {
82
0
  case '\\':
83
0
      if (*cp != '\0') {
84
    /* Skip escaped character. */
85
0
    cp++;
86
0
    prev = '\0';
87
0
    continue;
88
0
      }
89
0
      break;
90
0
  case '?':
91
0
  case '*':
92
0
  case '+':
93
0
      if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
94
    /* Invalid repetition operator. */
95
0
    debug_return_int(REG_BADRPT);
96
0
      }
97
0
      break;
98
0
  case '{':
99
      /*
100
       * Try to match bound: {[0-9\\]*\?,[0-9\\]*}
101
       * GNU libc supports escaped digits and commas.
102
       */
103
0
      b1 = parse_num(cp, &ep);
104
0
      switch (ep[0]) {
105
0
      case '\\':
106
0
    if (ep[1] != ',')
107
0
        break;
108
0
    ep++;
109
0
    FALLTHROUGH;
110
0
      case ',':
111
0
    cp = ep + 1;
112
0
    b2 = parse_num(cp, &ep);
113
0
    break;
114
0
      }
115
0
      cp = ep;
116
0
      if (*cp == '}') {
117
0
    if (b1 < 0 || b1 > 255 || b2 < 0 || b2 > 255) {
118
        /* Invalid bound value. */
119
0
        debug_return_int(REG_BADBR);
120
0
    }
121
0
    if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
122
        /* Invalid repetition operator. */
123
0
        debug_return_int(REG_BADRPT);
124
0
    }
125
    /* Skip past '}', prev will be set to '{' below */
126
0
    cp++;
127
0
    break;
128
0
      }
129
0
      prev = '\0';
130
0
      continue;
131
0
  }
132
0
  prev = ch;
133
0
    }
134
135
0
    debug_return_int(0);
136
0
}
137
138
/*
139
 * Wrapper around regcomp() that handles a regex starting with (?i).
140
 * Avoid using regex_t in the function args so we don't need to
141
 * include regex.h everywhere.
142
 */
143
bool
144
sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr)
145
0
{
146
0
    int errcode, cflags = REG_EXTENDED|REG_NOSUB;
147
0
    regex_t *preg;
148
0
    char *copy = NULL;
149
0
    const char *cp;
150
0
    regex_t rebuf;
151
0
    debug_decl(regex_compile, SUDO_DEBUG_UTIL);
152
153
    /* Some callers just want to check the validity of the pattern. */
154
0
    preg = v ? v : &rebuf;
155
156
    /* Limit the length of regular expressions to avoid fuzzer issues. */
157
0
    if (strlen(pattern) > 1024) {
158
0
  *errstr = N_("regular expression too large");
159
0
  debug_return_bool(false);
160
0
    }
161
162
    /* Check for (?i) to enable case-insensitive matching. */
163
0
    cp = pattern[0] == '^' ? pattern + 1 : pattern;
164
0
    if (strncmp(cp, "(?i)", 4) == 0) {
165
0
  cflags |= REG_ICASE;
166
0
  copy = strdup(pattern + 4);
167
0
  if (copy == NULL) {
168
0
      *errstr = N_("unable to allocate memory");
169
0
      debug_return_bool(false);
170
0
  }
171
0
  if (pattern[0] == '^')
172
0
      copy[0] = '^';
173
0
  pattern = copy;
174
0
    }
175
176
0
    errcode = check_pattern(pattern);
177
0
    if (errcode == 0)
178
0
  errcode = regcomp(preg, pattern, cflags);
179
0
    if (errcode == 0) {
180
0
  if (preg == &rebuf)
181
0
      regfree(&rebuf);
182
0
    } else {
183
0
        regerror(errcode, preg, errbuf, sizeof(errbuf));
184
0
        *errstr = errbuf;
185
0
    }
186
0
    free(copy);
187
188
    debug_return_bool(errcode == 0);
189
0
}