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)