summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorShai Berger <shai@platonix.com>2025-10-11 21:42:56 +0300
committerNatalia <124304+nessita@users.noreply.github.com>2025-12-02 09:27:50 -0300
commit99e7d22f55497278d0bcb2e15e72ef532e62a31d (patch)
treed4749dee8c162590ea5a5a7e0cd118bcb4f22a24 /tests
parent479415ce5249bcdebeb6570c72df2a87f45a7bbf (diff)
[5.2.x] 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 <jacobtylerwalls@gmail.com> Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 50efb718b31333051bc2dcb06911b8fa1358c98c from main.
Diffstat (limited to 'tests')
-rw-r--r--tests/serializers/test_deserialization.py54
1 files changed, 54 insertions, 0 deletions
diff --git a/tests/serializers/test_deserialization.py b/tests/serializers/test_deserialization.py
index 0bbb46b7ce..a718a99038 100644
--- a/tests/serializers/test_deserialization.py
+++ b/tests/serializers/test_deserialization.py
@@ -1,11 +1,15 @@
import json
+import time
import unittest
from django.core.serializers.base import DeserializationError, DeserializedObject
from django.core.serializers.json import Deserializer as JsonDeserializer
from django.core.serializers.jsonl import Deserializer as JsonlDeserializer
from django.core.serializers.python import Deserializer
+from django.core.serializers.xml_serializer import Deserializer as XMLDeserializer
+from django.db import models
from django.test import SimpleTestCase
+from django.test.utils import garbage_collect
from .models import Author
@@ -133,3 +137,53 @@ class TestDeserializer(SimpleTestCase):
self.assertEqual(first_item.object, self.jane)
self.assertEqual(second_item.object, self.joe)
+
+ 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 = XMLDeserializer(crafted_xml)
+ garbage_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)