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

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.

(cherry picked from commit e75f92047c22e659d49bba3a083cd0c9935f21e6)
(cherry picked from commit e8a9d3f63aebf6065d69bd0125d4b9c367f88def)
This commit is contained in:
Tom Levy 2023-02-21 15:14:30 +02:00 committed by Oran Agra
parent 789f6a95db
commit 8b565570f2
2 changed files with 29 additions and 4 deletions

View File

@ -45,8 +45,8 @@
#include "sha256.h"
/* 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]) {
@ -58,12 +58,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 '?':
@ -165,6 +178,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

@ -272,4 +272,10 @@ start_server {tags {"keyspace"}} {
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"
} {}
}