diff --git a/Model/Rule.php b/Model/Rule.php index c888bcc..2317df0 100644 --- a/Model/Rule.php +++ b/Model/Rule.php @@ -121,7 +121,11 @@ private function scalarValueMatchesCondition(string $value, Condition $condition switch ($condition->type) { case 'regex': - return (bool)preg_match('/' . str_replace('/', '\/', $condition->value) . '/', $value); + $result = preg_match('/' . str_replace('/', '\/', $condition->value) . '/', $value); + if ($result === false) { + $this->logPcreFailure($condition); + } + return (bool)$result; case 'contains': return strpos($value, $condition->value) !== false; case 'equals': @@ -131,6 +135,15 @@ private function scalarValueMatchesCondition(string $value, Condition $condition } } + private function logPcreFailure(Condition $condition): void + { + $this->logger->warning('Regex condition failed with a PCRE error.', [ + 'error' => function_exists('preg_last_error_msg') ? preg_last_error_msg() : preg_last_error(), + 'target' => $condition->target, + 'pattern' => $condition->value + ]); + } + private function targetValueMatchesCondition($value, Condition $condition): bool { if ($condition->type === 'network') { diff --git a/Test/Model/RuleTest.php b/Test/Model/RuleTest.php index 0a3500c..4a1d661 100644 --- a/Test/Model/RuleTest.php +++ b/Test/Model/RuleTest.php @@ -146,6 +146,27 @@ public function testRuleWithMultipleConditions() $this->assertTrue($rule->matches($request)); } + public function testRuleRegexLogsAndFailsOpenOnPcreError() + { + $originalBacktrackLimit = ini_get('pcre.backtrack_limit'); + ini_set('pcre.backtrack_limit', '1'); + + try { + $logger = $this->createMock(Logger::class); + $logger->expects($this->once())->method('warning'); + $request = $this->createConfiguredMock(Http::class, [ + 'getContent' => str_repeat('a', 50) . 'X' + ]); + $rule = new Rule(new IP(), $logger, 'block', [ + new Condition('req.body', 'regex', '(a+)+$') + ]); + + $this->assertFalse($rule->matches($request)); + } finally { + ini_set('pcre.backtrack_limit', $originalBacktrackLimit); + } + } + public function testRuleWithoutConditions() { $rule = new Rule(new IP(), $this->createMock(Logger::class), 'block', []);