Details: https://nvd.nist.gov/vuln/detail/CVE-2025-64460

Backport the patch that explicitly references this CVE in its
commit message.

Signed-off-by: Gyorgy Sarvari <[email protected]>
---
 .../CVE-2025-64460.patch                      | 199 ++++++++++++++++++
 .../python/python3-django_5.0.14.bb           |   1 +
 2 files changed, 200 insertions(+)
 create mode 100644 
meta-python/recipes-devtools/python/python3-django-5.0.14/CVE-2025-64460.patch

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 @@
+From f8fd8a25e04e2b6601fc9cdb69dea41db7b4ff18 Mon Sep 17 00:00:00 2001
+From: Shai Berger <[email protected]>
+Date: Sat, 11 Oct 2025 21:42:56 +0300
+Subject: [PATCH] Fixed CVE-2025-64460 -- Corrected quadratic inner text
+ accumulation in XML serializer.
+
+Previously, `getInnerText()` recursively used `list.extend()` on strings,
+which added each character from child nodes as a separate list element.
+On deeply nested XML content, this caused the overall deserialization
+work to grow quadratically with input size, potentially allowing
+disproportionate CPU consumption for crafted XML.
+
+The fix separates collection of inner texts from joining them, so that
+each subtree is joined only once, reducing the complexity to linear in
+the size of the input. These changes also include a mitigation for a
+xml.dom.minidom performance issue.
+
+Thanks Seokchan Yoon (https://ch4n3.kr/) for report.
+
+Co-authored-by: Jacob Walls <[email protected]>
+Co-authored-by: Natalia <[email protected]>
+
+Backport of 50efb718b31333051bc2dcb06911b8fa1358c98c from main.
+
+CVE: CVE-2025-64460
+Upstream-Status: Backport 
[https://github.com/django/django/commit/0db9ea4669312f1f4973e09f4bca06ab9c1ec74b]
+Signed-off-by: Gyorgy Sarvari <[email protected]>
+---
+ django/core/serializers/xml_serializer.py | 39 +++++++++++++---
+ docs/topics/serialization.txt             |  2 +
+ tests/serializers/test_xml.py             | 55 ++++++++++++++++++++++-
+ 3 files changed, 89 insertions(+), 7 deletions(-)
+
+diff --git a/django/core/serializers/xml_serializer.py 
b/django/core/serializers/xml_serializer.py
+index 16b6977..b2837bc 100644
+--- a/django/core/serializers/xml_serializer.py
++++ b/django/core/serializers/xml_serializer.py
+@@ -3,7 +3,8 @@ XML serializer.
+ """
+ 
+ import json
+-from xml.dom import pulldom
++from contextlib import contextmanager
++from xml.dom import minidom, pulldom
+ from xml.sax import handler
+ from xml.sax.expatreader import ExpatParser as _ExpatParser
+ 
+@@ -15,6 +16,25 @@ from django.db import DEFAULT_DB_ALIAS, models
+ from django.utils.xmlutils import SimplerXMLGenerator, 
UnserializableContentError
+ 
+ 
++@contextmanager
++def fast_cache_clearing():
++    """Workaround for performance issues in minidom document checks.
++
++    Speeds up repeated DOM operations by skipping unnecessary full traversal
++    of the DOM tree.
++    """
++    module_helper_was_lambda = False
++    if original_fn := getattr(minidom, "_in_document", None):
++        module_helper_was_lambda = original_fn.__name__ == "<lambda>"
++        if not module_helper_was_lambda:
++            minidom._in_document = lambda node: bool(node.ownerDocument)
++    try:
++        yield
++    finally:
++        if original_fn and not module_helper_was_lambda:
++            minidom._in_document = original_fn
++
++
+ class Serializer(base.Serializer):
+     """Serialize a QuerySet to XML."""
+ 
+@@ -209,7 +229,8 @@ class Deserializer(base.Deserializer):
+     def __next__(self):
+         for event, node in self.event_stream:
+             if event == "START_ELEMENT" and node.nodeName == "object":
+-                self.event_stream.expandNode(node)
++                with fast_cache_clearing():
++                    self.event_stream.expandNode(node)
+                 return self._handle_object(node)
+         raise StopIteration
+ 
+@@ -393,19 +414,25 @@ class Deserializer(base.Deserializer):
+ 
+ def getInnerText(node):
+     """Get all the inner text of a DOM node (recursively)."""
++    inner_text_list = getInnerTextList(node)
++    return "".join(inner_text_list)
++
++
++def getInnerTextList(node):
++    """Return a list of the inner texts of a DOM node (recursively)."""
+     # inspired by 
https://mail.python.org/pipermail/xml-sig/2005-March/011022.html
+-    inner_text = []
++    result = []
+     for child in node.childNodes:
+         if (
+             child.nodeType == child.TEXT_NODE
+             or child.nodeType == child.CDATA_SECTION_NODE
+         ):
+-            inner_text.append(child.data)
++            result.append(child.data)
+         elif child.nodeType == child.ELEMENT_NODE:
+-            inner_text.extend(getInnerText(child))
++            result.extend(getInnerTextList(child))
+         else:
+             pass
+-    return "".join(inner_text)
++    return result
+ 
+ 
+ # Below code based on Christian Heimes' defusedxml
+diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
+index 0bb5764..dc403ca 100644
+--- a/docs/topics/serialization.txt
++++ b/docs/topics/serialization.txt
+@@ -173,6 +173,8 @@ Identifier  Information
+ .. _jsonl: https://jsonlines.org/
+ .. _PyYAML: https://pyyaml.org/
+ 
++.. _serialization-formats-xml:
++
+ XML
+ ---
+ 
+diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py
+index c9df2f2..03462cf 100644
+--- a/tests/serializers/test_xml.py
++++ b/tests/serializers/test_xml.py
+@@ -1,7 +1,10 @@
++import gc
++import time
+ from xml.dom import minidom
+ 
+ from django.core import serializers
+-from django.core.serializers.xml_serializer import DTDForbidden
++from django.core.serializers.xml_serializer import Deserializer, DTDForbidden
++from django.db import models
+ from django.test import TestCase, TransactionTestCase
+ 
+ from .tests import SerializersTestBase, SerializersTransactionTestBase
+@@ -90,6 +93,56 @@ class XmlSerializerTestCase(SerializersTestBase, TestCase):
+         with self.assertRaises(DTDForbidden):
+             next(serializers.deserialize("xml", xml))
+ 
++    def test_crafted_xml_performance(self):
++        """The time to process invalid inputs is not quadratic."""
++
++        def build_crafted_xml(depth, leaf_text_len):
++            nested_open = "<nested>" * depth
++            nested_close = "</nested>" * depth
++            leaf = "x" * leaf_text_len
++            field_content = f"{nested_open}{leaf}{nested_close}"
++            return f"""
++                <django-objects version="1.0">
++                   <object model="contenttypes.contenttype" pk="1">
++                      <field name="app_label">{field_content}</field>
++                      <field name="model">m</field>
++                   </object>
++                </django-objects>
++            """
++
++        def deserialize(crafted_xml):
++            iterator = Deserializer(crafted_xml)
++            gc.collect()
++
++            start_time = time.perf_counter()
++            result = list(iterator)
++            end_time = time.perf_counter()
++
++            self.assertEqual(len(result), 1)
++            self.assertIsInstance(result[0].object, models.Model)
++            return end_time - start_time
++
++        def assertFactor(label, params, factor=2):
++            factors = []
++            prev_time = None
++            for depth, length in params:
++                crafted_xml = build_crafted_xml(depth, length)
++                elapsed = deserialize(crafted_xml)
++                if prev_time is not None:
++                    factors.append(elapsed / prev_time)
++                prev_time = elapsed
++
++            with self.subTest(label):
++                # Assert based on the average factor to reduce test flakiness.
++                self.assertLessEqual(sum(factors) / len(factors), factor)
++
++        assertFactor(
++            "varying depth, varying length",
++            [(50, 2000), (100, 4000), (200, 8000), (400, 16000), (800, 
32000)],
++            2,
++        )
++        assertFactor("constant depth, varying length", [(100, 1), (100, 
1000)], 2)
++
+ 
+ class XmlSerializerTransactionTestCase(
+     SerializersTransactionTestBase, TransactionTestCase
diff --git a/meta-python/recipes-devtools/python/python3-django_5.0.14.bb 
b/meta-python/recipes-devtools/python/python3-django_5.0.14.bb
index d176123893..c2c44b4cc7 100644
--- a/meta-python/recipes-devtools/python/python3-django_5.0.14.bb
+++ b/meta-python/recipes-devtools/python/python3-django_5.0.14.bb
@@ -4,6 +4,7 @@ inherit setuptools3
 # Windows-specific DoS via NFKC normalization, not applicable to Linux
 CVE_STATUS[CVE-2025-27556] = "not-applicable-platform: Issue only applies on 
Windows"
 
+SRC_URI += "file://CVE-2025-64460.patch"
 SRC_URI[sha256sum] = 
"29019a5763dbd48da1720d687c3522ef40d1c61be6fb2fad27ed79e9f655bc11"
 
 RDEPENDS:${PN} += "\
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#124252): 
https://lists.openembedded.org/g/openembedded-devel/message/124252
Mute This Topic: https://lists.openembedded.org/mt/117686819/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to