From 402c0caa851e265410fbcaa55318f22d2bf22ee2 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 11 Feb 2019 11:08:45 +0100 Subject: [PATCH] Fixed CVE-2019-6975 -- Fixed memory exhaustion in utils.numberformat.format(). Thanks Sjoerd Job Postmus for the report and initial patch. Thanks Michael Manfre, Tim Graham, and Florian Apolloner for review. --- django/utils/numberformat.py | 15 ++++++++++++++- docs/releases/1.11.19.txt | 12 ++++++++++++ docs/releases/2.0.11.txt | 12 ++++++++++++ docs/releases/2.1.6.txt | 12 ++++++++++++ tests/utils_tests/test_numberformat.py | 19 +++++++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 9c0496342d..88b35fc435 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -27,7 +27,20 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', # sign sign = '' if isinstance(number, Decimal): - str_number = '{:f}'.format(number) + # Format values with more than 200 digits (an arbitrary cutoff) using + # scientific notation to avoid high memory usage in {:f}'.format(). + _, digits, exponent = number.as_tuple() + if abs(exponent) + len(digits) > 200: + number = '{:e}'.format(number) + coefficient, exponent = number.split('e') + # Format the coefficient. + coefficient = format( + coefficient, decimal_sep, decimal_pos, grouping, + thousand_sep, force_grouping, use_l10n, + ) + return '{}e{}'.format(coefficient, exponent) + else: + str_number = '{:f}'.format(number) else: str_number = str(number) if str_number[0] == '-': diff --git a/docs/releases/1.11.19.txt b/docs/releases/1.11.19.txt index cae22c4415..9ce48f26b2 100644 --- a/docs/releases/1.11.19.txt +++ b/docs/releases/1.11.19.txt @@ -5,3 +5,15 @@ Django 1.11.19 release notes *February 11, 2019* Django 1.11.19 fixes a security issue in 1.11.18. + +CVE-2019-6975: Memory exhaustion in ``django.utils.numberformat.format()`` +-------------------------------------------------------------------------- + +If ``django.utils.numberformat.format()`` -- used by ``contrib.admin`` as well +as the the ``floatformat``, ``filesizeformat``, and ``intcomma`` templates +filters -- received a ``Decimal`` with a large number of digits or a large +exponent, it could lead to significant memory usage due to a call to +``'{:f}'.format()``. + +To avoid this, decimals with more than 200 digits are now formatted using +scientific notation. diff --git a/docs/releases/2.0.11.txt b/docs/releases/2.0.11.txt index 969af23ecf..f6c4368baa 100644 --- a/docs/releases/2.0.11.txt +++ b/docs/releases/2.0.11.txt @@ -5,3 +5,15 @@ Django 2.0.11 release notes *February 11, 2019* Django 2.0.11 fixes a security issue in 2.0.10. + +CVE-2019-6975: Memory exhaustion in ``django.utils.numberformat.format()`` +-------------------------------------------------------------------------- + +If ``django.utils.numberformat.format()`` -- used by ``contrib.admin`` as well +as the the ``floatformat``, ``filesizeformat``, and ``intcomma`` templates +filters -- received a ``Decimal`` with a large number of digits or a large +exponent, it could lead to significant memory usage due to a call to +``'{:f}'.format()``. + +To avoid this, decimals with more than 200 digits are now formatted using +scientific notation. diff --git a/docs/releases/2.1.6.txt b/docs/releases/2.1.6.txt index f6c589848a..fea8209115 100644 --- a/docs/releases/2.1.6.txt +++ b/docs/releases/2.1.6.txt @@ -6,6 +6,18 @@ Django 2.1.6 release notes Django 2.1.6 fixes a security issue and a bug in 2.1.5. +CVE-2019-6975: Memory exhaustion in ``django.utils.numberformat.format()`` +-------------------------------------------------------------------------- + +If ``django.utils.numberformat.format()`` -- used by ``contrib.admin`` as well +as the the ``floatformat``, ``filesizeformat``, and ``intcomma`` templates +filters -- received a ``Decimal`` with a large number of digits or a large +exponent, it could lead to significant memory usage due to a call to +``'{:f}'.format()``. + +To avoid this, decimals with more than 200 digits are now formatted using +scientific notation. + Bugfixes ======== diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py index b78b37551d..3d656025ab 100644 --- a/tests/utils_tests/test_numberformat.py +++ b/tests/utils_tests/test_numberformat.py @@ -80,6 +80,25 @@ class TestNumberFormat(SimpleTestCase): ) self.assertEqual(nformat(Decimal('3.'), '.'), '3') self.assertEqual(nformat(Decimal('3.0'), '.'), '3.0') + # Very large & small numbers. + tests = [ + ('9e9999', None, '9e+9999'), + ('9e9999', 3, '9.000e+9999'), + ('9e201', None, '9e+201'), + ('9e200', None, '9e+200'), + ('1.2345e999', 2, '1.23e+999'), + ('9e-999', None, '9e-999'), + ('1e-7', 8, '0.00000010'), + ('1e-8', 8, '0.00000001'), + ('1e-9', 8, '0.00000000'), + ('1e-10', 8, '0.00000000'), + ('1e-11', 8, '0.00000000'), + ('1' + ('0' * 300), 3, '1.000e+300'), + ('0.{}1234'.format('0' * 299), 3, '1.234e-300'), + ] + for value, decimal_pos, expected_value in tests: + with self.subTest(value=value): + self.assertEqual(nformat(Decimal(value), '.', decimal_pos), expected_value) def test_decimal_subclass(self): class EuroDecimal(Decimal):