diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 82b96c70ce8..4a5a1896d0b 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -6,6 +6,7 @@ import re from shutil import rmtree +import regex from django.conf import settings from django.db import models from django.db.models import F @@ -1137,6 +1138,8 @@ def __str__(self): class RegexAutomationRule(VersionAutomationRule): + TIMEOUT = 1 # timeout in seconds + allowed_actions = { VersionAutomationRule.ACTIVATE_VERSION_ACTION: actions.activate_version, VersionAutomationRule.SET_DEFAULT_VERSION_ACTION: actions.set_default_version, @@ -1146,11 +1149,31 @@ class Meta: proxy = True def match(self, version, match_arg): + """ + Find a match using regex.search. + + .. note:: + + We use the regex module with the timeout + arg to avoid ReDoS. + + We could use a finite state machine type of regex too, + but there isn't a stable library at the time of writting this code. + """ try: - match = re.search( - match_arg, version.verbose_name + match = regex.search( + match_arg, + version.verbose_name, + # Compatible with the re module + flags=regex.VERSION0, + timeout=self.TIMEOUT, ) return bool(match), match + except TimeoutError: + log.warning( + 'Timeout while parsing regex. pattern=%s, input=%s', + match_arg, version.verbose_name, + ) except Exception as e: log.info('Error parsing regex: %s', e) - return False, None + return False, None diff --git a/requirements/pip.txt b/requirements/pip.txt index 518921be45e..7af4c612e35 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -75,6 +75,7 @@ Unipath==1.1 django-kombu==0.9.4 mock==3.0.5 stripe==2.37.2 +regex==2019.11.1 # unicode-slugify==0.1.5 is not released on PyPI yet git+https://github.com/mozilla/unicode-slugify@b696c37#egg=unicode-slugify==0.1.5