mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-11-12 15:31:03 +00:00
python3-tornado: Fix CVE-2026-116577
This patch applies the upstream fix as referenced in [4], using the commit shown in [1], [2] and [3]. [1] https://github.com/tornadoweb/tornado/commit/1b7a1dc9f340f52de929852e8125aa12f25297c9 [2] https://github.com/tornadoweb/tornado/commit/09591f65286a600c2196df9c33b792b889a6bccb [3] https://github.com/tornadoweb/tornado/commit/3ef5ec3fc7a158ecd0ca7bfc5fcee42fa2773094 [4] https://github.com/tornadoweb/tornado/pull/3765 [5] https://nvd.nist.gov/vuln/detail/CVE-2026-116577 Signed-off-by: Yogita Urade <yurade@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
1 parent
2248ece66b
commit
bf1ca462d0
4 files changed
+398
No files matched your search
@@ -0,0 +1,195 @@
|
||||
From 1b7a1dc9f340f52de929852e8125aa12f25297c9 Mon Sep 17 00:00:00 2001
|
||||
From: Ben Darnell <ben@bendarnell.com>
|
||||
Date: Tue, 6 Oct 2026 09:59:02 -0400
|
||||
Subject: [PATCH] httputil: Remove support for "obsolete line folding"
|
||||
|
||||
This optional part of the HTTP specification is no longer used
|
||||
in practice, and continuing to support it adds complexity and risk
|
||||
of DoS via quadratic performance on malicious data.
|
||||
|
||||
CVE: CVE-2026-116577
|
||||
Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/1b7a1dc9f340f52de929852e8125aa12f25297c9]
|
||||
|
||||
Backport Changes:
|
||||
- Kept unittest.TestCase for HTTPHeadersTest because 6.5.7's httputil_test.py
|
||||
does not import tornado.test.util.TestCase.
|
||||
- Updated test_multi_line_headers to preserve folded-header acceptance for Curl
|
||||
and expect a 599 response from simple_httpclient.
|
||||
- The httputil_test.py hunk removes two extra target-version lines because
|
||||
6.5.7 formats the removed multiline .replace() expression across three lines.
|
||||
|
||||
Signed-off-by: Yogita Urade <yurade@cisco.com>
|
||||
---
|
||||
tornado/httputil.py | 34 +++++++++---------------
|
||||
tornado/test/httpclient_test.py | 17 +++++++++++--
|
||||
tornado/test/httputil_test.py | 47 +++------------------------------
|
||||
3 files changed, 29 insertions(+), 69 deletions(-)
|
||||
|
||||
diff --git a/tornado/httputil.py b/tornado/httputil.py
|
||||
index 6a5db233..45e1883d 100644
|
||||
--- a/tornado/httputil.py
|
||||
+++ b/tornado/httputil.py
|
||||
@@ -195,7 +195,6 @@ class HTTPHeaders(StrMutableMapping):
|
||||
# on demand (and cleared whenever the list is modified).
|
||||
self._as_list: dict[str, list[str]] = {}
|
||||
self._combined_cache: dict[str, str] = {}
|
||||
- self._last_key = None # type: Optional[str]
|
||||
if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders):
|
||||
# Copy constructor
|
||||
for k, v in args[0].get_all():
|
||||
@@ -219,7 +218,6 @@ class HTTPHeaders(StrMutableMapping):
|
||||
if _FORBIDDEN_HEADER_CHARS_RE.search(value):
|
||||
raise HTTPInputError("Invalid header value %r" % value)
|
||||
norm_name = _normalize_header(name)
|
||||
- self._last_key = norm_name
|
||||
if norm_name in self:
|
||||
self._combined_cache.pop(norm_name, None)
|
||||
self._as_list[norm_name].append(value)
|
||||
@@ -260,6 +258,11 @@ class HTTPHeaders(StrMutableMapping):
|
||||
In Tornado 7.0, certain deprecated features of HTTP will become errors.
|
||||
Specifically, line folding and the use of LF (with CR) as a line separator
|
||||
will be removed.
|
||||
+
|
||||
+ .. versionchanged:: 6.6
|
||||
+ Support for line folding was removed due to security/DoS concerns. This removal
|
||||
+ was ahead of the original removal schedule announced for Tornado 7.0.
|
||||
+ Support for LF without CR remains until Tornado 7.0.
|
||||
"""
|
||||
if m := re.search(r"\r?\n$", line):
|
||||
# RFC 9112 section 2.2: a recipient MAY recognize a single LF as a line
|
||||
@@ -270,27 +273,14 @@ class HTTPHeaders(StrMutableMapping):
|
||||
# Empty line, or the final CRLF of a header block.
|
||||
return
|
||||
if line[0] in HTTP_WHITESPACE:
|
||||
- # continuation of a multi-line header
|
||||
- # TODO(7.0): Remove support for line folding.
|
||||
- if self._last_key is None:
|
||||
- raise HTTPInputError("first header line cannot start with whitespace")
|
||||
- new_part = " " + line.strip(HTTP_WHITESPACE)
|
||||
- if _chars_are_bytes:
|
||||
- if not _ABNF.field_value.fullmatch(new_part[1:]):
|
||||
- raise HTTPInputError("Invalid header continuation %r" % new_part)
|
||||
- else:
|
||||
- if _FORBIDDEN_HEADER_CHARS_RE.search(new_part):
|
||||
- raise HTTPInputError("Invalid header value %r" % new_part)
|
||||
- self._as_list[self._last_key][-1] += new_part
|
||||
- self._combined_cache.pop(self._last_key, None)
|
||||
- else:
|
||||
- try:
|
||||
- name, value = line.split(":", 1)
|
||||
- except ValueError:
|
||||
- raise HTTPInputError("no colon in header line")
|
||||
- self.add(
|
||||
- name, value.strip(HTTP_WHITESPACE), _chars_are_bytes=_chars_are_bytes
|
||||
+ raise HTTPInputError(
|
||||
+ "header line cannot start with whitespace, folding not supported"
|
||||
)
|
||||
+ try:
|
||||
+ name, value = line.split(":", 1)
|
||||
+ except ValueError:
|
||||
+ raise HTTPInputError("no colon in header line")
|
||||
+ self.add(name, value.strip(HTTP_WHITESPACE), _chars_are_bytes=_chars_are_bytes)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, headers: str, *, _chars_are_bytes: bool = True) -> "HTTPHeaders":
|
||||
diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py
|
||||
index 46300200..4cb2e850 100644
|
||||
--- a/tornado/test/httpclient_test.py
|
||||
+++ b/tornado/test/httpclient_test.py
|
||||
@@ -734,10 +734,14 @@ def test_multi_line_headers(self):
|
||||
% (resp.body, value, container),
|
||||
)
|
||||
|
||||
def test_multi_line_headers(self):
|
||||
- # Multi-line http headers are rare but rfc-allowed
|
||||
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
+ # Line folding is an obsolete way of representing headers split across multiple lines.
|
||||
+ # https://www.rfc-editor.org/info/rfc9112/#section-5.2
|
||||
+ #
|
||||
+ # They must either be folded into a single line or rejected.
|
||||
+ # Tornado 6.6 removed support for line folding in simple_httpclient, but they are
|
||||
+ # still supported in libcurl.
|
||||
sock, port = bind_unused_port()
|
||||
with closing(sock):
|
||||
|
||||
@gen.coroutine
|
||||
@@ -760,10 +764,15 @@ def test_multi_line_headers(self):
|
||||
|
||||
netutil.add_accept_handler(sock, accept_callback) # type: ignore
|
||||
try:
|
||||
resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
- resp.rethrow()
|
||||
- self.assertEqual(resp.headers["X-XSS-Protection"], "1; mode=block")
|
||||
+ if self.http_client.__class__.__module__ == "tornado.curl_httpclient":
|
||||
+ resp.rethrow()
|
||||
+ self.assertEqual(
|
||||
+ resp.headers["X-XSS-Protection"], "1; mode=block"
|
||||
+ )
|
||||
+ else:
|
||||
+ self.assertEqual(resp.code, 599)
|
||||
finally:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
|
||||
@gen_test
|
||||
diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
|
||||
index 0e9d5c41..40108d9c 100644
|
||||
--- a/tornado/test/httputil_test.py
|
||||
+++ b/tornado/test/httputil_test.py
|
||||
@@ -384,50 +384,11 @@ Content-Disposition: form-data; name="files"; filename="ab.txt"
|
||||
|
||||
|
||||
class HTTPHeadersTest(unittest.TestCase):
|
||||
- def test_multi_line(self):
|
||||
- # Lines beginning with whitespace are appended to the previous line
|
||||
- # with any leading whitespace replaced by a single space.
|
||||
- # Note that while multi-line headers are a part of the HTTP spec,
|
||||
- # their use is strongly discouraged.
|
||||
- data = """\
|
||||
-Foo: bar
|
||||
- baz
|
||||
-Asdf: qwer
|
||||
-\tzxcv
|
||||
-Foo: even
|
||||
- more
|
||||
- lines
|
||||
-""".replace(
|
||||
- "\n", "\r\n"
|
||||
- )
|
||||
- headers = HTTPHeaders.parse(data)
|
||||
- self.assertEqual(headers["asdf"], "qwer zxcv")
|
||||
- self.assertEqual(headers.get_list("asdf"), ["qwer zxcv"])
|
||||
- self.assertEqual(headers["Foo"], "bar baz,even more lines")
|
||||
- self.assertEqual(headers.get_list("foo"), ["bar baz", "even more lines"])
|
||||
- self.assertEqual(
|
||||
- sorted(list(headers.get_all())),
|
||||
- [("Asdf", "qwer zxcv"), ("Foo", "bar baz"), ("Foo", "even more lines")],
|
||||
- )
|
||||
- # Verify case insensitivity in-operator
|
||||
- self.assertTrue("asdf" in headers)
|
||||
- self.assertTrue("Asdf" in headers)
|
||||
-
|
||||
- def test_continuation(self):
|
||||
+ def test_folding_rejected(self):
|
||||
data = "Foo: bar\r\n\tasdf"
|
||||
- headers = HTTPHeaders.parse(data)
|
||||
- self.assertEqual(headers["Foo"], "bar asdf")
|
||||
-
|
||||
- # If the first line starts with whitespace, it's a
|
||||
- # continuation line with nothing to continue, so reject it
|
||||
- # (with a proper error).
|
||||
- data = " Foo: bar"
|
||||
- self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)
|
||||
-
|
||||
- # \f (formfeed) is whitespace according to str.isspace, but
|
||||
- # not according to the HTTP spec.
|
||||
- data = "Foo: bar\r\n\fasdf"
|
||||
- self.assertRaises(HTTPInputError, HTTPHeaders.parse, data)
|
||||
+ with self.assertRaises(HTTPInputError):
|
||||
+ # Folded continuation lines are not supported.
|
||||
+ HTTPHeaders.parse(data)
|
||||
|
||||
def test_forbidden_ascii_characters(self):
|
||||
# Control characters and ASCII whitespace other than space, tab, and CRLF are not allowed in
|
||||
--
|
||||
2.35.6
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
From 09591f65286a600c2196df9c33b792b889a6bccb Mon Sep 17 00:00:00 2001
|
||||
From: Claude <noreply@anthropic.com>
|
||||
Date: Tue, 6 Oct 2026 14:14:19 +0000
|
||||
Subject: [PATCH] http1connection: Notify client delegate of header parse
|
||||
errors
|
||||
|
||||
Previously, an HTTPInputError raised while parsing response headers
|
||||
happened before the delegate was marked as needing on_connection_close,
|
||||
and close() cleared the stream's close callback, so simple_httpclient
|
||||
never completed the request and it hung until the timeout. Now the
|
||||
delegate is told the connection closed, matching the behavior for
|
||||
errors raised after headers_received.
|
||||
|
||||
Update test_multi_line_headers to expect an error from simple_httpclient
|
||||
now that line folding is no longer supported (curl still accepts it).
|
||||
|
||||
CVE: CVE-2026-116577
|
||||
Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/09591f65286a600c2196df9c33b792b889a6bccb]
|
||||
|
||||
Backport Changes:
|
||||
- Added a supports_line_folding flag to the shared test; Curl keeps the
|
||||
acceptance check while Simple expects HTTPError for folded response headers.
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
Claude-Session: https://claude.ai/code/session_016cNR4NyeuzQ1qFdqCk1yum
|
||||
Signed-off-by: Yogita Urade <yurade@cisco.com>
|
||||
---
|
||||
tornado/http1connection.py | 5 +++++
|
||||
tornado/test/httpclient_test.py | 19 +++++++++++++------
|
||||
tornado/test/curl_httpclient_test.py | 2 ++
|
||||
3 files changed, 20 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/tornado/http1connection.py b/tornado/http1connection.py
|
||||
--- a/tornado/http1connection.py
|
||||
+++ b/tornado/http1connection.py
|
||||
@@ -316,6 +316,11 @@ async def _read_message
|
||||
gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
|
||||
if not self.is_client:
|
||||
await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
|
||||
+ elif not need_delegate_close:
|
||||
+ # The error came before headers_received, so the delegate
|
||||
+ # doesn't know about this message yet. Tell it the
|
||||
+ # connection is closed so a client request doesn't hang.
|
||||
+ need_delegate_close = True
|
||||
self.close()
|
||||
return False
|
||||
finally:
|
||||
|
||||
diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py
|
||||
--- a/tornado/test/httpclient_test.py
|
||||
+++ b/tornado/test/httpclient_test.py
|
||||
@@ -263,6 +263,9 @@ class HTTPClientCommonTestCase
|
||||
|
||||
|
||||
class HTTPClientCommonTestCase(AsyncHTTPTestCase):
|
||||
+ # Set by subclasses whose client accepts obsolete line folding in
|
||||
+ # response headers (see test_multi_line_headers).
|
||||
+ supports_line_folding = False
|
||||
# Set by subclasses whose client decodes ``Content-Encoding: br``. No
|
||||
# implementation asks for brotli, so this is only true where the client
|
||||
# decodes an encoding it did not request (see BrotliBombHandler).
|
||||
@@ -764,14 +765,18 @@ class HTTPClientCommonTestCase
|
||||
|
||||
netutil.add_accept_handler(sock, accept_callback) # type: ignore
|
||||
try:
|
||||
- resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
- if self.http_client.__class__.__module__ == "tornado.curl_httpclient":
|
||||
+ if self.supports_line_folding:
|
||||
+ resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
resp.rethrow()
|
||||
- self.assertEqual(
|
||||
- resp.headers["X-XSS-Protection"], "1; mode=block"
|
||||
- )
|
||||
+ self.assertEqual(resp.headers["X-XSS-Protection"], "1; mode=block")
|
||||
else:
|
||||
- self.assertEqual(resp.code, 599)
|
||||
+ with ExpectLog(
|
||||
+ gen_log, "Malformed HTTP message.*folding", level=logging.INFO
|
||||
+ ):
|
||||
+ with self.assertRaises(HTTPError):
|
||||
+ self.fetch(
|
||||
+ "http://127.0.0.1:%d/" % port, raise_error=True
|
||||
+ )
|
||||
finally:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
|
||||
|
||||
diff --git a/tornado/test/curl_httpclient_test.py b/tornado/test/curl_httpclient_test.py
|
||||
--- a/tornado/test/curl_httpclient_test.py
|
||||
+++ b/tornado/test/curl_httpclient_test.py
|
||||
@@ -45,6 +45,8 @@ class CurlHTTPClientCommonTestCase
|
||||
# not we listed it in Accept-Encoding, so brotli is reachable whenever
|
||||
# libcurl was built with it (which is common).
|
||||
decompresses_brotli = pycurl is not None and "brotli" in pycurl.version
|
||||
+ # libcurl still accepts obsolete line folding in response headers.
|
||||
+ supports_line_folding = True
|
||||
|
||||
def get_http_client(self):
|
||||
client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))
|
||||
@@ -0,0 +1,101 @@
|
||||
From 3ef5ec3fc7a158ecd0ca7bfc5fcee42fa2773094 Mon Sep 17 00:00:00 2001
|
||||
From: Claude <noreply@anthropic.com>
|
||||
Date: Tue, 6 Oct 2026 15:28:59 +0000
|
||||
Subject: [PATCH] test: Accept either outcome in test_multi_line_headers
|
||||
|
||||
Rather than declaring per-implementation whether line folding is
|
||||
supported (which was wrong when --httpclient changes the default
|
||||
client), accept either a correctly unfolded header or a 599 error.
|
||||
|
||||
CVE: CVE-2026-116577
|
||||
Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/3ef5ec3fc7a158ecd0ca7bfc5fcee42fa2773094]
|
||||
|
||||
Backport Changes:
|
||||
- Rebased the test update on p2's 6.5.7-specific test body. Its fetch call and
|
||||
error assertion use a different line layout, accounting for two additional
|
||||
deleted lines in httpclient_test.py compared with upstream.
|
||||
|
||||
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||||
Claude-Session: https://claude.ai/code/session_016cNR4NyeuzQ1qFdqCk1yum
|
||||
Signed-off-by: Yogita Urade <yurade@cisco.com>
|
||||
---
|
||||
tornado/test/httpclient_test.py | 37 +++++++++++++++++-----------------
|
||||
tornado/test/curl_httpclient_test.py | 2 --
|
||||
2 files changed, 19 insertions(+), 20 deletions(-)
|
||||
|
||||
diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py
|
||||
index bb7de5c4..449f35a4 100644
|
||||
--- a/tornado/test/httpclient_test.py
|
||||
+++ b/tornado/test/httpclient_test.py
|
||||
@@ -263,9 +263,6 @@ class HTTPClientCommonTestCase(AsyncHTTPTestCase):
|
||||
|
||||
|
||||
class HTTPClientCommonTestCase(AsyncHTTPTestCase):
|
||||
- # Set by subclasses whose client accepts obsolete line folding in
|
||||
- # response headers (see test_multi_line_headers).
|
||||
- supports_line_folding = False
|
||||
# Set by subclasses whose client decodes ``Content-Encoding: br``. No
|
||||
# implementation asks for brotli, so this is only true where the client
|
||||
# decodes an encoding it did not request (see BrotliBombHandler).
|
||||
@@ -741,9 +738,10 @@ def test_multi_line_headers(self):
|
||||
# Line folding is an obsolete way of representing headers split across multiple lines.
|
||||
# https://www.rfc-editor.org/info/rfc9112/#section-5.2
|
||||
#
|
||||
- # They must either be folded into a single line or rejected.
|
||||
- # Tornado 6.6 removed support for line folding in simple_httpclient, but they are
|
||||
- # still supported in libcurl.
|
||||
+ # The spec requires user agents to unfold them, but simple_httpclient
|
||||
+ # rejects them as of Tornado 6.6 while libcurl still accepts them. Either
|
||||
+ # outcome is acceptable here; what matters is that the client doesn't hang
|
||||
+ # and doesn't misparse the header.
|
||||
sock, port = bind_unused_port()
|
||||
with closing(sock):
|
||||
|
||||
@@ -767,18 +765,21 @@ def test_multi_line_headers(self):
|
||||
|
||||
netutil.add_accept_handler(sock, accept_callback) # type: ignore
|
||||
try:
|
||||
- if self.supports_line_folding:
|
||||
- resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
- resp.rethrow()
|
||||
- self.assertEqual(resp.headers["X-XSS-Protection"], "1; mode=block")
|
||||
- else:
|
||||
- with ExpectLog(
|
||||
- gen_log, "Malformed HTTP message.*folding", level=logging.INFO
|
||||
- ):
|
||||
- with self.assertRaises(HTTPError):
|
||||
- self.fetch(
|
||||
- "http://127.0.0.1:%d/" % port, raise_error=True
|
||||
- )
|
||||
+ with ExpectLog(
|
||||
+ gen_log,
|
||||
+ "Malformed HTTP message.*folding",
|
||||
+ level=logging.INFO,
|
||||
+ required=False,
|
||||
+ ):
|
||||
+ try:
|
||||
+ resp = self.fetch("http://127.0.0.1:%d/" % port)
|
||||
+ except HTTPError as e:
|
||||
+ self.assertEqual(e.code, 599)
|
||||
+ else:
|
||||
+ resp.rethrow()
|
||||
+ self.assertEqual(
|
||||
+ resp.headers["X-XSS-Protection"], "1; mode=block"
|
||||
+ )
|
||||
finally:
|
||||
self.io_loop.remove_handler(sock.fileno())
|
||||
|
||||
|
||||
diff --git a/tornado/test/curl_httpclient_test.py b/tornado/test/curl_httpclient_test.py
|
||||
index 7a53f206..d7eb1adf 100644
|
||||
--- a/tornado/test/curl_httpclient_test.py
|
||||
+++ b/tornado/test/curl_httpclient_test.py
|
||||
@@ -45,8 +45,6 @@ class CurlHTTPClientCommonTestCase
|
||||
# not we listed it in Accept-Encoding, so brotli is reachable whenever
|
||||
# libcurl was built with it (which is common).
|
||||
decompresses_brotli = pycurl is not None and "brotli" in pycurl.version
|
||||
- # libcurl still accepts obsolete line folding in response headers.
|
||||
- supports_line_folding = True
|
||||
|
||||
def get_http_client(self):
|
||||
client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))
|
||||
@@ -16,6 +16,9 @@ SRC_URI += "file://CVE-2026-91990.patch \
|
||||
file://CVE-2026-103263.patch \
|
||||
file://CVE-2026-103263-dependent.patch \
|
||||
file://CVE-2026-115927.patch \
|
||||
file://CVE-2026-116577_p1.patch \
|
||||
file://CVE-2026-116577_p2.patch \
|
||||
file://CVE-2026-116577_p3.patch \
|
||||
"
|
||||
|
||||
inherit pypi python_setuptools_build_meta
|
||||
|
||||
Reference in new issue
Block a user