diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 1a1a6d06b1..98da64730d 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -225,13 +225,16 @@ def repercent_broken_unicode(path): repercent-encode any octet produced that is not part of a strictly legal UTF-8 octet sequence. """ - try: - path.decode() - except UnicodeDecodeError as e: - repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~") - path = repercent_broken_unicode( - path[:e.start] + force_bytes(repercent) + path[e.end:]) - return path + while True: + try: + path.decode() + except UnicodeDecodeError as e: + # CVE-2019-14235: A recursion shouldn't be used since the exception + # handling uses massive amounts of memory + repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~") + path = path[:e.start] + force_bytes(repercent) + path[e.end:] + else: + return path def filepath_to_uri(path): diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index 03b33ebf63..04acca90f1 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -45,3 +45,13 @@ CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONFie ` for :class:`~django.contrib.postgres.fields.HStoreField` were subject to SQL injection, using a suitably crafted dictionary, with dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``. + +CVE-2019-14235: Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()`` +===================================================================================== + +If passed certain inputs, :func:`django.utils.encoding.uri_to_iri` could lead +to significant memory usage due to excessive recursion when re-percent-encoding +invalid UTF-8 octet sequences. + +``uri_to_iri()`` now avoids recursion when re-percent-encoding invalid UTF-8 +octet sequences. diff --git a/docs/releases/2.1.11.txt b/docs/releases/2.1.11.txt index 0de4175b5f..ae344f35b3 100644 --- a/docs/releases/2.1.11.txt +++ b/docs/releases/2.1.11.txt @@ -45,3 +45,13 @@ CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONFie ` for :class:`~django.contrib.postgres.fields.HStoreField` were subject to SQL injection, using a suitably crafted dictionary, with dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``. + +CVE-2019-14235: Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()`` +===================================================================================== + +If passed certain inputs, :func:`django.utils.encoding.uri_to_iri` could lead +to significant memory usage due to excessive recursion when re-percent-encoding +invalid UTF-8 octet sequences. + +``uri_to_iri()`` now avoids recursion when re-percent-encoding invalid UTF-8 +octet sequences. diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 3aac51869c..8a71fec783 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -46,6 +46,16 @@ CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONFie were subject to SQL injection, using a suitably crafted dictionary, with dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``. +CVE-2019-14235: Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()`` +===================================================================================== + +If passed certain inputs, :func:`django.utils.encoding.uri_to_iri` could lead +to significant memory usage due to excessive recursion when re-percent-encoding +invalid UTF-8 octet sequences. + +``uri_to_iri()`` now avoids recursion when re-percent-encoding invalid UTF-8 +octet sequences. + Bugfixes ======== diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py index c461df71ee..ea7ba5f335 100644 --- a/tests/utils_tests/test_encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -1,4 +1,5 @@ import datetime +import sys import unittest from unittest import mock from urllib.parse import quote_plus @@ -6,8 +7,8 @@ from urllib.parse import quote_plus from django.test import SimpleTestCase from django.utils.encoding import ( DjangoUnicodeDecodeError, escape_uri_path, filepath_to_uri, force_bytes, - force_text, get_system_encoding, iri_to_uri, smart_bytes, smart_text, - uri_to_iri, + force_text, get_system_encoding, iri_to_uri, repercent_broken_unicode, + smart_bytes, smart_text, uri_to_iri, ) from django.utils.functional import SimpleLazyObject from django.utils.translation import gettext_lazy @@ -90,6 +91,15 @@ class TestEncodingUtils(SimpleTestCase): with mock.patch('locale.getdefaultlocale', side_effect=Exception): self.assertEqual(get_system_encoding(), 'ascii') + def test_repercent_broken_unicode_recursion_error(self): + # Prepare a string long enough to force a recursion error if the tested + # function uses recursion. + data = b'\xfc' * sys.getrecursionlimit() + try: + self.assertEqual(repercent_broken_unicode(data), b'%FC' * sys.getrecursionlimit()) + except RecursionError: + self.fail('Unexpected RecursionError raised.') + class TestRFC3987IEncodingUtils(unittest.TestCase):