Skip to content

Commit 09a135b

Browse files
authored
Merge pull request #2736 from brandonpayton/add-regex-match-limits-and-error-reporting
Add isolated PCRE match limits as a layer of ReDoS defense
2 parents 62bbd7b + d875738 commit 09a135b

19 files changed

+7831
-7360
lines changed

Diff for: headers/modsecurity/rules_set_properties.h

+2
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ class RulesSetProperties {
379379
from->m_responseBodyLimitAction,
380380
PropertyNotSetBodyLimitAction);
381381

382+
to->m_pcreMatchLimit.merge(&from->m_pcreMatchLimit);
382383
to->m_uploadFileLimit.merge(&from->m_uploadFileLimit);
383384
to->m_uploadFileMode.merge(&from->m_uploadFileMode);
384385
to->m_uploadDirectory.merge(&from->m_uploadDirectory);
@@ -470,6 +471,7 @@ class RulesSetProperties {
470471
ConfigDouble m_requestBodyLimit;
471472
ConfigDouble m_requestBodyNoFilesLimit;
472473
ConfigDouble m_responseBodyLimit;
474+
ConfigInt m_pcreMatchLimit;
473475
ConfigInt m_uploadFileLimit;
474476
ConfigInt m_uploadFileMode;
475477
DebugLog *m_debugLog;

Diff for: headers/modsecurity/transaction.h

+4
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ class TransactionAnchoredVariables {
134134
m_variableInboundDataError(t, "INBOUND_DATA_ERROR"),
135135
m_variableMatchedVar(t, "MATCHED_VAR"),
136136
m_variableMatchedVarName(t, "MATCHED_VAR_NAME"),
137+
m_variableMscPcreError(t, "MSC_PCRE_ERROR"),
138+
m_variableMscPcreLimitsExceeded(t, "MSC_PCRE_LIMITS_EXCEEDED"),
137139
m_variableMultipartBoundaryQuoted(t, "MULTIPART_BOUNDARY_QUOTED"),
138140
m_variableMultipartBoundaryWhiteSpace(t,
139141
"MULTIPART_BOUNDARY_WHITESPACE"),
@@ -219,6 +221,8 @@ class TransactionAnchoredVariables {
219221
AnchoredVariable m_variableInboundDataError;
220222
AnchoredVariable m_variableMatchedVar;
221223
AnchoredVariable m_variableMatchedVarName;
224+
AnchoredVariable m_variableMscPcreError;
225+
AnchoredVariable m_variableMscPcreLimitsExceeded;
222226
AnchoredVariable m_variableMultipartBoundaryQuoted;
223227
AnchoredVariable m_variableMultipartBoundaryWhiteSpace;
224228
AnchoredVariable m_variableMultipartCrlfLFLines;

Diff for: src/operators/rx.cc

+26-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,36 @@ bool Rx::evaluate(Transaction *transaction, RuleWithActions *rule,
5151
re = m_re;
5252
}
5353

54-
std::vector<Utils::SMatchCapture> captures;
5554
if (re->hasError()) {
5655
ms_dbg_a(transaction, 3, "Error with regular expression: \"" + re->pattern + "\"");
5756
return false;
5857
}
59-
re->searchOneMatch(input, captures);
58+
59+
Utils::RegexResult regex_result;
60+
std::vector<Utils::SMatchCapture> captures;
61+
62+
if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) {
63+
unsigned long match_limit = transaction->m_rules->m_pcreMatchLimit.m_value;
64+
regex_result = re->searchOneMatch(input, captures, match_limit);
65+
} else {
66+
regex_result = re->searchOneMatch(input, captures);
67+
}
68+
69+
// FIXME: DRY regex error reporting. This logic is currently duplicated in other operators.
70+
if (regex_result != Utils::RegexResult::Ok) {
71+
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);
72+
73+
std::string regex_error_str = "OTHER";
74+
if (regex_result == Utils::RegexResult::ErrorMatchLimit) {
75+
regex_error_str = "MATCH_LIMIT";
76+
transaction->m_variableMscPcreLimitsExceeded.set("1", transaction->m_variableOffset);
77+
}
78+
79+
ms_dbg_a(transaction, 1, "rx: regex error '" + regex_error_str + "' for pattern '" + re->pattern + "'");
80+
81+
82+
return false;
83+
}
6084

6185
if (rule && rule->hasCaptureAction() && transaction) {
6286
for (const Utils::SMatchCapture& capture : captures) {

Diff for: src/operators/rx_global.cc

+23-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,30 @@ bool RxGlobal::evaluate(Transaction *transaction, RuleWithActions *rule,
5151
re = m_re;
5252
}
5353

54+
Utils::RegexResult regex_result;
5455
std::vector<Utils::SMatchCapture> captures;
55-
re->searchGlobal(input, captures);
56+
if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) {
57+
unsigned long match_limit = transaction->m_rules->m_pcreMatchLimit.m_value;
58+
regex_result = re->searchGlobal(input, captures, match_limit);
59+
} else {
60+
regex_result = re->searchGlobal(input, captures);
61+
}
62+
63+
// FIXME: DRY regex error reporting. This logic is currently duplicated in other operators.
64+
if (regex_result != Utils::RegexResult::Ok) {
65+
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);
66+
67+
std::string regex_error_str = "OTHER";
68+
if (regex_result == Utils::RegexResult::ErrorMatchLimit) {
69+
regex_error_str = "MATCH_LIMIT";
70+
transaction->m_variableMscPcreLimitsExceeded.set("1", transaction->m_variableOffset);
71+
}
72+
73+
ms_dbg_a(transaction, 1, "rxGlobal: regex error '" + regex_error_str + "' for pattern '" + re->pattern + "'");
74+
75+
return false;
76+
}
77+
5678

5779
if (rule && rule->hasCaptureAction() && transaction) {
5880
for (const Utils::SMatchCapture& capture : captures) {

Diff for: src/parser/location.hh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// A Bison parser, made by GNU Bison 3.7.6.
1+
// A Bison parser, made by GNU Bison 3.8.2.
22

33
// Locations for Bison parsers in C++
44

Diff for: src/parser/position.hh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// A Bison parser, made by GNU Bison 3.7.6.
1+
// A Bison parser, made by GNU Bison 3.8.2.
22

33
// Starting with Bison 3.2, this file is useless: the structure it
44
// used to define is now defined in "location.hh".

0 commit comments

Comments
 (0)