From 01db9774f6bdf898bd66e542de1ef4c15ca570a7 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:18:27 -0300 Subject: [PATCH 01/11] [4.2.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index a9428ad60237..359404347659 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 18, "final", 0) +VERSION = (4, 2, 19, "alpha", 0) __version__ = get_version(VERSION) From 8769b44fdabc960dc23d1222430470809fd751b1 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:37:50 -0300 Subject: [PATCH 02/11] [4.2.x] Added CVE-2024-56374 to security archive. Backport of f2a1dcaa53626ff11b921ef142b780a8fd746d32 from main. --- docs/releases/security.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 95a6e003b217..f997fe94a3a3 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -36,6 +36,17 @@ Issues under Django's security process All security issues have been handled under versions of Django's security process. These are listed below. +January 14, 2025 - :cve:`2024-56374` +------------------------------------ + +Potential denial-of-service vulnerability in IPv6 validation. +`Full description +`__ + +* Django 5.1 :commit:`(patch) <4806731e58f3e8700a3c802e77899d54ac6021fe>` +* Django 5.0 :commit:`(patch) ` +* Django 4.2 :commit:`(patch) ` + December 4, 2024 - :cve:`2024-53907` ------------------------------------ From 043dfadbcebc752d5ab2e9ff08c7e48044d20dc2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 14 Jan 2025 23:08:50 +0100 Subject: [PATCH 03/11] [4.2.x] Fixed #36098 -- Fixed validate_ipv6_address()/validate_ipv46_address() crash for non-string values. Regression in ca2be7724e1244a4cb723de40a070f873c6e94bf. Backport of b3c5830769d8a5dbf2f974da7116fe503c9454d9 from main. --- django/utils/ipv6.py | 10 ++++++---- docs/releases/4.2.19.txt | 14 ++++++++++++++ docs/releases/index.txt | 1 + tests/utils_tests/test_ipv6.py | 18 ++++++++++++++++++ tests/validators/tests.py | 11 +++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 docs/releases/4.2.19.txt diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py index de41a97f7210..487fa7b1e102 100644 --- a/django/utils/ipv6.py +++ b/django/utils/ipv6.py @@ -49,12 +49,14 @@ def clean_ipv6_address( return str(addr) -def is_valid_ipv6_address(ip_str): +def is_valid_ipv6_address(ip_addr): """ - Return whether or not the `ip_str` string is a valid IPv6 address. + Return whether the `ip_addr` object is a valid IPv6 address. """ + if isinstance(ip_addr, ipaddress.IPv6Address): + return True try: - _ipv6_address_from_str(ip_str) - except ValueError: + _ipv6_address_from_str(ip_addr) + except (TypeError, ValueError): return False return True diff --git a/docs/releases/4.2.19.txt b/docs/releases/4.2.19.txt new file mode 100644 index 000000000000..91bc8e584293 --- /dev/null +++ b/docs/releases/4.2.19.txt @@ -0,0 +1,14 @@ +=========================== +Django 4.2.19 release notes +=========================== + +*Expected February 5, 2025* + +Django 4.2.19 fixes a regression in 4.2.18. + +Bugfixes +======== + +* Fixed a regression in Django 4.2.18 that caused ``validate_ipv6_address()`` + and ``validate_ipv46_address()`` to crash when handling non-string values + (:ticket:`36098`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index ec69c8e414bb..034821f1519c 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 4.2.19 4.2.18 4.2.17 4.2.16 diff --git a/tests/utils_tests/test_ipv6.py b/tests/utils_tests/test_ipv6.py index 2d06507fa152..3734222318f4 100644 --- a/tests/utils_tests/test_ipv6.py +++ b/tests/utils_tests/test_ipv6.py @@ -1,5 +1,7 @@ import traceback +from decimal import Decimal from io import StringIO +from ipaddress import IPv6Address from django.core.exceptions import ValidationError from django.test import SimpleTestCase @@ -24,6 +26,16 @@ def test_validates_correct_with_v4mapping(self): self.assertTrue(is_valid_ipv6_address("::ffff:254.42.16.14")) self.assertTrue(is_valid_ipv6_address("::ffff:0a0a:0a0a")) + def test_validates_correct_with_ipv6_instance(self): + cases = [ + IPv6Address("::ffff:2.125.160.216"), + IPv6Address("fe80::1"), + IPv6Address("::"), + ] + for case in cases: + with self.subTest(case=case): + self.assertIs(is_valid_ipv6_address(case), True) + def test_validates_incorrect_plain_address(self): self.assertFalse(is_valid_ipv6_address("foo")) self.assertFalse(is_valid_ipv6_address("127.0.0.1")) @@ -46,6 +58,12 @@ def test_validates_incorrect_with_v4mapping(self): self.assertFalse(is_valid_ipv6_address("::999.42.16.14")) self.assertFalse(is_valid_ipv6_address("::zzzz:0a0a")) + def test_validates_incorrect_with_non_string(self): + cases = [None, [], {}, (), Decimal("2.46"), 192.168, 42] + for case in cases: + with self.subTest(case=case): + self.assertIs(is_valid_ipv6_address(case), False) + def test_cleans_plain_address(self): self.assertEqual(clean_ipv6_address("DEAD::0:BEEF"), "dead::beef") self.assertEqual( diff --git a/tests/validators/tests.py b/tests/validators/tests.py index e99baab862d9..f4ab7fbece2f 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -1,3 +1,4 @@ +import ipaddress import re import types from datetime import datetime, timedelta @@ -381,15 +382,25 @@ (validate_ipv6_address, "fe80::1", None), (validate_ipv6_address, "::1", None), (validate_ipv6_address, "1:2:3:4:5:6:7:8", None), + (validate_ipv6_address, ipaddress.IPv6Address("::ffff:2.125.160.216"), None), + (validate_ipv6_address, ipaddress.IPv6Address("fe80::1"), None), + (validate_ipv6_address, Decimal("33.1"), ValidationError), + (validate_ipv6_address, 9.22, ValidationError), (validate_ipv6_address, "1:2", ValidationError), (validate_ipv6_address, "::zzz", ValidationError), (validate_ipv6_address, "12345::", ValidationError), (validate_ipv46_address, "1.1.1.1", None), (validate_ipv46_address, "255.0.0.0", None), (validate_ipv46_address, "0.0.0.0", None), + (validate_ipv46_address, ipaddress.IPv4Address("1.1.1.1"), None), + (validate_ipv46_address, ipaddress.IPv4Address("255.0.0.0"), None), (validate_ipv46_address, "fe80::1", None), (validate_ipv46_address, "::1", None), (validate_ipv46_address, "1:2:3:4:5:6:7:8", None), + (validate_ipv46_address, ipaddress.IPv6Address("::ffff:2.125.160.216"), None), + (validate_ipv46_address, ipaddress.IPv6Address("fe80::1"), None), + (validate_ipv46_address, Decimal("33.1"), ValidationError), + (validate_ipv46_address, 9.22, ValidationError), (validate_ipv46_address, "256.1.1.1", ValidationError), (validate_ipv46_address, "25.1.1.", ValidationError), (validate_ipv46_address, "25,1,1,1", ValidationError), From 57b0229421ad64fea6ba50a8628e6aedce017152 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:01:11 +0100 Subject: [PATCH 04/11] [4.2.x] Refs #36098 -- Fixed validate_ipv4_address() crash for non-string values. Regression in ca2be7724e1244a4cb723de40a070f873c6e94bf. --- django/core/validators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/core/validators.py b/django/core/validators.py index 4a5835dbb0d4..c5886039380d 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -272,6 +272,8 @@ def __eq__(self, other): def validate_ipv4_address(value): + if isinstance(value, ipaddress.IPv4Address): + return try: ipaddress.IPv4Address(value) except ValueError: From 7bd1ddf1d81e9120c28322ee2a0b9c11941a6af2 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Fri, 17 Jan 2025 00:13:10 -0500 Subject: [PATCH 05/11] [4.2.x] Refs #34060 -- Adjusted CVE-2024-53908 regression test for psycopg2. The lack of explicit cast for JSON literals on psycopg2 is fixed on 5.1+ by 0d8fbe2ade29f1b7bd9e6ba7a0281f5478603a43 but didn't qualify for a backport to stable/4.2.x. --- tests/model_fields/test_jsonfield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index 4c8d14bf9a17..8535aa2e8c7f 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -611,7 +611,7 @@ def test_has_key_deep(self): def test_has_key_literal_lookup(self): self.assertSequenceEqual( NullableJSONModel.objects.filter( - HasKey(Value({"foo": "bar"}, JSONField()), "foo") + HasKey(Cast(Value({"foo": "bar"}, JSONField()), JSONField()), "foo") ).order_by("id"), self.objs, ) From 83231cca9c26fbfe4835fb7c3e53a61ca1c4e7e3 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:38:24 -0300 Subject: [PATCH 06/11] [4.2.x] Added release date for 4.2.19. Backport of 294cc965efe0dfc8457aa5a8e78cb6d53abfcf92 from main. --- docs/releases/4.2.19.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/4.2.19.txt b/docs/releases/4.2.19.txt index 91bc8e584293..9bb2d3ed52dd 100644 --- a/docs/releases/4.2.19.txt +++ b/docs/releases/4.2.19.txt @@ -2,7 +2,7 @@ Django 4.2.19 release notes =========================== -*Expected February 5, 2025* +*February 5, 2025* Django 4.2.19 fixes a regression in 4.2.18. From db89d2fee7906eaadfeea59506156e9578d32412 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:55:11 -0300 Subject: [PATCH 07/11] [4.2.x] Bumped version for 4.2.19 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 359404347659..06a976fb7a28 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 19, "alpha", 0) +VERSION = (4, 2, 19, "final", 0) __version__ = get_version(VERSION) From 73e210755a90728fd7ff6f9a441c23521288a697 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:02:59 -0300 Subject: [PATCH 08/11] [4.2.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 06a976fb7a28..53e78e2dd8b5 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 19, "final", 0) +VERSION = (4, 2, 20, "alpha", 0) __version__ = get_version(VERSION) From 348e46a3e0cd80718d237a3b6d96a1c84bff317d Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:03:26 +0100 Subject: [PATCH 09/11] [4.2.x] Added stub release notes and release date for 4.2.20. Backport of ea1e3703bee28bfbe4f32ceb39ad31763353b143 from main. --- docs/releases/4.2.20.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/4.2.20.txt diff --git a/docs/releases/4.2.20.txt b/docs/releases/4.2.20.txt new file mode 100644 index 000000000000..c71fa05f43c5 --- /dev/null +++ b/docs/releases/4.2.20.txt @@ -0,0 +1,7 @@ +=========================== +Django 4.2.20 release notes +=========================== + +*March 6, 2025* + +Django 4.2.20 fixes a security issue with severity "moderate" in 4.2.19. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 034821f1519c..00e4465845f8 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 4.2.20 4.2.19 4.2.18 4.2.17 From e88f7376fe68dbf4ebaf11fad1513ce700b45860 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 25 Feb 2025 09:40:54 +0100 Subject: [PATCH 10/11] [4.2.x] Fixed CVE-2025-26699 -- Mitigated potential DoS in wordwrap template filter. Thanks sw0rd1ight for the report. Backport of 55d89e25f4115c5674cdd9b9bcba2bb2bb6d820b from main. --- django/utils/text.py | 28 +++++++------------ docs/releases/4.2.20.txt | 6 ++++ .../filter_tests/test_wordwrap.py | 11 ++++++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index e1b835e0e219..81ae88dc76d4 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,6 +1,7 @@ import gzip import re import secrets +import textwrap import unicodedata from gzip import GzipFile from gzip import compress as gzip_compress @@ -97,24 +98,15 @@ def wrap(text, width): ``width``. """ - def _generator(): - for line in text.splitlines(True): # True keeps trailing linebreaks - max_width = min((line.endswith("\n") and width + 1 or width), width) - while len(line) > max_width: - space = line[: max_width + 1].rfind(" ") + 1 - if space == 0: - space = line.find(" ") + 1 - if space == 0: - yield line - line = "" - break - yield "%s\n" % line[: space - 1] - line = line[space:] - max_width = min((line.endswith("\n") and width + 1 or width), width) - if line: - yield line - - return "".join(_generator()) + wrapper = textwrap.TextWrapper( + width=width, + break_long_words=False, + break_on_hyphens=False, + ) + result = [] + for line in text.splitlines(True): + result.extend(wrapper.wrap(line)) + return "\n".join(result) class Truncator(SimpleLazyObject): diff --git a/docs/releases/4.2.20.txt b/docs/releases/4.2.20.txt index c71fa05f43c5..5849fe2a42ed 100644 --- a/docs/releases/4.2.20.txt +++ b/docs/releases/4.2.20.txt @@ -5,3 +5,9 @@ Django 4.2.20 release notes *March 6, 2025* Django 4.2.20 fixes a security issue with severity "moderate" in 4.2.19. + +CVE-2025-26699: Potential denial-of-service vulnerability in ``django.utils.text.wrap()`` +========================================================================================= + +The ``wrap()`` and :tfilter:`wordwrap` template filter were subject to a +potential denial-of-service attack when used with very long strings. diff --git a/tests/template_tests/filter_tests/test_wordwrap.py b/tests/template_tests/filter_tests/test_wordwrap.py index 88fbd274da94..4afa1dd234f1 100644 --- a/tests/template_tests/filter_tests/test_wordwrap.py +++ b/tests/template_tests/filter_tests/test_wordwrap.py @@ -78,3 +78,14 @@ def test_wrap_lazy_string(self): "this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\n" "I'm afraid", ) + + def test_wrap_long_text(self): + long_text = ( + "this is a long paragraph of text that really needs" + " to be wrapped I'm afraid " * 20_000 + ) + self.assertIn( + "this is a\nlong\nparagraph\nof text\nthat\nreally\nneeds to\nbe wrapped\n" + "I'm afraid", + wordwrap(long_text, 10), + ) From 35c58a7924aa07e87e925f6eea978c278f143f36 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:03:51 +0100 Subject: [PATCH 11/11] [4.2.x] Bumped version for 4.2.20 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 53e78e2dd8b5..dddc14f597a5 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (4, 2, 20, "alpha", 0) +VERSION = (4, 2, 20, "final", 0) __version__ = get_version(VERSION)