mirror of https://github.com/sudo-project/sudo.git
195 lines
4.9 KiB
C
195 lines
4.9 KiB
C
/*
|
|
* SPDX-License-Identifier: ISC
|
|
*
|
|
* Copyright (c) 2023 Todd C. Miller <Todd.Miller@sudo.ws>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <regex.h>
|
|
|
|
#include <sudo_compat.h>
|
|
#include <sudo_debug.h>
|
|
#include <sudo_util.h>
|
|
#include <sudo_gettext.h>
|
|
|
|
static char errbuf[1024];
|
|
|
|
/*
|
|
* Parse a number between 0 and INT_MAX, handling escaped digits.
|
|
* Returns the number on success or -1 on error.
|
|
* Sets endp to the first non-matching character.
|
|
*/
|
|
static int
|
|
parse_num(const char *str, char **endp)
|
|
{
|
|
debug_decl(check_pattern, SUDO_DEBUG_UTIL);
|
|
const int lastval = INT_MAX / 10;
|
|
const int remainder = INT_MAX % 10;
|
|
int result = 0;
|
|
char ch;
|
|
|
|
while ((ch = *str++) != '\0') {
|
|
if (ch == '\\' && isdigit((unsigned char)str[0]))
|
|
ch = *str++;
|
|
else if (!isdigit((unsigned char)ch))
|
|
break;
|
|
ch -= '0';
|
|
if (result > lastval || (result == lastval && ch > remainder)) {
|
|
result = -1;
|
|
break;
|
|
}
|
|
result *= 10;
|
|
result += ch;
|
|
}
|
|
*endp = (char *)(str - 1);
|
|
|
|
debug_return_int(result);
|
|
}
|
|
|
|
/*
|
|
* Check pattern for invalid repetition sequences.
|
|
* This is implementation-specific behavior, not all regcomp(3) forbid them.
|
|
* Glibc allows it but uses excessive memory for repeated '+' ops.
|
|
*/
|
|
static int
|
|
check_pattern(const char *pattern)
|
|
{
|
|
debug_decl(check_pattern, SUDO_DEBUG_UTIL);
|
|
const char *cp = pattern;
|
|
int b1, b2 = 0;
|
|
char ch, *ep, prev = '\0';
|
|
|
|
while ((ch = *cp++) != '\0') {
|
|
switch (ch) {
|
|
case '\\':
|
|
if (*cp != '\0') {
|
|
/* Skip escaped character. */
|
|
cp++;
|
|
prev = '\0';
|
|
continue;
|
|
}
|
|
break;
|
|
case '?':
|
|
case '*':
|
|
case '+':
|
|
if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
|
|
/* Invalid repetition operator. */
|
|
debug_return_int(REG_BADRPT);
|
|
}
|
|
break;
|
|
case '{':
|
|
/*
|
|
* Try to match bound: {[0-9\\]*\?,[0-9\\]*}
|
|
* GNU libc supports escaped digits and commas.
|
|
*/
|
|
b1 = parse_num(cp, &ep);
|
|
switch (ep[0]) {
|
|
case '\\':
|
|
if (ep[1] != ',')
|
|
break;
|
|
ep++;
|
|
FALLTHROUGH;
|
|
case ',':
|
|
cp = ep + 1;
|
|
b2 = parse_num(cp, &ep);
|
|
break;
|
|
}
|
|
cp = ep;
|
|
if (*cp == '}') {
|
|
if (b1 < 0 || b1 > 255 || b2 < 0 || b2 > 255) {
|
|
/* Invalid bound value. */
|
|
debug_return_int(REG_BADBR);
|
|
}
|
|
if (prev == '?' || prev == '*' || prev == '+' || prev == '{' ) {
|
|
/* Invalid repetition operator. */
|
|
debug_return_int(REG_BADRPT);
|
|
}
|
|
/* Skip past '}', prev will be set to '{' below */
|
|
cp++;
|
|
break;
|
|
}
|
|
prev = '\0';
|
|
continue;
|
|
}
|
|
prev = ch;
|
|
}
|
|
|
|
debug_return_int(0);
|
|
}
|
|
|
|
/*
|
|
* Wrapper around regcomp() that handles a regex starting with (?i).
|
|
* Avoid using regex_t in the function args so we don't need to
|
|
* include regex.h everywhere.
|
|
*/
|
|
bool
|
|
sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr)
|
|
{
|
|
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
|
|
regex_t *preg;
|
|
char *copy = NULL;
|
|
const char *cp;
|
|
regex_t rebuf;
|
|
debug_decl(regex_compile, SUDO_DEBUG_UTIL);
|
|
|
|
/* Some callers just want to check the validity of the pattern. */
|
|
preg = v ? v : &rebuf;
|
|
|
|
/* Limit the length of regular expressions to avoid fuzzer issues. */
|
|
if (strlen(pattern) > 1024) {
|
|
*errstr = N_("regular expression too large");
|
|
debug_return_bool(false);
|
|
}
|
|
|
|
/* Check for (?i) to enable case-insensitive matching. */
|
|
cp = pattern[0] == '^' ? pattern + 1 : pattern;
|
|
if (strncmp(cp, "(?i)", 4) == 0) {
|
|
cflags |= REG_ICASE;
|
|
copy = strdup(pattern + 4);
|
|
if (copy == NULL) {
|
|
*errstr = N_("unable to allocate memory");
|
|
debug_return_bool(false);
|
|
}
|
|
if (pattern[0] == '^')
|
|
copy[0] = '^';
|
|
pattern = copy;
|
|
}
|
|
|
|
errcode = check_pattern(pattern);
|
|
if (errcode == 0)
|
|
errcode = regcomp(preg, pattern, cflags);
|
|
if (errcode == 0) {
|
|
if (preg == &rebuf)
|
|
regfree(&rebuf);
|
|
} else {
|
|
regerror(errcode, preg, errbuf, sizeof(errbuf));
|
|
*errstr = errbuf;
|
|
}
|
|
free(copy);
|
|
|
|
debug_return_bool(errcode == 0);
|
|
}
|