File CVE-2025-14550.patch of Package python-Django.42494

From 3c718fe4480c62b226b6d79a34ed42375a3d145d Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 14 Jan 2026 15:25:45 +0000
Subject: [PATCH 2/7] [4.2.x] Fixed CVE-2025-14550 -- Optimized repeated header
 parsing in ASGI requests.

Thanks Jiyong Yang for the report, and Natalia Bidart, Jacob Walls, and
Shai Berger for reviews.

Backport of 9e19d8150506c7f94c27fd3778ef1f8a34c556c0 from main.
---
 django/core/handlers/asgi.py |  7 ++++---
 docs/releases/4.2.28.txt     | 12 ++++++++++++
 tests/asgi/tests.py          | 27 +++++++++++++++++++++++++++
 3 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py
index f0125e7321..d951823118 100644
--- a/django/core/handlers/asgi.py
+++ b/django/core/handlers/asgi.py
@@ -2,6 +2,7 @@ import logging
 import sys
 import tempfile
 import traceback
+from collections import defaultdict
 
 from asgiref.sync import ThreadSensitiveContext, sync_to_async
 
@@ -81,6 +82,7 @@ class ASGIRequest(HttpRequest):
             self.META["SERVER_NAME"] = "unknown"
             self.META["SERVER_PORT"] = "0"
         # Headers go into META.
+        _headers = defaultdict(list)
         for name, value in self.scope.get("headers", []):
             name = name.decode("latin1")
             if name == "content-length":
@@ -92,9 +94,8 @@ class ASGIRequest(HttpRequest):
             # HTTP/2 say only ASCII chars are allowed in headers, but decode
             # latin1 just in case.
             value = value.decode("latin1")
-            if corrected_name in self.META:
-                value = self.META[corrected_name] + "," + value
-            self.META[corrected_name] = value
+            _headers[corrected_name].append(value)
+        self.META.update({name: ",".join(value) for name, value in _headers.items()})
         # Pull out request encoding, if provided.
         self._set_content_type_params(self.META)
         # Directly assign the body file to be our stream.
diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py
index f2e293d8bc..9395c8626c 100644
--- a/tests/asgi/tests.py
+++ b/tests/asgi/tests.py
@@ -7,6 +7,7 @@ from asgiref.testing import ApplicationCommunicator
 
 from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
 from django.core.asgi import get_asgi_application
+from django.core.handlers.asgi import ASGIRequest
 from django.core.signals import request_finished, request_started
 from django.db import close_old_connections
 from django.test import (
@@ -193,6 +194,32 @@ class ASGITest(SimpleTestCase):
         self.assertEqual(response_body["type"], "http.response.body")
         self.assertEqual(response_body["body"], b"Echo!")
 
+    async def test_meta_not_modified_with_repeat_headers(self):
+        scope = self.async_request_factory._base_scope(path="/", http_version="2.0")
+        scope["headers"] = [(b"foo", b"bar")] * 200_000
+
+        setitem_count = 0
+
+        class InstrumentedDict(dict):
+            def __setitem__(self, *args, **kwargs):
+                nonlocal setitem_count
+                setitem_count += 1
+                super().__setitem__(*args, **kwargs)
+
+        class InstrumentedASGIRequest(ASGIRequest):
+            @property
+            def META(self):
+                return self._meta
+
+            @META.setter
+            def META(self, value):
+                self._meta = InstrumentedDict(**value)
+
+        request = InstrumentedASGIRequest(scope, None)
+
+        self.assertEqual(len(request.headers["foo"].split(",")), 200_000)
+        self.assertLessEqual(setitem_count, 100)
+
     async def test_untouched_request_body_gets_closed(self):
         application = get_asgi_application()
         scope = self.async_request_factory._base_scope(method="POST", path="/post/")
-- 
2.50.1 (Apple Git-155)

openSUSE Build Service is sponsored by