Skip to content

Fix infinite loop / OOM in regex_match on overlapping matches#15

Open
thiagomkuhl wants to merge 1 commit into
careapp-group:masterfrom
thiagomkuhl:fix/regex-match-infinite-loop
Open

Fix infinite loop / OOM in regex_match on overlapping matches#15
thiagomkuhl wants to merge 1 commit into
careapp-group:masterfrom
thiagomkuhl:fix/regex-match-infinite-loop

Conversation

@thiagomkuhl

Copy link
Copy Markdown

Summary

  • regex_match in lib/src/matching.dart tracks its scan cursor incorrectly: RegExp.firstMatch is run against a substring of the password (password.substring(last_index)), so rx_match.start is relative to that substring, not to the original password. The existing code used it as an absolute index and additionally re-included the match's last character in the next substring's start (... - 1).
  • For overlapping matches this makes last_index oscillate between two values forever — the loop never terminates and matches grows without bound until the process runs out of memory.
  • Concrete repro: matching.regex_match('20112001Vi-') never returns. The recent_year regex matches "2011" at index 0, then (due to the bug) re-scans from index 3, matches "2001" at relative index 1 (absolute 4), computes a new last_index of 4, then re-scans from index 4 and matches "2001" again at relative index 0 (absolute 4), computing last_index back to 3 — and repeats forever.
  • This is a real-world crash: it OOM-killed a Flutter app in production during signup password-strength validation (Zxcvbn().evaluate(password)).

Fix

Track the absolute offset explicitly (last_index + rx_match.start) and advance the cursor strictly past the end of the matched token (abs_end + 1, no - 1), so the loop always makes forward progress and never re-scans a position it has already consumed.

Test plan

  • Added a regression test (regex matching terminates on overlapping matches) reproducing the hang with '20112001Vi-', asserting it returns exactly the two expected non-overlapping matches.
  • Ran the full existing test suite (dart test) — all passing tests still pass. One pre-existing unrelated failure (scoring_test.dart: regex guesses) is present on master too (it hardcodes an assumption about the current year vs. REFERENCE_YEAR = DateTime.now().year) and is untouched by this change.

regex_match tracked its scan cursor incorrectly: RegExp.firstMatch is
run against a substring of the password, so rx_match.start is relative
to that substring, not to the original password. The code used it as
an absolute index and additionally re-included the match's last
character in the next substring (`... - 1`). For overlapping matches
(e.g. two year-like digit runs sharing a digit, such as "20112001") the
cursor oscillates between two values forever, growing the matches list
without bound and eventually exhausting memory.

Track the absolute offset explicitly and advance strictly past each
matched token so the loop always makes forward progress. Adds a
regression test.
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