diff options
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-django-5.0.14')
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch | 199 |
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 @@ | |||
| 1 | From f8fd8a25e04e2b6601fc9cdb69dea41db7b4ff18 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Shai Berger <shai@platonix.com> | ||
| 3 | Date: Sat, 11 Oct 2025 21:42:56 +0300 | ||
| 4 | Subject: [PATCH] Fixed CVE-2025-64460 -- Corrected quadratic inner text | ||
| 5 | accumulation in XML serializer. | ||
| 6 | |||
| 7 | Previously, `getInnerText()` recursively used `list.extend()` on strings, | ||
| 8 | which added each character from child nodes as a separate list element. | ||
| 9 | On deeply nested XML content, this caused the overall deserialization | ||
| 10 | work to grow quadratically with input size, potentially allowing | ||
| 11 | disproportionate CPU consumption for crafted XML. | ||
| 12 | |||
| 13 | The fix separates collection of inner texts from joining them, so that | ||
| 14 | each subtree is joined only once, reducing the complexity to linear in | ||
| 15 | the size of the input. These changes also include a mitigation for a | ||
| 16 | xml.dom.minidom performance issue. | ||
| 17 | |||
| 18 | Thanks Seokchan Yoon (https://ch4n3.kr/) for report. | ||
| 19 | |||
| 20 | Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com> | ||
| 21 | Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> | ||
| 22 | |||
| 23 | Backport of 50efb718b31333051bc2dcb06911b8fa1358c98c from main. | ||
| 24 | |||
| 25 | CVE: CVE-2025-64460 | ||
| 26 | Upstream-Status: Backport [https://github.com/django/django/commit/0db9ea4669312f1f4973e09f4bca06ab9c1ec74b] | ||
| 27 | Signed-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 | |||
| 34 | diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py | ||
| 35 | index 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 | ||
| 114 | diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt | ||
| 115 | index 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 | |||
| 127 | diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py | ||
| 128 | index 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 | ||
