From 0504af64292071e1a9565193ea8265c60600f7d7 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:38:34 +0200 Subject: [PATCH] [5.1.x] Fixed CVE-2024-41989 -- Prevented excessive memory consumption in floatformat. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Elias Myllymäki for the report. Co-authored-by: Shai Berger --- django/template/defaultfilters.py | 13 +++++++++++++ docs/releases/4.2.15.txt | 9 +++++++++ docs/releases/5.0.8.txt | 9 +++++++++ .../filter_tests/test_floatformat.py | 17 +++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 02cac06bcfd..66c6e76d207 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -162,6 +162,19 @@ def floatformat(text, arg=-1): except ValueError: return input_val + _, digits, exponent = d.as_tuple() + try: + number_of_digits_and_exponent_sum = len(digits) + abs(exponent) + except TypeError: + # Exponent values can be "F", "n", "N". + number_of_digits_and_exponent_sum = 0 + + # Values with more than 200 digits, or with a large exponent, are returned "as is" + # to avoid high memory consumption and potential denial-of-service attacks. + # The cut-off of 200 is consistent with django.utils.numberformat.floatformat(). + if number_of_digits_and_exponent_sum > 200: + return input_val + try: m = int(d) - d except (ValueError, OverflowError, InvalidOperation): diff --git a/docs/releases/4.2.15.txt b/docs/releases/4.2.15.txt index d312f8580f1..f3fdb0a3cfe 100644 --- a/docs/releases/4.2.15.txt +++ b/docs/releases/4.2.15.txt @@ -7,6 +7,15 @@ Django 4.2.15 release notes Django 4.2.15 fixes three security issues with severity "moderate", one security issue with severity "high", and a regression in 4.2.14. +CVE-2024-41989: Memory exhaustion in ``django.utils.numberformat.floatformat()`` +================================================================================ + +If :tfilter:`floatformat` received a string representation of a number in +scientific notation with a large exponent, it could lead to significant memory +consumption. + +To avoid this, decimals with more than 200 digits are now returned as is. + Bugfixes ======== diff --git a/docs/releases/5.0.8.txt b/docs/releases/5.0.8.txt index 704ecf2c610..c371e4af0b6 100644 --- a/docs/releases/5.0.8.txt +++ b/docs/releases/5.0.8.txt @@ -7,6 +7,15 @@ Django 5.0.8 release notes Django 5.0.8 fixes three security issues with severity "moderate", one security issue with severity "high", and several bugs in 5.0.7. +CVE-2024-41989: Memory exhaustion in ``django.utils.numberformat.floatformat()`` +================================================================================ + +If :tfilter:`floatformat` received a string representation of a number in +scientific notation with a large exponent, it could lead to significant memory +consumption. + +To avoid this, decimals with more than 200 digits are now returned as is. + Bugfixes ======== diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py index 145858b75fd..3d6c34a5523 100644 --- a/tests/template_tests/filter_tests/test_floatformat.py +++ b/tests/template_tests/filter_tests/test_floatformat.py @@ -73,6 +73,7 @@ class FunctionTests(SimpleTestCase): self.assertEqual(floatformat(1.5e-15, 20), "0.00000000000000150000") self.assertEqual(floatformat(1.5e-15, -20), "0.00000000000000150000") self.assertEqual(floatformat(1.00000000000000015, 16), "1.0000000000000002") + self.assertEqual(floatformat("1e199"), "1" + "0" * 199) def test_invalid_inputs(self): cases = [ @@ -169,6 +170,22 @@ class FunctionTests(SimpleTestCase): self.assertEqual(floatformat(pos_inf), "inf") self.assertEqual(floatformat(neg_inf), "-inf") self.assertEqual(floatformat(pos_inf / pos_inf), "nan") + self.assertEqual(floatformat("inf"), "inf") + self.assertEqual(floatformat("NaN"), "NaN") + + def test_too_many_digits_to_render(self): + cases = [ + "1e200", + "1E200", + "1E10000000000000000", + "-1E10000000000000000", + "1e10000000000000000", + "-1e10000000000000000", + "1" + "0" * 1_000_000, + ] + for value in cases: + with self.subTest(value=value): + self.assertEqual(floatformat(value), value) def test_float_dunder_method(self): class FloatWrapper: