[1.11.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.

Backport of 402c0caa851e265410fbcaa55318f22d2bf22ee2 from master.
This commit is contained in:
Carlton Gibson 2019-02-11 11:15:45 +01:00
parent 11cb39514d
commit 0bbb560183
3 changed files with 44 additions and 1 deletions

View File

@ -30,7 +30,20 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='',
# sign # sign
sign = '' sign = ''
if isinstance(number, Decimal): 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,
)
return '{}e{}'.format(coefficient, exponent)
else:
str_number = '{:f}'.format(number)
else: else:
str_number = six.text_type(number) str_number = six.text_type(number)
if str_number[0] == '-': if str_number[0] == '-':

View File

@ -5,3 +5,15 @@ Django 1.11.19 release notes
*February 11, 2019* *February 11, 2019*
Django 1.11.19 fixes a security issue in 1.11.18. 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.

View File

@ -60,6 +60,24 @@ class TestNumberFormat(TestCase):
self.assertEqual(nformat(Decimal('1234'), '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34') self.assertEqual(nformat(Decimal('1234'), '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34')
self.assertEqual(nformat(Decimal('-1234.33'), '.', decimal_pos=1), '-1234.3') self.assertEqual(nformat(Decimal('-1234.33'), '.', decimal_pos=1), '-1234.3')
self.assertEqual(nformat(Decimal('0.00000001'), '.', decimal_pos=8), '0.00000001') self.assertEqual(nformat(Decimal('0.00000001'), '.', decimal_pos=8), '0.00000001')
# 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:
self.assertEqual(nformat(Decimal(value), '.', decimal_pos), expected_value)
def test_decimal_subclass(self): def test_decimal_subclass(self):
class EuroDecimal(Decimal): class EuroDecimal(Decimal):