diff --git a/CVE-2025-48432-pre1.patch b/CVE-2025-48432-pre1.patch new file mode 100644 index 0000000000000000000000000000000000000000..4e8e7e9e38aef3715d760ee6da40fb0a07e84cc8 --- /dev/null +++ b/CVE-2025-48432-pre1.patch @@ -0,0 +1,220 @@ +From 32fd8dec5618bd09eccdeb9dbf512043193d68ef 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. +--- + 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 ba8f87d6ac74..b8d928151e7b 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 @@ def assertMaskedSecretCorrect(self, masked_secret, secret): + 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 @@ def _check_bad_or_missing_cookie(self, cookie, expected): + 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 @@ def _check_bad_or_missing_token( + 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 @@ def test_put_and_delete_rejected(self): + 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 @@ def test_reading_post_data_raises_unreadable_post_error(self): + 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 @@ def test_bad_origin_bad_domain(self): + 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 @@ def test_bad_origin_null_origin(self): + 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 @@ def test_bad_origin_bad_protocol(self): + 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 @@ def test_bad_origin_csrf_trusted_origin_bad_protocol(self): + 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 @@ def test_bad_origin_cannot_be_parsed(self): + 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 2138a7fe50f3..4ffa49a1b805 100644 +--- a/tests/logging_tests/tests.py ++++ b/tests/logging_tests/tests.py +@@ -94,6 +94,28 @@ def test_django_logger_debug(self): + + + 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 @@ def assertLogsRequest( + 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 @@ def test_page_not_found_warning(self): + 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/", diff --git a/CVE-2025-48432-pre2.patch b/CVE-2025-48432-pre2.patch new file mode 100644 index 0000000000000000000000000000000000000000..7d2e742ae512d93d2d9196b99b58faf0e7789745 --- /dev/null +++ b/CVE-2025-48432-pre2.patch @@ -0,0 +1,161 @@ +From acbe655a0fa1200d2de31c6020f310ba9aa2f636 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. +--- + 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 c73a3acd6d72..2138a7fe50f3 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.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 @@ + RequireDebugFalse, + RequireDebugTrue, + ServerFormatter, ++ log_response, + ) + from django.views.debug import ExceptionReporter + +@@ -646,3 +649,121 @@ def patch_django_server_logger(): + 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() ++ ) diff --git a/CVE-2025-48432.patch b/CVE-2025-48432.patch new file mode 100644 index 0000000000000000000000000000000000000000..d68c433e0ef021e46dfa4043c0b6ab3d4a584a97 --- /dev/null +++ b/CVE-2025-48432.patch @@ -0,0 +1,161 @@ +From ac03c5e7df8680c61cdb0d3bdb8be9095dba841e 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 +Co-authored-by: Jake Howard + +Backport of a07ebec5591e233d8bbb38b7d63f35c5479eef0e from main. +--- + django/utils/log.py | 7 +++- + docs/releases/4.2.22.txt | 14 +++++++ + tests/logging_tests/tests.py | 79 +++++++++++++++++++++++++++++++++++- + 3 files changed, 98 insertions(+), 2 deletions(-) + +diff --git a/django/utils/log.py b/django/utils/log.py +index fd0cc1bdc1ff..d7465f73d75c 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 4ffa49a1b805..cda0a62f2c16 100644 +--- a/tests/logging_tests/tests.py ++++ b/tests/logging_tests/tests.py +@@ -94,7 +94,6 @@ def test_django_logger_debug(self): + + + class LoggingAssertionMixin: +- + def assertLogRecord( + self, + logger_cm, +@@ -147,6 +146,14 @@ def test_page_not_found_warning(self): + 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 @@ async def test_async_page_not_found_warning(self): + + 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 @@ def assertResponseLogged(self, logger_cm, msg, levelno, status_code, request): + 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 @@ def test_logs_with_custom_logger(self): + 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🙂", "smile\\U0001f642"), ++ ("weird ☃️", "weird \\u2603\\ufe0f"), ++ # Non-Latin alphabets. ++ ("Привет", "\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442"), ++ ("你好", "\\u4f60\\u597d"), ++ # ANSI escape sequences. ++ ("escape\x1b[31mred\x1b[0m", "escape\\x1b[31mred\\x1b[0m"), ++ ( ++ "/\x1b[1;31mCAUTION!!YOU ARE PWNED\x1b[0m/", ++ "/\\x1b[1;31mCAUTION!!YOU ARE PWNED\\x1b[0m/", ++ ), ++ ( ++ "/\r\n\r\n1984-04-22 INFO Listening on 0.0.0.0:8080\r\n\r\n", ++ "/\\r\\n\\r\\n1984-04-22 INFO Listening on 0.0.0.0:8080\\r\\n\\r\\n", ++ ), ++ # Plain safe input. ++ ("normal-path", "normal-path"), ++ ("slash/colon:", "slash/colon:"), ++ # Non strings. ++ (0, "0"), ++ ([1, 2, 3], "[1, 2, 3]"), ++ ({"test": "🙂"}, "{'test': '🙂'}"), ++ ] ++ ++ msg = "Test message: %s" ++ for case, expected in test_cases: ++ with self.assertLogs("django.request", level="ERROR") as cm: ++ with self.subTest(case=case): ++ response = HttpResponse(status=318) ++ log_response(msg, case, response=response, level="error") ++ ++ record = self.assertResponseLogged( ++ cm, ++ msg % expected, ++ levelno=logging.ERROR, ++ status_code=318, ++ request=None, ++ ) ++ # Log record is always a single line. ++ self.assertEqual(len(record.getMessage().splitlines()), 1) diff --git a/python-django.spec b/python-django.spec index 6af94bc858a53c22764c37ce4481dc987ed1023a..ce367584e08ecc7dc0ae05fde5e4a1c85a9b518f 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,7 +1,7 @@ %global _empty_manifest_terminate_build 0 Name: python-django Version: 4.2.15 -Release: 6 +Release: 7 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. License: Apache-2.0 and Python-2.0 and BSD-3-Clause URL: https://www.djangoproject.com/ @@ -13,6 +13,9 @@ Patch3: CVE-2024-53908.patch Patch4: CVE-2024-56374.patch Patch5: backport-CVE-2025-26699.patch Patch6: CVE-2025-32873.patch +Patch7: CVE-2025-48432-pre1.patch +Patch8: CVE-2025-48432-pre2.patch +Patch9: CVE-2025-48432.patch BuildArch: noarch %description @@ -79,6 +82,9 @@ mv %{buildroot}/doclist.lst . %{_docdir}/* %changelog +* Thu Jun 05 2025 yaoxin <1024769339@qq.com> - 4.2.15-7 +- Fix CVE-2025-48432 + * Fri May 09 2025 yaoxin <1024769339@qq.com> - 4.2.15-6 - Fix CVE-2025-32873