summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlton Gibson <carlton.gibson@noumenal.es>2019-02-11 11:08:45 +0100
committerCarlton Gibson <carlton.gibson@noumenal.es>2019-02-11 11:14:09 +0100
commit1f42f82566c9d2d73aff1c42790d6b1b243f7676 (patch)
tree427c70eeb027d84ee99094fffc5f2ca45d27daa4
parentf6f0f524c3c96830fdaf1b49ed4ca12d54d37c89 (diff)
[2.0.x] 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.
-rw-r--r--django/utils/numberformat.py15
-rw-r--r--docs/releases/1.11.19.txt12
-rw-r--r--docs/releases/2.0.11.txt12
-rw-r--r--tests/utils_tests/test_numberformat.py19
4 files changed, 57 insertions, 1 deletions
diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py
index e910be9206..5814c454a4 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/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py
index 3b815adfb8..4e6a991cf4 100644
--- a/tests/utils_tests/test_numberformat.py
+++ b/tests/utils_tests/test_numberformat.py
@@ -75,6 +75,25 @@ class TestNumberFormat(TestCase):
)
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):