[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1106819: marked as done (bookworm-pu: package python-tornado/6.2.0-3+deb12u2)



Your message dated Thu, 5 Jun 2025 07:04:07 +0200
with message-id <aEElR1E6U83UyZrg@eldamar.lan>
and subject line Re: Bug#1106819: bookworm-pu: package python-tornado/6.2.0-3+deb12u2
has caused the Debian Bug report #1106819,
regarding bookworm-pu: package python-tornado/6.2.0-3+deb12u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1106819: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1106819
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: python-tornado@packages.debian.org
Control: affects -1 + src:python-tornado
User: release.debian.org@packages.debian.org
Usertags: pu

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

[ Reason ]
This upload intends to fix the vulnerability CVE-2025-47287.

CVE-2025-47287 allows a remote attacker to create an extremely high volume of
log entries, constituting a DoS attack.

[ Impact ]
Users of Debian Bookworm will continue to be vulnerable to the mentioned issues
if the update is not approved.

[ Tests ]
The package comes with the testsuite enabled. The tests were adjusted to match
the new behavior to throw errors instead of logging warnings. All tests succeed.

[ Risks ]
The changes are quite simple. However, regressions are always possible. The
fact that the tests are successful reduce the risk of regressions.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
Instead of logging warning messages, errors are created which preserve the
backtrace. Parsing the body has been moved within the code into
RequestHandler._execute() to be in the right exception handler scope. The tests
have been adjusted to this change.

[ Other info ]
All patches contain links to the original reports and commits.

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEvu1N7VVEpMA+KD3HS80FZ8KW0F0FAmg5KDUACgkQS80FZ8KW
0F1sIw//dTsOnVtiz826x6FGeL7kUAK4BPs587XaPQgdu31TzDKHbkc5xDAE+Nas
Ay4vZMvlGAQmEsb6H/z+2F4aYgDKmoYojTfs/fRorMebieixeimVAE9J/NT0o436
cDzuuxhJkjghKDPFcSOoPNr1iFCoGbtTNpIHgPXn4cFRbryzGQwbYg26f9sIX5Mt
46P1E1lKcis0sIR19kt2Ri2dFi+GWZODCNmBzFZIW0pLhIs14TU4+cBv6yjCF51d
VUu8wKX63UnIbd3iilcPC7HvN65U4zn9YiBmSR/F9co2zRCrlxMdX7neb7DVkHvz
7Fz7yWJ28WE4CDq7AFp3ESsGgX0ySsTOuofMPJoMpWkMAI0tcRdPvypETSbtfnMr
OlsnwMwL1xZWxDrpHlRqirS8hpELmFpywlT8LxWknqHAv53ZOH42HtXabYzw+I0+
hT6UjMV4Ihv6HRbNXIAVFeIiLoJQMuexxtA71bZ3YzxvHhsfQmntKiyxDHRE4mqL
2lxzQQpFGkIaSNAkTVbUF9b4Qu9BPLhwvfL2OI43y6SAvM3UAfMR2lM8o6/Y+yjI
Er4pY4WDJ2CsNoFAY4nZLfSRbmGNHnqjAp3Hx2DCrBBpATyu+qgyOqgsH/sLTSUG
DXY1qaNJCvERiLOKaRFUoJpnESwemLyB0Q2BFKgzJHgHy9fIk1w=
=Cx9E
-----END PGP SIGNATURE-----
diff -Nru python-tornado-6.2.0/debian/changelog python-tornado-6.2.0/debian/changelog
--- python-tornado-6.2.0/debian/changelog	2024-12-31 01:53:59.000000000 +0100
+++ python-tornado-6.2.0/debian/changelog	2025-05-30 05:19:15.000000000 +0200
@@ -1,3 +1,15 @@
+python-tornado (6.2.0-3+deb12u2) bookworm; urgency=medium
+
+  * Non-maintainer upload by the Debian LTS team.
+  * d/patches/CVE-2025-47287.patch: Add patch to fix CVE-2025-47287.
+    - When Tornado's 'multipart/form-data' parser encounters certain errors,
+      it logs a warning but continues trying to parse the remainder of the
+      data. This allows remote attackers to generate an extremely high volume
+      of logs, constituting a DoS attack. This DoS is compounded by the fact
+      that the logging subsystem is synchronous (closes: #1105886).
+
+ -- Daniel Leidert <dleidert@debian.org>  Fri, 30 May 2025 05:19:15 +0200
+
 python-tornado (6.2.0-3+deb12u1) bookworm; urgency=medium
 
   * Non-maintainer upload by the Debian LTS team.
diff -Nru python-tornado-6.2.0/debian/patches/CVE-2023-28370.patch python-tornado-6.2.0/debian/patches/CVE-2023-28370.patch
--- python-tornado-6.2.0/debian/patches/CVE-2023-28370.patch	1970-01-01 01:00:00.000000000 +0100
+++ python-tornado-6.2.0/debian/patches/CVE-2023-28370.patch	2025-05-30 05:19:15.000000000 +0200
@@ -0,0 +1,234 @@
+From: Ben Darnell <ben@bendarnell.com>
+Date: Thu, 8 May 2025 13:29:43 -0400
+Subject: httputil: Raise errors instead of logging in multipart/form-data
+ parsing
+
+We used to continue after logging an error, which allowed repeated
+errors to spam the logs. The error raised here will still be logged,
+but only once per request, consistent with other error handling in
+Tornado.
+
+Reviewed-By: Daniel Leidert <dleidert@debian.org>
+Origin: https://github.com/tornadoweb/tornado/pull/3497
+Bug: https://github.com/tornadoweb/tornado/security/advisories/GHSA-7cx3-6m66-7c5m
+Bug-Debian: https://bugs.debian.org/1105886
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-47287
+Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2025-47287
+---
+ tornado/httputil.py             | 29 +++++++++++------------------
+ tornado/test/httpserver_test.py |  4 ++--
+ tornado/test/httputil_test.py   | 13 ++++++++-----
+ tornado/web.py                  | 17 +++++++++++++----
+ 4 files changed, 34 insertions(+), 29 deletions(-)
+
+diff --git a/tornado/httputil.py b/tornado/httputil.py
+index f74eced..80c159c 100644
+--- a/tornado/httputil.py
++++ b/tornado/httputil.py
+@@ -34,7 +34,6 @@ import unicodedata
+ from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl
+ 
+ from tornado.escape import native_str, parse_qs_bytes, utf8
+-from tornado.log import gen_log
+ from tornado.util import ObjectDict, unicode_type
+ 
+ 
+@@ -759,25 +758,23 @@ def parse_body_arguments(
+     """
+     if content_type.startswith("application/x-www-form-urlencoded"):
+         if headers and "Content-Encoding" in headers:
+-            gen_log.warning(
+-                "Unsupported Content-Encoding: %s", headers["Content-Encoding"]
++            raise HTTPInputError(
++                "Unsupported Content-Encoding: %s" % headers["Content-Encoding"]
+             )
+-            return
+         try:
+             # real charset decoding will happen in RequestHandler.decode_argument()
+             uri_arguments = parse_qs_bytes(body, keep_blank_values=True)
+         except Exception as e:
+-            gen_log.warning("Invalid x-www-form-urlencoded body: %s", e)
++            raise HTTPInputError("Invalid x-www-form-urlencoded body: %s" % e) from e
+             uri_arguments = {}
+         for name, values in uri_arguments.items():
+             if values:
+                 arguments.setdefault(name, []).extend(values)
+     elif content_type.startswith("multipart/form-data"):
+         if headers and "Content-Encoding" in headers:
+-            gen_log.warning(
+-                "Unsupported Content-Encoding: %s", headers["Content-Encoding"]
++            raise HTTPInputError(
++                "Unsupported Content-Encoding: %s" % headers["Content-Encoding"]
+             )
+-            return
+         try:
+             fields = content_type.split(";")
+             for field in fields:
+@@ -786,9 +783,9 @@ def parse_body_arguments(
+                     parse_multipart_form_data(utf8(v), body, arguments, files)
+                     break
+             else:
+-                raise ValueError("multipart boundary not found")
++                raise HTTPInputError("multipart boundary not found")
+         except Exception as e:
+-            gen_log.warning("Invalid multipart/form-data: %s", e)
++            raise HTTPInputError("Invalid multipart/form-data: %s" % e) from e
+ 
+ 
+ def parse_multipart_form_data(
+@@ -817,26 +814,22 @@ def parse_multipart_form_data(
+         boundary = boundary[1:-1]
+     final_boundary_index = data.rfind(b"--" + boundary + b"--")
+     if final_boundary_index == -1:
+-        gen_log.warning("Invalid multipart/form-data: no final boundary")
+-        return
++        raise HTTPInputError("Invalid multipart/form-data: no final boundary found")
+     parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n")
+     for part in parts:
+         if not part:
+             continue
+         eoh = part.find(b"\r\n\r\n")
+         if eoh == -1:
+-            gen_log.warning("multipart/form-data missing headers")
+-            continue
++            raise HTTPInputError("multipart/form-data missing headers")
+         headers = HTTPHeaders.parse(part[:eoh].decode("utf-8"))
+         disp_header = headers.get("Content-Disposition", "")
+         disposition, disp_params = _parse_header(disp_header)
+         if disposition != "form-data" or not part.endswith(b"\r\n"):
+-            gen_log.warning("Invalid multipart/form-data")
+-            continue
++            raise HTTPInputError("Invalid multipart/form-data")
+         value = part[eoh + 4 : -2]
+         if not disp_params.get("name"):
+-            gen_log.warning("multipart/form-data value missing name")
+-            continue
++            raise HTTPInputError("multipart/form-data missing name")
+         name = disp_params["name"]
+         if disp_params.get("filename"):
+             ctype = headers.get("Content-Type", "application/unknown")
+diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py
+index 367a0ef..3fff206 100644
+--- a/tornado/test/httpserver_test.py
++++ b/tornado/test/httpserver_test.py
+@@ -1025,9 +1025,9 @@ class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):
+         # Gzip support is opt-in; without it the server fails to parse
+         # the body (but parsing form bodies is currently just a log message,
+         # not a fatal error).
+-        with ExpectLog(gen_log, "Unsupported Content-Encoding"):
++        with ExpectLog(gen_log, ".*Unsupported Content-Encoding"):
+             response = self.post_gzip("foo=bar")
+-        self.assertEqual(json_decode(response.body), {})
++        self.assertEqual(response.code, 400)
+ 
+ 
+ class StreamingChunkSizeTest(AsyncHTTPTestCase):
+diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
+index 25faf66..4ceda3b 100644
+--- a/tornado/test/httputil_test.py
++++ b/tornado/test/httputil_test.py
+@@ -12,7 +12,6 @@ from tornado.httputil import (
+ )
+ from tornado.escape import utf8, native_str
+ from tornado.log import gen_log
+-from tornado.testing import ExpectLog
+ 
+ import copy
+ import datetime
+@@ -194,7 +193,9 @@ Foo
+             b"\n", b"\r\n"
+         )
+         args, files = form_data_args()
+-        with ExpectLog(gen_log, "multipart/form-data missing headers"):
++        with self.assertRaises(
++            HTTPInputError, msg="multipart/form-data missing headers"
++        ):
+             parse_multipart_form_data(b"1234", data, args, files)
+         self.assertEqual(files, {})
+ 
+@@ -208,7 +209,7 @@ Foo
+             b"\n", b"\r\n"
+         )
+         args, files = form_data_args()
+-        with ExpectLog(gen_log, "Invalid multipart/form-data"):
++        with self.assertRaises(HTTPInputError, msg="Invalid multipart/form-data"):
+             parse_multipart_form_data(b"1234", data, args, files)
+         self.assertEqual(files, {})
+ 
+@@ -221,7 +222,7 @@ Foo--1234--""".replace(
+             b"\n", b"\r\n"
+         )
+         args, files = form_data_args()
+-        with ExpectLog(gen_log, "Invalid multipart/form-data"):
++        with self.assertRaises(HTTPInputError, msg="Invalid multipart/form-data"):
+             parse_multipart_form_data(b"1234", data, args, files)
+         self.assertEqual(files, {})
+ 
+@@ -235,7 +236,9 @@ Foo
+             b"\n", b"\r\n"
+         )
+         args, files = form_data_args()
+-        with ExpectLog(gen_log, "multipart/form-data value missing name"):
++        with self.assertRaises(
++            HTTPInputError, msg="multipart/form-data value missing name"
++        ):
+             parse_multipart_form_data(b"1234", data, args, files)
+         self.assertEqual(files, {})
+ 
+diff --git a/tornado/web.py b/tornado/web.py
+index 05b571e..98181c0 100644
+--- a/tornado/web.py
++++ b/tornado/web.py
+@@ -1670,6 +1670,14 @@ class RequestHandler(object):
+         try:
+             if self.request.method not in self.SUPPORTED_METHODS:
+                 raise HTTPError(405)
++
++            # If we're not in stream_request_body mode, this is the place where we parse the body.
++            if not _has_stream_request_body(self.__class__):
++                try:
++                    self.request._parse_body()
++                except httputil.HTTPInputError as e:
++                    raise HTTPError(400, "Invalid body: %s" % e) from e
++
+             self.path_args = [self.decode_argument(arg) for arg in args]
+             self.path_kwargs = dict(
+                 (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items()
+@@ -1864,7 +1872,7 @@ def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
+ 
+ 
+ def removeslash(
+-    method: Callable[..., Optional[Awaitable[None]]]
++    method: Callable[..., Optional[Awaitable[None]]],
+ ) -> Callable[..., Optional[Awaitable[None]]]:
+     """Use this decorator to remove trailing slashes from the request path.
+ 
+@@ -1893,7 +1901,7 @@ def removeslash(
+ 
+ 
+ def addslash(
+-    method: Callable[..., Optional[Awaitable[None]]]
++    method: Callable[..., Optional[Awaitable[None]]],
+ ) -> Callable[..., Optional[Awaitable[None]]]:
+     """Use this decorator to add a missing trailing slash to the request path.
+ 
+@@ -2317,8 +2325,9 @@ class _HandlerDelegate(httputil.HTTPMessageDelegate):
+         if self.stream_request_body:
+             future_set_result_unless_cancelled(self.request._body_future, None)
+         else:
++            # Note that the body gets parsed in RequestHandler._execute so it can be in
++            # the right exception handler scope.
+             self.request.body = b"".join(self.chunks)
+-            self.request._parse_body()
+             self.execute()
+ 
+     def on_connection_close(self) -> None:
+@@ -3183,7 +3192,7 @@ class GZipContentEncoding(OutputTransform):
+ 
+ 
+ def authenticated(
+-    method: Callable[..., Optional[Awaitable[None]]]
++    method: Callable[..., Optional[Awaitable[None]]],
+ ) -> Callable[..., Optional[Awaitable[None]]]:
+     """Decorate methods with this to require that the user be logged in.
+ 
diff -Nru python-tornado-6.2.0/debian/patches/series python-tornado-6.2.0/debian/patches/series
--- python-tornado-6.2.0/debian/patches/series	2024-12-31 01:53:59.000000000 +0100
+++ python-tornado-6.2.0/debian/patches/series	2025-05-30 05:19:15.000000000 +0200
@@ -7,3 +7,4 @@
 CVE-2024-52804.patch
 CVE-2023-28370-1.patch
 CVE-2023-28370-2.patch
+CVE-2023-28370.patch

--- End Message ---
--- Begin Message ---
Hi Daniel,

On Thu, Jun 05, 2025 at 02:16:58AM +0200, Daniel Leidert wrote:
> On Wed, 2025-06-04 at 16:38 +0200, Salvatore Bonaccorso wrote:
> 
> [python-tornado DSA]
> 
> > The changes look good to me, yes please go ahead with the upload to
> > security-master.
> 
> Uploaded.

Thanks! (so closing as well this release.d.o bug along).

Regards,
Salvatore

--- End Message ---

Reply to: