summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch')
-rw-r--r--meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch199
1 files changed, 199 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch b/meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch
new file mode 100644
index 0000000000..c7a2928536
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch
@@ -0,0 +1,199 @@
1From f8fd8a25e04e2b6601fc9cdb69dea41db7b4ff18 Mon Sep 17 00:00:00 2001
2From: Shai Berger <shai@platonix.com>
3Date: Sat, 11 Oct 2025 21:42:56 +0300
4Subject: [PATCH] Fixed CVE-2025-64460 -- Corrected quadratic inner text
5 accumulation in XML serializer.
6
7Previously, `getInnerText()` recursively used `list.extend()` on strings,
8which added each character from child nodes as a separate list element.
9On deeply nested XML content, this caused the overall deserialization
10work to grow quadratically with input size, potentially allowing
11disproportionate CPU consumption for crafted XML.
12
13The fix separates collection of inner texts from joining them, so that
14each subtree is joined only once, reducing the complexity to linear in
15the size of the input. These changes also include a mitigation for a
16xml.dom.minidom performance issue.
17
18Thanks Seokchan Yoon (https://ch4n3.kr/) for report.
19
20Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
21Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
22
23Backport of 50efb718b31333051bc2dcb06911b8fa1358c98c from main.
24
25CVE: CVE-2025-64460
26Upstream-Status: Backport [https://github.com/django/django/commit/0db9ea4669312f1f4973e09f4bca06ab9c1ec74b]
27Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
28---
29 django/core/serializers/xml_serializer.py | 39 +++++++++++++---
30 docs/topics/serialization.txt | 2 +
31 tests/serializers/test_xml.py | 55 ++++++++++++++++++++++-
32 3 files changed, 89 insertions(+), 7 deletions(-)
33
34diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
35index 16b6977..b2837bc 100644
36--- a/django/core/serializers/xml_serializer.py
37+++ b/django/core/serializers/xml_serializer.py
38@@ -3,7 +3,8 @@ XML serializer.
39 """
40
41 import json
42-from xml.dom import pulldom
43+from contextlib import contextmanager
44+from xml.dom import minidom, pulldom
45 from xml.sax import handler
46 from xml.sax.expatreader import ExpatParser as _ExpatParser
47
48@@ -15,6 +16,25 @@ from django.db import DEFAULT_DB_ALIAS, models
49 from django.utils.xmlutils import SimplerXMLGenerator, UnserializableContentError
50
51
52+@contextmanager
53+def fast_cache_clearing():
54+ """Workaround for performance issues in minidom document checks.
55+
56+ Speeds up repeated DOM operations by skipping unnecessary full traversal
57+ of the DOM tree.
58+ """
59+ module_helper_was_lambda = False
60+ if original_fn := getattr(minidom, "_in_document", None):
61+ module_helper_was_lambda = original_fn.__name__ == "<lambda>"
62+ if not module_helper_was_lambda:
63+ minidom._in_document = lambda node: bool(node.ownerDocument)
64+ try:
65+ yield
66+ finally:
67+ if original_fn and not module_helper_was_lambda:
68+ minidom._in_document = original_fn
69+
70+
71 class Serializer(base.Serializer):
72 """Serialize a QuerySet to XML."""
73
74@@ -209,7 +229,8 @@ class Deserializer(base.Deserializer):
75 def __next__(self):
76 for event, node in self.event_stream:
77 if event == "START_ELEMENT" and node.nodeName == "object":
78- self.event_stream.expandNode(node)
79+ with fast_cache_clearing():
80+ self.event_stream.expandNode(node)
81 return self._handle_object(node)
82 raise StopIteration
83
84@@ -393,19 +414,25 @@ class Deserializer(base.Deserializer):
85
86 def getInnerText(node):
87 """Get all the inner text of a DOM node (recursively)."""
88+ inner_text_list = getInnerTextList(node)
89+ return "".join(inner_text_list)
90+
91+
92+def getInnerTextList(node):
93+ """Return a list of the inner texts of a DOM node (recursively)."""
94 # inspired by https://mail.python.org/pipermail/xml-sig/2005-March/011022.html
95- inner_text = []
96+ result = []
97 for child in node.childNodes:
98 if (
99 child.nodeType == child.TEXT_NODE
100 or child.nodeType == child.CDATA_SECTION_NODE
101 ):
102- inner_text.append(child.data)
103+ result.append(child.data)
104 elif child.nodeType == child.ELEMENT_NODE:
105- inner_text.extend(getInnerText(child))
106+ result.extend(getInnerTextList(child))
107 else:
108 pass
109- return "".join(inner_text)
110+ return result
111
112
113 # Below code based on Christian Heimes' defusedxml
114diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
115index 0bb5764..dc403ca 100644
116--- a/docs/topics/serialization.txt
117+++ b/docs/topics/serialization.txt
118@@ -173,6 +173,8 @@ Identifier Information
119 .. _jsonl: https://jsonlines.org/
120 .. _PyYAML: https://pyyaml.org/
121
122+.. _serialization-formats-xml:
123+
124 XML
125 ---
126
127diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py
128index c9df2f2..03462cf 100644
129--- a/tests/serializers/test_xml.py
130+++ b/tests/serializers/test_xml.py
131@@ -1,7 +1,10 @@
132+import gc
133+import time
134 from xml.dom import minidom
135
136 from django.core import serializers
137-from django.core.serializers.xml_serializer import DTDForbidden
138+from django.core.serializers.xml_serializer import Deserializer, DTDForbidden
139+from django.db import models
140 from django.test import TestCase, TransactionTestCase
141
142 from .tests import SerializersTestBase, SerializersTransactionTestBase
143@@ -90,6 +93,56 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase):
144 with self.assertRaises(DTDForbidden):
145 next(serializers.deserialize("xml", xml))
146
147+ def test_crafted_xml_performance(self):
148+ """The time to process invalid inputs is not quadratic."""
149+
150+ def build_crafted_xml(depth, leaf_text_len):
151+ nested_open = "<nested>" * depth
152+ nested_close = "</nested>" * depth
153+ leaf = "x" * leaf_text_len
154+ field_content = f"{nested_open}{leaf}{nested_close}"
155+ return f"""
156+ <django-objects version="1.0">
157+ <object model="contenttypes.contenttype" pk="1">
158+ <field name="app_label">{field_content}</field>
159+ <field name="model">m</field>
160+ </object>
161+ </django-objects>
162+ """
163+
164+ def deserialize(crafted_xml):
165+ iterator = Deserializer(crafted_xml)
166+ gc.collect()
167+
168+ start_time = time.perf_counter()
169+ result = list(iterator)
170+ end_time = time.perf_counter()
171+
172+ self.assertEqual(len(result), 1)
173+ self.assertIsInstance(result[0].object, models.Model)
174+ return end_time - start_time
175+
176+ def assertFactor(label, params, factor=2):
177+ factors = []
178+ prev_time = None
179+ for depth, length in params:
180+ crafted_xml = build_crafted_xml(depth, length)
181+ elapsed = deserialize(crafted_xml)
182+ if prev_time is not None:
183+ factors.append(elapsed / prev_time)
184+ prev_time = elapsed
185+
186+ with self.subTest(label):
187+ # Assert based on the average factor to reduce test flakiness.
188+ self.assertLessEqual(sum(factors) / len(factors), factor)
189+
190+ assertFactor(
191+ "varying depth, varying length",
192+ [(50, 2000), (100, 4000), (200, 8000), (400, 16000), (800, 32000)],
193+ 2,
194+ )
195+ assertFactor("constant depth, varying length", [(100, 1), (100, 1000)], 2)
196+
197
198 class XmlSerializerTransactionTestCase(
199 SerializersTransactionTestBase, TransactionTestCase