Skip to content

Log a warning when a regex condition hits a PCRE error - #44

Open
lbajsarowicz wants to merge 1 commit into
sansecio:mainfrom
lbajsarowicz:fix/log-pcre-errors
Open

Log a warning when a regex condition hits a PCRE error#44
lbajsarowicz wants to merge 1 commit into
sansecio:mainfrom
lbajsarowicz:fix/log-pcre-errors

Conversation

@lbajsarowicz

@lbajsarowicz lbajsarowicz commented Sep 5, 2026

Copy link
Copy Markdown

Problem

In Model/Rule.php::scalarValueMatchesCondition(), the regex branch is:

return (bool)preg_match('/' . str_replace('/', '\/', $condition->value) . '/', $value);

preg_match() can return false on a PCRE error (invalid pattern, pcre.backtrack_limit / JIT stack exhaustion on a large request body, invalid UTF-8 with /u). Casting false to bool makes it indistinguishable from a genuine non-match: the rule fails open silently and nothing is logged, so a rule that stops matching due to a PCRE error is invisible in var/log/sansec_shield.log.

Change

  • Added a private logPcreFailure() helper. It is called only when preg_match() returns false, and logs through the existing injected logger with preg_last_error_msg() (PHP >= 8.0) falling back to the integer preg_last_error() code on PHP 7.2/7.3, plus the condition's target and pattern.
  • The happy path (a normal match/no-match result) is unchanged: still a single preg_match() call, one extra === false comparison, zero extra work when the pattern evaluates normally. No new object allocation, no try/catch, no extra function calls unless PCRE actually failed.
  • Fail-open behavior is preserved as-is (documented module behaviour) — the method still returns false on a PCRE error, only now it also logs.

Testing

Added testRuleRegexLogsAndFailsOpenOnPcreError() in Test/Model/RuleTest.php.

It triggers a genuine runtime PCRE failure rather than a pattern compile error: ini_set('pcre.backtrack_limit', '1') plus a catastrophic-backtracking pattern ((a+)+$) against a non-matching body forces preg_match() to return false via PREG_BACKTRACK_LIMIT_ERROR (or the JIT-stack equivalent). This path does not emit a PHP E_WARNING, unlike an invalid pattern (unbalanced parenthesis etc.), which would raise "preg_match(): Compilation failed" and fail PHPUnit's --fail-on-warning run. The test asserts Rule::matches() still returns false (fail-open) and that the logger mock receives exactly one warning() call, then restores the original pcre.backtrack_limit in a finally block.

Local test run: PHPUnit with --fail-on-warning and xmllint on etc/*.xml passed (58 tests, 64 assertions).

preg_match() returns false (not 0) when PCRE fails to evaluate a pattern:
a bad pattern, a backtrack or JIT stack limit hit on a large body, or
invalid UTF-8 under /u. The regex branch cast that value to bool, so a
PCRE failure was silently treated as "no match" while every other failure
path in Rule::matches() already logs a warning. Log the PCRE error on the
false branch only; the fast path is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant