Coverage Report

Created: 2023-06-07 06:46

/src/sudo/lib/util/regex.c
Line
Count
Source (jump to first uncovered line)
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
/*
20
 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21
 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22
 */
23
24
#include <config.h>
25
26
#include <stddef.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <ctype.h>
30
#include <limits.h>
31
#include <regex.h>
32
33
#include "sudo_compat.h"
34
#include "sudo_debug.h"
35
#include "sudo_util.h"
36
#include "sudo_gettext.h"
37
38
static char errbuf[1024];
39
40
/*
41
 * Parse a number between 0 and INT_MAX, handling escaped digits.
42
 * Returns the number on success or -1 on error.
43
 * Sets endp to the first non-matching character.
44
 */
45
static int
46
parse_num(const char *str, char **endp)
47
0
{
48
0
    debug_decl(check_pattern, SUDO_DEBUG_UTIL);
49
0
    const unsigned int lastval = INT_MAX / 10;
50
0
    const unsigned int remainder = INT_MAX % 10;
51
0
    unsigned int result = 0;
52
0
    unsigned char ch;
53
54
0
    while ((ch = *str++) != '\0') {
55
0
  if (ch == '\\' && isdigit((unsigned int)str[0]))
56
0
      ch = *str++;
57
0
  else if (!isdigit(ch))
58
0
      break;
59
0
  ch -= '0';
60
0
  if (result > lastval || (result == lastval && ch > remainder)) {
61
0
      result = -1;
62
0
      break;
63
0
  }
64
0
  result *= 10;
65
0
  result += ch;
66
0
    }
67
0
    *endp = (char *)(str - 1);
68
69
0
    debug_return_int(result);
70
0
}
71
72
/*
73
 * Check pattern for invalid repetition sequences.
74
 * This is implementation-specific behavior, not all regcomp(3) forbid them.
75
 * Glibc allows it but uses excessive memory for repeated '+' ops.
76
 */
77
static int
78
check_pattern(const char *pattern)
79
0
{
80
0
    debug_decl(check_pattern, SUDO_DEBUG_UTIL);
81
0
    const char *cp = pattern;
82
0
    int b1, b2 = 0;
83
0
    char ch, *ep, prev = '\0';
84
85
0
    while ((ch = *cp++) != '\0') {
86
0
  switch (ch) {
87
0
  case '\\':
88
0
      if (*cp != '\0') {
89
    /* Skip escaped character. */
90
0
    cp++;
91
0
    prev = '\0';
92
0
    continue;
93
0
      }
94
0
      break;
95
0
  case '?':
96
0
  case '*':
97
0
  case '+':
98
0
      if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
99
    /* Invalid repetition operator. */
100
0
    debug_return_int(REG_BADRPT);
101
0
      }
102
0
      break;
103
0
  case '{':
104
      /*
105
       * Try to match bound: {[0-9\\]*\?,[0-9\\]*}
106
       * GNU libc supports escaped digits and commas.
107
       */
108
0
      b1 = parse_num(cp, &ep);
109
0
      switch (ep[0]) {
110
0
      case '\\':
111
0
    if (ep[1] != ',')
112
0
        break;
113
0
    ep++;
114
0
    FALLTHROUGH;
115
0
      case ',':
116
0
    cp = ep + 1;
117
0
    b2 = parse_num(cp, &ep);
118
0
    break;
119
0
      }
120
0
      cp = ep;
121
0
      if (*cp == '}') {
122
0
    if (b1 < 0 || b1 > 255 || b2 < 0 || b2 > 255) {
123
        /* Invalid bound value. */
124
0
        debug_return_int(REG_BADBR);
125
0
    }
126
0
    if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
127
        /* Invalid repetition operator. */
128
0
        debug_return_int(REG_BADRPT);
129
0
    }
130
    /* Skip past '}', prev will be set to '{' below */
131
0
    cp++;
132
0
    break;
133
0
      }
134
0
      prev = '\0';
135
0
      continue;
136
0
  }
137
0
  prev = ch;
138
0
    }
139
140
0
    debug_return_int(0);
141
0
}
142
143
/*
144
 * Wrapper around regcomp() that handles a regex starting with (?i).
145
 * Avoid using regex_t in the function args so we don't need to
146
 * include regex.h everywhere.
147
 */
148
bool
149
sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr)
150
0
{
151
0
    int errcode, cflags = REG_EXTENDED|REG_NOSUB;
152
0
    regex_t *preg;
153
0
    char *copy = NULL;
154
0
    const char *cp;
155
0
    regex_t rebuf;
156
0
    debug_decl(regex_compile, SUDO_DEBUG_UTIL);
157
158
    /* Some callers just want to check the validity of the pattern. */
159
0
    preg = v ? v : &rebuf;
160
161
    /* Limit the length of regular expressions to avoid fuzzer issues. */
162
0
    if (strlen(pattern) > 1024) {
163
0
  *errstr = N_("regular expression too large");
164
0
  debug_return_bool(false);
165
0
    }
166
167
    /* Check for (?i) to enable case-insensitive matching. */
168
0
    cp = pattern[0] == '^' ? pattern + 1 : pattern;
169
0
    if (strncmp(cp, "(?i)", 4) == 0) {
170
0
  cflags |= REG_ICASE;
171
0
  copy = strdup(pattern + 4);
172
0
  if (copy == NULL) {
173
0
      *errstr = N_("unable to allocate memory");
174
0
      debug_return_bool(false);
175
0
  }
176
0
  if (pattern[0] == '^')
177
0
      copy[0] = '^';
178
0
  pattern = copy;
179
0
    }
180
181
0
    errcode = check_pattern(pattern);
182
0
    if (errcode == 0)
183
0
  errcode = regcomp(preg, pattern, cflags);
184
0
    if (errcode == 0) {
185
0
  if (preg == &rebuf)
186
0
      regfree(&rebuf);
187
0
    } else {
188
0
        regerror(errcode, preg, errbuf, sizeof(errbuf));
189
0
        *errstr = errbuf;
190
0
    }
191
0
    free(copy);
192
193
0
    debug_return_bool(errcode == 0);
194
0
}