new file mode 100644
@@ -0,0 +1,166 @@
+From d4a83d38022de809f4ae015ff4b7592c11f3b371 Mon Sep 17 00:00:00 2001
+From: Natalia <124304+nessita@users.noreply.github.com>
+Date: Mon, 19 May 2025 22:45:38 -0300
+Subject: [PATCH] [4.2.x] Refs #26688 -- Added tests for `log_response()`
+ internal helper.
+
+Backport of 897046815944cc9a2da7ed9e8082f45ffe8110e3 from main.
+
+CVE: CVE-2025-48432
+Upstream-Status: Backport [https://github.com/django/django/commit/acbe655a0fa1200d2de31c6020f310ba9aa2f636]
+(cherry picked from commit acbe655a0fa1200d2de31c6020f310ba9aa2f636)
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ tests/logging_tests/tests.py | 121 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 121 insertions(+)
+
+diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
+index c73a3acd6d..2138a7fe50 100644
+--- a/tests/logging_tests/tests.py
++++ b/tests/logging_tests/tests.py
+@@ -1,6 +1,7 @@
+ import logging
+ from contextlib import contextmanager
+ from io import StringIO
++from unittest import TestCase
+
+ from admin_scripts.tests import AdminScriptTestCase
+
+@@ -9,6 +10,7 @@ from django.core import mail
+ from django.core.exceptions import DisallowedHost, PermissionDenied, SuspiciousOperation
+ from django.core.files.temp import NamedTemporaryFile
+ from django.core.management import color
++from django.http import HttpResponse
+ from django.http.multipartparser import MultiPartParserError
+ from django.test import RequestFactory, SimpleTestCase, override_settings
+ from django.test.utils import LoggingCaptureMixin
+@@ -19,6 +21,7 @@ from django.utils.log import (
+ RequireDebugFalse,
+ RequireDebugTrue,
+ ServerFormatter,
++ log_response,
+ )
+ from django.views.debug import ExceptionReporter
+
+@@ -646,3 +649,121 @@ class LogFormattersTests(SimpleTestCase):
+ self.assertRegex(
+ logger_output.getvalue(), r"^\[[/:,\w\s\d]+\] %s\n" % log_msg
+ )
++
++
++class LogResponseRealLoggerTests(TestCase):
++ request = RequestFactory().get("/test-path/")
++
++ def assertResponseLogged(self, logger_cm, msg, levelno, status_code, request):
++ self.assertEqual(
++ records_len := len(logger_cm.records),
++ 1,
++ f"Unexpected number of records for {logger_cm=} in {levelno=} (expected 1, "
++ f"got {records_len}).",
++ )
++ record = logger_cm.records[0]
++ self.assertEqual(record.getMessage(), msg)
++ self.assertEqual(record.levelno, levelno)
++ self.assertEqual(record.status_code, status_code)
++ self.assertEqual(record.request, request)
++
++ def test_missing_response_raises_attribute_error(self):
++ with self.assertRaises(AttributeError):
++ log_response("No response provided", response=None, request=self.request)
++
++ def test_missing_request_logs_with_none(self):
++ response = HttpResponse(status=403)
++ with self.assertLogs("django.request", level="INFO") as cm:
++ log_response(msg := "Missing request", response=response, request=None)
++ self.assertResponseLogged(cm, msg, logging.WARNING, 403, request=None)
++
++ def test_logs_5xx_as_error(self):
++ response = HttpResponse(status=508)
++ with self.assertLogs("django.request", level="ERROR") as cm:
++ log_response(
++ msg := "Server error occurred", response=response, request=self.request
++ )
++ self.assertResponseLogged(cm, msg, logging.ERROR, 508, self.request)
++
++ def test_logs_4xx_as_warning(self):
++ response = HttpResponse(status=418)
++ with self.assertLogs("django.request", level="WARNING") as cm:
++ log_response(
++ msg := "This is a teapot!", response=response, request=self.request
++ )
++ self.assertResponseLogged(cm, msg, logging.WARNING, 418, self.request)
++
++ def test_logs_2xx_as_info(self):
++ response = HttpResponse(status=201)
++ with self.assertLogs("django.request", level="INFO") as cm:
++ log_response(msg := "OK response", response=response, request=self.request)
++ self.assertResponseLogged(cm, msg, logging.INFO, 201, self.request)
++
++ def test_custom_log_level(self):
++ response = HttpResponse(status=403)
++ with self.assertLogs("django.request", level="DEBUG") as cm:
++ log_response(
++ msg := "Debug level log",
++ response=response,
++ request=self.request,
++ level="debug",
++ )
++ self.assertResponseLogged(cm, msg, logging.DEBUG, 403, self.request)
++
++ def test_logs_only_once_per_response(self):
++ response = HttpResponse(status=500)
++ with self.assertLogs("django.request", level="ERROR") as cm:
++ log_response("First log", response=response, request=self.request)
++ log_response("Second log", response=response, request=self.request)
++ self.assertResponseLogged(cm, "First log", logging.ERROR, 500, self.request)
++
++ def test_exc_info_output(self):
++ response = HttpResponse(status=500)
++ try:
++ raise ValueError("Simulated failure")
++ except ValueError as exc:
++ with self.assertLogs("django.request", level="ERROR") as cm:
++ log_response(
++ "With exception",
++ response=response,
++ request=self.request,
++ exception=exc,
++ )
++ self.assertResponseLogged(
++ cm, "With exception", logging.ERROR, 500, self.request
++ )
++ self.assertIn("ValueError", "\n".join(cm.output)) # Stack trace included
++
++ def test_format_args_are_applied(self):
++ response = HttpResponse(status=500)
++ with self.assertLogs("django.request", level="ERROR") as cm:
++ log_response(
++ "Something went wrong: %s (%d)",
++ "DB error",
++ 42,
++ response=response,
++ request=self.request,
++ )
++ msg = "Something went wrong: DB error (42)"
++ self.assertResponseLogged(cm, msg, logging.ERROR, 500, self.request)
++
++ def test_logs_with_custom_logger(self):
++ handler = logging.StreamHandler(log_stream := StringIO())
++ handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(message)s"))
++
++ custom_logger = logging.getLogger("my.custom.logger")
++ custom_logger.setLevel(logging.DEBUG)
++ custom_logger.addHandler(handler)
++ self.addCleanup(custom_logger.removeHandler, handler)
++
++ response = HttpResponse(status=404)
++ log_response(
++ msg := "Handled by custom logger",
++ response=response,
++ request=self.request,
++ logger=custom_logger,
++ )
++
++ self.assertEqual(
++ f"WARNING:my.custom.logger:{msg}", log_stream.getvalue().strip()
++ )
new file mode 100644
@@ -0,0 +1,225 @@
+From 88c0244497d492e84b4a2925ac4554698cdf179a Mon Sep 17 00:00:00 2001
+From: Natalia <124304+nessita@users.noreply.github.com>
+Date: Mon, 19 May 2025 22:46:00 -0300
+Subject: [PATCH] [4.2.x] Added helpers in csrf_tests and logging_tests to
+ assert logs from `log_response()`.
+
+Backport of ad6f99889838ccc2c30b3c02ed3868c9b565e81b from main.
+
+CVE: CVE-2025-48432
+Upstream-Status: Backport [https://github.com/django/django/commit/32fd8dec5618bd09eccdeb9dbf512043193d68ef]
+(cherry picked from commit 32fd8dec5618bd09eccdeb9dbf512043193d68ef)
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ tests/csrf_tests/tests.py | 53 ++++++++++++++++++------------------
+ tests/logging_tests/tests.py | 42 ++++++++++++++++++++--------
+ 2 files changed, 57 insertions(+), 38 deletions(-)
+
+diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py
+index ba8f87d6ac..b8d928151e 100644
+--- a/tests/csrf_tests/tests.py
++++ b/tests/csrf_tests/tests.py
+@@ -1,3 +1,4 @@
++import logging
+ import re
+
+ from django.conf import settings
+@@ -57,6 +58,21 @@ class CsrfFunctionTestMixin:
+ actual = _unmask_cipher_token(masked_secret)
+ self.assertEqual(actual, secret)
+
++ def assertForbiddenReason(
++ self, response, logger_cm, reason, levelno=logging.WARNING
++ ):
++ self.assertEqual(
++ records_len := len(logger_cm.records),
++ 1,
++ f"Unexpected number of records for {logger_cm=} in {levelno=} (expected 1, "
++ f"got {records_len}).",
++ )
++ record = logger_cm.records[0]
++ self.assertEqual(record.getMessage(), "Forbidden (%s): " % reason)
++ self.assertEqual(record.levelno, levelno)
++ self.assertEqual(record.status_code, 403)
++ self.assertEqual(response.status_code, 403)
++
+
+ class CsrfFunctionTests(CsrfFunctionTestMixin, SimpleTestCase):
+ def test_unmask_cipher_token(self):
+@@ -347,8 +363,7 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ mw.process_request(req)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ resp = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(403, resp.status_code)
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % expected)
++ self.assertForbiddenReason(resp, cm, expected)
+
+ def test_no_csrf_cookie(self):
+ """
+@@ -373,9 +388,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ mw.process_request(req)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ resp = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(403, resp.status_code)
+ self.assertEqual(resp["Content-Type"], "text/html; charset=utf-8")
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % expected)
++ self.assertForbiddenReason(resp, cm, expected)
+
+ def test_csrf_cookie_bad_or_missing_token(self):
+ """
+@@ -480,18 +494,12 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ mw = CsrfViewMiddleware(post_form_view)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ resp = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(403, resp.status_code)
+- self.assertEqual(
+- cm.records[0].getMessage(), "Forbidden (%s): " % REASON_NO_CSRF_COOKIE
+- )
++ self.assertForbiddenReason(resp, cm, REASON_NO_CSRF_COOKIE)
+
+ req = self._get_request(method="DELETE")
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ resp = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(403, resp.status_code)
+- self.assertEqual(
+- cm.records[0].getMessage(), "Forbidden (%s): " % REASON_NO_CSRF_COOKIE
+- )
++ self.assertForbiddenReason(resp, cm, REASON_NO_CSRF_COOKIE)
+
+ def test_put_and_delete_allowed(self):
+ """
+@@ -879,11 +887,7 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ mw.process_request(req)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ resp = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(resp.status_code, 403)
+- self.assertEqual(
+- cm.records[0].getMessage(),
+- "Forbidden (%s): " % REASON_CSRF_TOKEN_MISSING,
+- )
++ self.assertForbiddenReason(resp, cm, REASON_CSRF_TOKEN_MISSING)
+
+ def test_reading_post_data_raises_os_error(self):
+ """
+@@ -908,9 +912,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ self.assertIs(mw._origin_verified(req), False)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ response = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(response.status_code, 403)
+ msg = REASON_BAD_ORIGIN % req.META["HTTP_ORIGIN"]
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % msg)
++ self.assertForbiddenReason(response, cm, msg)
+
+ @override_settings(ALLOWED_HOSTS=["www.example.com"])
+ def test_bad_origin_null_origin(self):
+@@ -923,9 +926,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ self.assertIs(mw._origin_verified(req), False)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ response = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(response.status_code, 403)
+ msg = REASON_BAD_ORIGIN % req.META["HTTP_ORIGIN"]
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % msg)
++ self.assertForbiddenReason(response, cm, msg)
+
+ @override_settings(ALLOWED_HOSTS=["www.example.com"])
+ def test_bad_origin_bad_protocol(self):
+@@ -939,9 +941,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ self.assertIs(mw._origin_verified(req), False)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ response = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(response.status_code, 403)
+ msg = REASON_BAD_ORIGIN % req.META["HTTP_ORIGIN"]
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % msg)
++ self.assertForbiddenReason(response, cm, msg)
+
+ @override_settings(
+ ALLOWED_HOSTS=["www.example.com"],
+@@ -966,9 +967,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ self.assertIs(mw._origin_verified(req), False)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ response = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(response.status_code, 403)
+ msg = REASON_BAD_ORIGIN % req.META["HTTP_ORIGIN"]
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % msg)
++ self.assertForbiddenReason(response, cm, msg)
+ self.assertEqual(mw.allowed_origins_exact, {"http://no-match.com"})
+ self.assertEqual(
+ mw.allowed_origin_subdomains,
+@@ -992,9 +992,8 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
+ self.assertIs(mw._origin_verified(req), False)
+ with self.assertLogs("django.security.csrf", "WARNING") as cm:
+ response = mw.process_view(req, post_form_view, (), {})
+- self.assertEqual(response.status_code, 403)
+ msg = REASON_BAD_ORIGIN % req.META["HTTP_ORIGIN"]
+- self.assertEqual(cm.records[0].getMessage(), "Forbidden (%s): " % msg)
++ self.assertForbiddenReason(response, cm, msg)
+
+ @override_settings(ALLOWED_HOSTS=["www.example.com"])
+ def test_good_origin_insecure(self):
+diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
+index 2138a7fe50..4ffa49a1b8 100644
+--- a/tests/logging_tests/tests.py
++++ b/tests/logging_tests/tests.py
+@@ -94,6 +94,28 @@ class DefaultLoggingTests(
+
+
+ class LoggingAssertionMixin:
++
++ def assertLogRecord(
++ self,
++ logger_cm,
++ level,
++ msg,
++ status_code,
++ exc_class=None,
++ ):
++ self.assertEqual(
++ records_len := len(logger_cm.records),
++ 1,
++ f"Wrong number of calls for {logger_cm=} in {level=} (expected 1, got "
++ f"{records_len}).",
++ )
++ record = logger_cm.records[0]
++ self.assertEqual(record.getMessage(), msg)
++ self.assertEqual(record.status_code, status_code)
++ if exc_class:
++ self.assertIsNotNone(record.exc_info)
++ self.assertEqual(record.exc_info[0], exc_class)
++
+ def assertLogsRequest(
+ self, url, level, msg, status_code, logger="django.request", exc_class=None
+ ):
+@@ -102,17 +124,7 @@ class LoggingAssertionMixin:
+ self.client.get(url)
+ except views.UncaughtException:
+ pass
+- self.assertEqual(
+- len(cm.records),
+- 1,
+- "Wrong number of calls for logger %r in %r level." % (logger, level),
+- )
+- record = cm.records[0]
+- self.assertEqual(record.getMessage(), msg)
+- self.assertEqual(record.status_code, status_code)
+- if exc_class:
+- self.assertIsNotNone(record.exc_info)
+- self.assertEqual(record.exc_info[0], exc_class)
++ self.assertLogRecord(cm, level, msg, status_code, exc_class)
+
+
+ @override_settings(DEBUG=True, ROOT_URLCONF="logging_tests.urls")
+@@ -135,6 +147,14 @@ class HandlerLoggingTests(
+ msg="Not Found: /does_not_exist/",
+ )
+
++ async def test_async_page_not_found_warning(self):
++ logger = "django.request"
++ level = "WARNING"
++ with self.assertLogs(logger, level) as cm:
++ await self.async_client.get("/does_not_exist/")
++
++ self.assertLogRecord(cm, level, "Not Found: /does_not_exist/", 404)
++
+ def test_page_not_found_raised(self):
+ self.assertLogsRequest(
+ url="/does_not_exist_raised/",
new file mode 100644
@@ -0,0 +1,164 @@
+From 5e137465b5b7668f9a32f5c4f4af374fd705f38d Mon Sep 17 00:00:00 2001
+From: Natalia <124304+nessita@users.noreply.github.com>
+Date: Tue, 20 May 2025 15:29:52 -0300
+Subject: [PATCH] [4.2.x] Fixed CVE-2025-48432 -- Escaped formatting arguments
+ in `log_response()`.
+
+Suitably crafted requests containing a CRLF sequence in the request
+path may have allowed log injection, potentially corrupting log files,
+obscuring other attacks, misleading log post-processing tools, or
+forging log entries.
+
+To mitigate this, all positional formatting arguments passed to the
+logger are now escaped using "unicode_escape" encoding.
+
+Thanks to Seokchan Yoon (https://ch4n3.kr/) for the report.
+
+Co-authored-by: Carlton Gibson <carlton@noumenal.es>
+Co-authored-by: Jake Howard <git@theorangeone.net>
+
+Backport of a07ebec5591e233d8bbb38b7d63f35c5479eef0e from main.
+CVE: CVE-2025-48432
+Upstream-Status: Backport [https://github.com/django/django/commit/ac03c5e7df8680c61cdb0d3bdb8be9095dba841e]
+(cherry picked from commit ac03c5e7df8680c61cdb0d3bdb8be9095dba841e)
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ django/utils/log.py | 7 +++-
+ tests/logging_tests/tests.py | 79 +++++++++++++++++++++++++++++++++++-
+ 2 files changed, 84 insertions(+), 2 deletions(-)
+
+diff --git a/django/utils/log.py b/django/utils/log.py
+index fd0cc1bdc1..d7465f73d7 100644
+--- a/django/utils/log.py
++++ b/django/utils/log.py
+@@ -238,9 +238,14 @@ def log_response(
+ else:
+ level = "info"
+
++ escaped_args = tuple(
++ a.encode("unicode_escape").decode("ascii") if isinstance(a, str) else a
++ for a in args
++ )
++
+ getattr(logger, level)(
+ message,
+- *args,
++ *escaped_args,
+ extra={
+ "status_code": response.status_code,
+ "request": request,
+diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
+index 4ffa49a1b8..cda0a62f2c 100644
+--- a/tests/logging_tests/tests.py
++++ b/tests/logging_tests/tests.py
+@@ -94,7 +94,6 @@ class DefaultLoggingTests(
+
+
+ class LoggingAssertionMixin:
+-
+ def assertLogRecord(
+ self,
+ logger_cm,
+@@ -147,6 +146,14 @@ class HandlerLoggingTests(
+ msg="Not Found: /does_not_exist/",
+ )
+
++ def test_control_chars_escaped(self):
++ self.assertLogsRequest(
++ url="/%1B[1;31mNOW IN RED!!!1B[0m/",
++ level="WARNING",
++ status_code=404,
++ msg=r"Not Found: /\x1b[1;31mNOW IN RED!!!1B[0m/",
++ )
++
+ async def test_async_page_not_found_warning(self):
+ logger = "django.request"
+ level = "WARNING"
+@@ -155,6 +162,16 @@ class HandlerLoggingTests(
+
+ self.assertLogRecord(cm, level, "Not Found: /does_not_exist/", 404)
+
++ async def test_async_control_chars_escaped(self):
++ logger = "django.request"
++ level = "WARNING"
++ with self.assertLogs(logger, level) as cm:
++ await self.async_client.get(r"/%1B[1;31mNOW IN RED!!!1B[0m/")
++
++ self.assertLogRecord(
++ cm, level, r"Not Found: /\x1b[1;31mNOW IN RED!!!1B[0m/", 404
++ )
++
+ def test_page_not_found_raised(self):
+ self.assertLogsRequest(
+ url="/does_not_exist_raised/",
+@@ -686,6 +703,7 @@ class LogResponseRealLoggerTests(TestCase):
+ self.assertEqual(record.levelno, levelno)
+ self.assertEqual(record.status_code, status_code)
+ self.assertEqual(record.request, request)
++ return record
+
+ def test_missing_response_raises_attribute_error(self):
+ with self.assertRaises(AttributeError):
+@@ -787,3 +805,62 @@ class LogResponseRealLoggerTests(TestCase):
+ self.assertEqual(
+ f"WARNING:my.custom.logger:{msg}", log_stream.getvalue().strip()
+ )
++
++ def test_unicode_escape_escaping(self):
++ test_cases = [
++ # Control characters.
++ ("line\nbreak", "line\\nbreak"),
++ ("carriage\rreturn", "carriage\\rreturn"),
++ ("tab\tseparated", "tab\\tseparated"),
++ ("formfeed\f", "formfeed\\x0c"),
++ ("bell\a", "bell\\x07"),
++ ("multi\nline\ntext", "multi\\nline\\ntext"),
++ # Slashes.
++ ("slash\\test", "slash\\\\test"),
++ ("back\\slash", "back\\\\slash"),
++ # Quotes.
++ ('quote"test"', 'quote"test"'),
++ ("quote'test'", "quote'test'"),
++ # Accented, composed characters, emojis and symbols.
++ ("café", "caf\\xe9"),
++ ("e\u0301", "e\\u0301"), # e + combining acute
++ ("smile
https://nvd.nist.gov/vuln/detail/CVE-2025-48432 Following patches are needed to avoid cherry-pick conflicts - CVE-2025-48432-1.patch - CVE-2025-48432-2.patch - CVE-2025-48432-4.patch Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com> --- .../CVE-2025-48432-1.patch | 166 +++++++++++++ .../CVE-2025-48432-2.patch | 225 ++++++++++++++++++ .../CVE-2025-48432-3.patch | 164 +++++++++++++ .../CVE-2025-48432-4.patch | 193 +++++++++++++++ .../CVE-2025-48432-5.patch | 76 ++++++ .../CVE-2025-48432-6.patch | 144 +++++++++++ .../python/python3-django_4.2.20.bb | 6 + 7 files changed, 974 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-1.patch create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-2.patch create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-3.patch create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-4.patch create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-5.patch create mode 100644 meta-python/recipes-devtools/python/python3-django-4.2.20/CVE-2025-48432-6.patch