From 2fba573e878946d44bcaea7ecd886bd1b297831a Mon Sep 17 00:00:00 2001 From: bbrucezhang Date: Fri, 27 Jun 2025 15:41:02 +0800 Subject: [PATCH] Fix CVE-2024-56374 --- Fixed-CVE-2024-56374.patch | 269 +++++++++++++++++++++++++++++++++++++ python-django.spec | 7 +- 2 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 Fixed-CVE-2024-56374.patch diff --git a/Fixed-CVE-2024-56374.patch b/Fixed-CVE-2024-56374.patch new file mode 100644 index 0000000..1df55a7 --- /dev/null +++ b/Fixed-CVE-2024-56374.patch @@ -0,0 +1,269 @@ +From b1ec7e2cc4f0ce5a60ad2fc8215ca0ce61e3713e Mon Sep 17 00:00:00 2001 +From: Your Name +Date: Fri, 27 Jun 2025 11:02:40 +0800 +Subject: [PATCH] Fixed CVE-2024-56374 -- Mitigated potential DoS in IPv6 + validation + +--- + django/db/models/fields/__init__.py | 6 ++-- + django/forms/fields.py | 7 ++-- + django/utils/ipv6.py | 19 +++++++++-- + docs/ref/forms/fields.txt | 13 ++++++-- + .../field_tests/test_genericipaddressfield.py | 33 ++++++++++++++++++- + tests/utils_tests/test_ipv6.py | 31 +++++++++++++++-- + 6 files changed, 95 insertions(+), 14 deletions(-) + +diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py +index 81e32f2..bb3f644 100644 +--- a/django/db/models/fields/__init__.py ++++ b/django/db/models/fields/__init__.py +@@ -30,7 +30,7 @@ from django.utils.dateparse import ( + ) + from django.utils.duration import duration_microseconds, duration_string + from django.utils.functional import Promise, cached_property +-from django.utils.ipv6 import clean_ipv6_address ++from django.utils.ipv6 import MAX_IPV6_ADDRESS_LENGTH, clean_ipv6_address + from django.utils.itercompat import is_iterable + from django.utils.text import capfirst + from django.utils.translation import gettext_lazy as _ +@@ -2220,7 +2220,7 @@ class GenericIPAddressField(Field): + invalid_error_message, + ) = validators.ip_address_validators(protocol, unpack_ipv4) + self.default_error_messages["invalid"] = invalid_error_message +- kwargs["max_length"] = 39 ++ kwargs["max_length"] = MAX_IPV6_ADDRESS_LENGTH + super().__init__(verbose_name, name, *args, **kwargs) + + def check(self, **kwargs): +@@ -2247,7 +2247,7 @@ class GenericIPAddressField(Field): + kwargs["unpack_ipv4"] = self.unpack_ipv4 + if self.protocol != "both": + kwargs["protocol"] = self.protocol +- if kwargs.get("max_length") == 39: ++ if kwargs.get("max_length") == self.max_length: + del kwargs["max_length"] + return name, path, args, kwargs + +diff --git a/django/forms/fields.py b/django/forms/fields.py +index f00d567..fe6cacc 100644 +--- a/django/forms/fields.py ++++ b/django/forms/fields.py +@@ -46,7 +46,7 @@ from django.utils.choices import normalize_choices + from django.utils.dateparse import parse_datetime, parse_duration + from django.utils.deprecation import RemovedInDjango60Warning + from django.utils.duration import duration_string +-from django.utils.ipv6 import clean_ipv6_address ++from django.utils.ipv6 import MAX_IPV6_ADDRESS_LENGTH, clean_ipv6_address + from django.utils.regex_helper import _lazy_re_compile + from django.utils.translation import gettext_lazy as _ + from django.utils.translation import ngettext_lazy +@@ -1295,6 +1295,7 @@ class GenericIPAddressField(CharField): + self.default_validators = validators.ip_address_validators( + protocol, unpack_ipv4 + )[0] ++ kwargs.setdefault("max_length", MAX_IPV6_ADDRESS_LENGTH) + super().__init__(**kwargs) + + def to_python(self, value): +@@ -1302,7 +1303,9 @@ class GenericIPAddressField(CharField): + return "" + value = value.strip() + if value and ":" in value: +- return clean_ipv6_address(value, self.unpack_ipv4) ++ return clean_ipv6_address( ++ value, self.unpack_ipv4, max_length=self.max_length ++ ) + return value + + +diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py +index 88dd6ec..de41a97 100644 +--- a/django/utils/ipv6.py ++++ b/django/utils/ipv6.py +@@ -3,9 +3,22 @@ import ipaddress + from django.core.exceptions import ValidationError + from django.utils.translation import gettext_lazy as _ + ++MAX_IPV6_ADDRESS_LENGTH = 39 ++ ++ ++def _ipv6_address_from_str(ip_str, max_length=MAX_IPV6_ADDRESS_LENGTH): ++ if len(ip_str) > max_length: ++ raise ValueError( ++ f"Unable to convert {ip_str} to an IPv6 address (value too long)." ++ ) ++ return ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str))) ++ + + def clean_ipv6_address( +- ip_str, unpack_ipv4=False, error_message=_("This is not a valid IPv6 address.") ++ ip_str, ++ unpack_ipv4=False, ++ error_message=_("This is not a valid IPv6 address."), ++ max_length=MAX_IPV6_ADDRESS_LENGTH, + ): + """ + Clean an IPv6 address string. +@@ -24,7 +37,7 @@ def clean_ipv6_address( + Return a compressed IPv6 address or the same value. + """ + try: +- addr = ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str))) ++ addr = _ipv6_address_from_str(ip_str, max_length) + except ValueError: + raise ValidationError(error_message, code="invalid") + +@@ -41,7 +54,7 @@ def is_valid_ipv6_address(ip_str): + Return whether or not the `ip_str` string is a valid IPv6 address. + """ + try: +- ipaddress.IPv6Address(ip_str) ++ _ipv6_address_from_str(ip_str) + except ValueError: + return False + return True +diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt +index 70c5900..82ed92f 100644 +--- a/docs/ref/forms/fields.txt ++++ b/docs/ref/forms/fields.txt +@@ -774,7 +774,7 @@ For each field, we describe the default widget used if you don't specify + * Empty value: ``''`` (an empty string) + * Normalizes to: A string. IPv6 addresses are normalized as described below. + * Validates that the given value is a valid IP address. +- * Error message keys: ``required``, ``invalid`` ++ * Error message keys: ``required``, ``invalid``, ``max_length`` + + The IPv6 address normalization follows :rfc:`4291#section-2.2` section 2.2, + including using the IPv4 format suggested in paragraph 3 of that section, like +@@ -782,7 +782,7 @@ For each field, we describe the default widget used if you don't specify + ``2001::1``, and ``::ffff:0a0a:0a0a`` to ``::ffff:10.10.10.10``. All characters + are converted to lowercase. + +- Takes two optional arguments: ++ Takes three optional arguments: + + .. attribute:: protocol + +@@ -797,6 +797,15 @@ For each field, we describe the default widget used if you don't specify + ``192.0.2.1``. Default is disabled. Can only be used + when ``protocol`` is set to ``'both'``. + ++ .. attribute:: max_length ++ ++ Defaults to 39, and behaves the same way as it does for ++ :class:`CharField`. ++ ++ .. versionchanged:: 4.2.18 ++ ++ The default value for ``max_length`` was set to 39 characters. ++ + ``ImageField`` + -------------- + +diff --git a/tests/forms_tests/field_tests/test_genericipaddressfield.py b/tests/forms_tests/field_tests/test_genericipaddressfield.py +index 80722f5..ef00a72 100644 +--- a/tests/forms_tests/field_tests/test_genericipaddressfield.py ++++ b/tests/forms_tests/field_tests/test_genericipaddressfield.py +@@ -1,6 +1,7 @@ + from django.core.exceptions import ValidationError + from django.forms import GenericIPAddressField + from django.test import SimpleTestCase ++from django.utils.ipv6 import MAX_IPV6_ADDRESS_LENGTH + + + class GenericIPAddressFieldTest(SimpleTestCase): +@@ -125,6 +126,35 @@ class GenericIPAddressFieldTest(SimpleTestCase): + ): + f.clean("1:2") + ++ def test_generic_ipaddress_max_length_custom(self): ++ # Valid IPv4-mapped IPv6 address, len 45. ++ addr = "0000:0000:0000:0000:0000:ffff:192.168.100.228" ++ f = GenericIPAddressField(max_length=len(addr)) ++ f.clean(addr) ++ ++ def test_generic_ipaddress_max_length_validation_error(self): ++ # Valid IPv4-mapped IPv6 address, len 45. ++ addr = "0000:0000:0000:0000:0000:ffff:192.168.100.228" ++ ++ cases = [ ++ ({}, MAX_IPV6_ADDRESS_LENGTH), # Default value. ++ ({"max_length": len(addr) - 1}, len(addr) - 1), ++ ] ++ for kwargs, max_length in cases: ++ max_length_plus_one = max_length + 1 ++ msg = ( ++ f"Ensure this value has at most {max_length} characters (it has " ++ f"{max_length_plus_one}).'" ++ ) ++ with self.subTest(max_length=max_length): ++ f = GenericIPAddressField(**kwargs) ++ with self.assertRaisesMessage(ValidationError, msg): ++ f.clean("x" * max_length_plus_one) ++ with self.assertRaisesMessage( ++ ValidationError, "This is not a valid IPv6 address." ++ ): ++ f.clean(addr) ++ + def test_generic_ipaddress_as_generic_not_required(self): + f = GenericIPAddressField(required=False) + self.assertEqual(f.clean(""), "") +@@ -150,7 +180,8 @@ class GenericIPAddressFieldTest(SimpleTestCase): + f.clean(" fe80::223:6cff:fe8a:2e8a "), "fe80::223:6cff:fe8a:2e8a" + ) + self.assertEqual( +- f.clean(" 2a02::223:6cff:fe8a:2e8a "), "2a02::223:6cff:fe8a:2e8a" ++ f.clean(" " * MAX_IPV6_ADDRESS_LENGTH + " 2a02::223:6cff:fe8a:2e8a "), ++ "2a02::223:6cff:fe8a:2e8a", + ) + with self.assertRaisesMessage( + ValidationError, "'This is not a valid IPv6 address.'" +diff --git a/tests/utils_tests/test_ipv6.py b/tests/utils_tests/test_ipv6.py +index bf78ed9..1754c7b 100644 +--- a/tests/utils_tests/test_ipv6.py ++++ b/tests/utils_tests/test_ipv6.py +@@ -1,9 +1,16 @@ +-import unittest ++import traceback ++from io import StringIO + +-from django.utils.ipv6 import clean_ipv6_address, is_valid_ipv6_address ++from django.core.exceptions import ValidationError ++from django.test import SimpleTestCase ++from django.utils.ipv6 import ( ++ MAX_IPV6_ADDRESS_LENGTH, ++ clean_ipv6_address, ++ is_valid_ipv6_address, ++) + + +-class TestUtilsIPv6(unittest.TestCase): ++class TestUtilsIPv6(SimpleTestCase): + def test_validates_correct_plain_address(self): + self.assertTrue(is_valid_ipv6_address("fe80::223:6cff:fe8a:2e8a")) + self.assertTrue(is_valid_ipv6_address("2a02::223:6cff:fe8a:2e8a")) +@@ -64,3 +71,21 @@ class TestUtilsIPv6(unittest.TestCase): + self.assertEqual( + clean_ipv6_address("::ffff:18.52.18.52", unpack_ipv4=True), "18.52.18.52" + ) ++ ++ def test_address_too_long(self): ++ addresses = [ ++ "0000:0000:0000:0000:0000:ffff:192.168.100.228", # IPv4-mapped IPv6 address ++ "0000:0000:0000:0000:0000:ffff:192.168.100.228%123456", # % scope/zone ++ "fe80::223:6cff:fe8a:2e8a:1234:5678:00000", # MAX_IPV6_ADDRESS_LENGTH + 1 ++ ] ++ msg = "This is the error message." ++ value_error_msg = "Unable to convert %s to an IPv6 address (value too long)." ++ for addr in addresses: ++ with self.subTest(addr=addr): ++ self.assertGreater(len(addr), MAX_IPV6_ADDRESS_LENGTH) ++ self.assertEqual(is_valid_ipv6_address(addr), False) ++ with self.assertRaisesMessage(ValidationError, msg) as ctx: ++ clean_ipv6_address(addr, error_message=msg) ++ exception_traceback = StringIO() ++ traceback.print_exception(ctx.exception, file=exception_traceback) ++ self.assertIn(value_error_msg % addr, exception_traceback.getvalue()) +-- +2.41.3 + diff --git a/python-django.spec b/python-django.spec index 86bfdfc..1439ab4 100644 --- a/python-django.spec +++ b/python-django.spec @@ -4,7 +4,7 @@ Summary: A high-level Python Web framework Name: python-django Version: 5.0.8 -Release: 4%{?dist} +Release: 5%{?dist} License: BSD URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/refs/tags/%{version}.tar.gz @@ -14,6 +14,7 @@ Patch002: python-django-5.0.8-CVE-2024-45231.patch Patch003: python-django-5.0.8-CVE-2024-53907.patch Patch004: python-django-5.0.8-CVE-2024-53908.patch patch005: python-django-5.0.8-CVE-2025-26699.patch +Patch006: Fixed-CVE-2024-56374.patch BuildArch: noarch @@ -123,6 +124,10 @@ python3 runtests.py --settings=test_sqlite --verbosity=2 --parallel 1 %changelog +* Fri Jun 27 2025 bbrucezhang - 5.0.8-5 +- [Type] security +- [DESC] Resolves: CVE-2024-56374 + * Sat Jun 21 2025 Wseelly - 5.0.8-4 - [Type] security - [DESC] Resolves: CVE-2025-26699 -- Gitee