String pattern matching had exponential time complexity on pathological patterns (CVE-2022-36021) (#11858)

Authenticated users can use string matching commands with a
specially crafted pattern to trigger a denial-of-service attack on Redis,
causing it to hang and consume 100% CPU time.

Co-authored-by: Tom Levy <tomlevy93@gmail.com>
This commit is contained in:
Oran Agra 2023-02-28 15:15:26 +02:00 committed by GitHub
parent 18017df7c1
commit dcbfcb916c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 4 deletions

View File

@ -53,8 +53,8 @@
#define UNUSED(x) ((void)(x))
/* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase)
static int stringmatchlen_impl(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase, int *skipLongerMatches)
{
while(patternLen && stringLen) {
switch(pattern[0]) {
@ -66,12 +66,25 @@ int stringmatchlen(const char *pattern, int patternLen,
if (patternLen == 1)
return 1; /* match */
while(stringLen) {
if (stringmatchlen(pattern+1, patternLen-1,
string, stringLen, nocase))
if (stringmatchlen_impl(pattern+1, patternLen-1,
string, stringLen, nocase, skipLongerMatches))
return 1; /* match */
if (*skipLongerMatches)
return 0; /* no match */
string++;
stringLen--;
}
/* There was no match for the rest of the pattern starting
* from anywhere in the rest of the string. If there were
* any '*' earlier in the pattern, we can terminate the
* search early without trying to match them to longer
* substrings. This is because a longer match for the
* earlier part of the pattern would require the rest of the
* pattern to match starting later in the string, and we
* have just determined that there is no match for the rest
* of the pattern starting from anywhere in the current
* string. */
*skipLongerMatches = 1;
return 0; /* no match */
break;
case '?':
@ -173,6 +186,12 @@ int stringmatchlen(const char *pattern, int patternLen,
return 0;
}
int stringmatchlen(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase) {
int skipLongerMatches = 0;
return stringmatchlen_impl(pattern,patternLen,string,stringLen,nocase,&skipLongerMatches);
}
int stringmatch(const char *pattern, const char *string, int nocase) {
return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase);
}

View File

@ -493,4 +493,10 @@ foreach {type large} [array get largevalue] {
r keys *
r keys *
} {dlskeriewrioeuwqoirueioqwrueoqwrueqw}
test {Regression for pattern matching long nested loops} {
r flushdb
r SET aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1
r KEYS "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*b"
} {}
}