-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add isolated PCRE match limits as a layer of ReDoS defense #2736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8c269d3
8c4b7c1
0c42ee2
f3d8198
23a0e26
6f1bd27
d875738
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,12 +51,36 @@ bool Rx::evaluate(Transaction *transaction, RuleWithActions *rule, | |
re = m_re; | ||
} | ||
|
||
std::vector<Utils::SMatchCapture> captures; | ||
if (re->hasError()) { | ||
ms_dbg_a(transaction, 3, "Error with regular expression: \"" + re->pattern + "\""); | ||
return false; | ||
} | ||
re->searchOneMatch(input, captures); | ||
|
||
Utils::RegexResult regex_result; | ||
std::vector<Utils::SMatchCapture> captures; | ||
|
||
if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) { | ||
unsigned long match_limit = transaction->m_rules->m_pcreMatchLimit.m_value; | ||
regex_result = re->searchOneMatch(input, captures, match_limit); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we abandon the get_default functionality, I suspect there wouldn't be a compelling reason to overload this function (or the 'Global' equivalent). A single function could either have the second argument explicitly passed something like 0 or -1 (or the second arg could be declared with a default of one of those). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went ahead and added a default value for the match_limit param in the declarations in src/utils/regex.h and left this logic the same. My thinking is that, this way, only regex.cc needs to worry about the default value. |
||
} else { | ||
regex_result = re->searchOneMatch(input, captures); | ||
} | ||
|
||
// FIXME: DRY regex error reporting. This logic is currently duplicated in other operators. | ||
if (regex_result != Utils::RegexResult::Ok) { | ||
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error variable gets set to '1' when an error is encountered, but there is no setting to '0' otherwise -- it's just not set to anything. ModSecurity error variables should have testable value for 'no error' and this is conventionally '0'. Often this the variable is set to either 0 or 1 upon completion of the single task per transaction (such as REQBODY_ERROR). You won't be able to do that here. The best analog is URL_ENCODED_ERROR, which is initialized to 0 in the Transaction constructors. Same thing with m_variableMscPcreLimitsExceeded There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing this out. It's definitely good to explicitly set it to a sensible initial value. Done. |
||
|
||
std::string regex_error_str = "OTHER"; | ||
if (regex_result == Utils::RegexResult::ErrorMatchLimit) { | ||
regex_error_str = "MATCH_LIMIT"; | ||
transaction->m_variableMscPcreLimitsExceeded.set("1", transaction->m_variableOffset); | ||
} | ||
|
||
ms_dbg_a(transaction, 1, "rx: regex error '" + regex_error_str + "' for pattern '" + re->pattern + "'"); | ||
|
||
|
||
return false; | ||
} | ||
|
||
if (rule && rule->hasCaptureAction() && transaction) { | ||
for (const Utils::SMatchCapture& capture : captures) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with this size. 'int' should be big enough on any architecture that matters.
Note, however, the disparity between pcre1 and pcre2 (long vs. uint32_t) for this. This may another reason to avoid doing the get_default