Package: release.debian.org Severity: normal Tags: bullseye User: release.debian....@packages.debian.org Usertags: pu X-Debbugs-Cc: d...@debian.org
[ Reason ] Catching up on outstanding security issues. Security team deemed them no-dsa. [ Impact ] Outstanding security issues remain unresolved. [ Tests ] Twisted has a comprehensive test-suite, the relevant updates come with tests, and no regressions were noticed. [ Risks ] The same patches are carried in Ubuntu, and in Debian LTS * ELTS. They did need some backporting to older releases, but nothing too risky. [ 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 ] * CVE-2022-21712: Information disclosure results in leaking of HTTP cookie and authorization headers when following cross origin redirects - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are removed when forming requests, in src/twisted/web/client.py, src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py. - Thanks Canonical for backporting the patches. * CVE-2022-21716: Parsing of SSH version identifier field during an SSH handshake can result in a denial of service when excessively large packets are received - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received handshake buffer is checked, prior to processing version string in src/twisted/conch/ssh/transport.py and src/twisted/conch/test/test_transport.py - Thanks Canonical for backporting the patches. * CVE-2022-24801: Correct several defects in HTTP request parsing that could permit HTTP request smuggling: disallow signed Content-Length headers, forbid illegal characters in chunked extensions, forbid 0x prefix to chunk lengths, and only strip space and horizontal tab from header values. - debian/patches/CVE-2022-24801-*.patch * Patch: remove spurious test for illegal whitespace in xmlns, to allow tests to pass, again. This was a regression introduced by the patch to expat for CVE-2022-25236. The resolution upstream was to just delete the test. [ Other info ] (Anything else the release team should know.)
diff -Nru twisted-20.3.0/debian/changelog twisted-20.3.0/debian/changelog --- twisted-20.3.0/debian/changelog 2021-04-24 12:36:24.000000000 -0400 +++ twisted-20.3.0/debian/changelog 2022-05-05 09:59:26.000000000 -0400 @@ -1,3 +1,30 @@ +twisted (20.3.0-7+deb11u1) bullseye; urgency=medium + + * Team upload. + * CVE-2022-21712: Information disclosure results in leaking of HTTP cookie + and authorization headers when following cross origin redirects + - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are + removed when forming requests, in src/twisted/web/client.py, + src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py. + - Thanks Canonical for backporting the patches. + * CVE-2022-21716: Parsing of SSH version identifier field during an SSH + handshake can result in a denial of service when excessively large packets + are received + - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received + handshake buffer is checked, prior to processing version string in + src/twisted/conch/ssh/transport.py and + src/twisted/conch/test/test_transport.py + - Thanks Canonical for backporting the patches. + * CVE-2022-24801: Correct several defects in HTTP request parsing that could + permit HTTP request smuggling: disallow signed Content-Length headers, + forbid illegal characters in chunked extensions, forbid 0x prefix to chunk + lengths, and only strip space and horizontal tab from header values. + - debian/patches/CVE-2022-24801-*.patch + * Patch: remove spurious test for illegal whitespace in xmlns, to allow + tests to pass, again. + + -- Stefano Rivera <stefa...@debian.org> Thu, 05 May 2022 09:59:26 -0400 + twisted (20.3.0-7) unstable; urgency=medium * Team upload. diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,29 @@ +From 0c44b4806a27d258baf13d6f714f06eddb28da5a Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 15:31:51 -0800 +Subject: [PATCH] correct docstring to suggest the right order + +--- + src/twisted/web/iweb.py | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/src/twisted/web/iweb.py ++++ b/src/twisted/web/iweb.py +@@ -716,12 +716,12 @@ class IAgent(Interface): + obtained by combining a number of (hypothetical) implementations:: + + baseAgent = Agent(reactor) +- redirect = BrowserLikeRedirectAgent(baseAgent, limit=10) ++ decode = ContentDecoderAgent(baseAgent, [(b"gzip", GzipDecoder())]) ++ cookie = CookieAgent(decode, diskStore.cookie) + authenticate = AuthenticateAgent( +- redirect, [diskStore.credentials, GtkAuthInterface()]) +- cookie = CookieAgent(authenticate, diskStore.cookie) +- decode = ContentDecoderAgent(cookie, [(b"gzip", GzipDecoder())]) +- cache = CacheAgent(decode, diskStore.cache) ++ cookie, [diskStore.credentials, GtkAuthInterface()]) ++ cache = CacheAgent(authenticate, diskStore.cache) ++ redirect = BrowserLikeRedirectAgent(cache, limit=10) + + doSomeRequests(cache) + """ diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,273 @@ +From eda4f1e2ec9988a142de244f1a2b285939718c03 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 12:57:49 -0800 +Subject: [PATCH] failing test for header data leak + +--- + src/twisted/web/test/test_agent.py | 154 +++++++++++++++++++++++------ + 1 file changed, 123 insertions(+), 31 deletions(-) + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -5,11 +5,17 @@ + Tests for L{twisted.web.client.Agent} and related new client APIs. + """ + +-import zlib +- + from io import BytesIO +- +-from zope.interface.verify import verifyObject ++from twisted.test.iosim import FakeTransport, IOPump ++from twisted.test.proto_helpers import ( ++ AccumulatingProtocol, ++ EventLoggingObserver, ++ MemoryReactorClock, ++ StringTransport, ++) ++from twisted.test.test_sslverify import certificatesForAuthorityAndServer ++from typing import Optional, TYPE_CHECKING ++from unittest import skipIf, SkipTest + + from twisted.trial.unittest import TestCase, SynchronousTestCase + from twisted.web import client, error, http_headers +@@ -20,8 +26,6 @@ from twisted.internet import defer, task + from twisted.python.failure import Failure + from twisted.python.compat import cookielib, intToBytes, unicode + from twisted.python.components import proxyForInterface +-from twisted.test.proto_helpers import (StringTransport, MemoryReactorClock, +- EventLoggingObserver) + from twisted.internet.task import Clock + from twisted.internet.error import ConnectionRefusedError, ConnectionDone + from twisted.internet.error import ConnectionLost +@@ -30,33 +34,48 @@ from twisted.internet.defer import Defer + from twisted.internet.endpoints import TCP4ClientEndpoint + from twisted.internet.address import IPv4Address, IPv6Address + +-from twisted.web.client import (FileBodyProducer, Request, HTTPConnectionPool, +- ResponseDone, _HTTP11ClientFactory, URI) ++from twisted.web.client import ( ++ FileBodyProducer, ++ BrowserLikePolicyForHTTPS, ++ Request, ++ HTTPConnectionPool, ++ HostnameCachingHTTPSPolicy, ++ ResponseDone, ++ _HTTP11ClientFactory, ++ URI, ++) + + from twisted.web.iweb import ( +- UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse, IAgentEndpointFactory, +- ) ++ IAgent, ++ IBodyProducer, ++ IPolicyForHTTPS, ++ IRequest, ++ IResponse, ++ IAgentEndpointFactory, ++ UNKNOWN_LENGTH, ++) + from twisted.web.http_headers import Headers + from twisted.web._newclient import HTTP11ClientProtocol, Response + + from twisted.internet.interfaces import IOpenSSLClientConnectionCreator +-from zope.interface.declarations import implementer + from twisted.web.iweb import IPolicyForHTTPS + from twisted.python.deprecate import getDeprecationWarningString + from incremental import Version ++ ++import zlib + from twisted.web.client import (BrowserLikePolicyForHTTPS, + HostnameCachingHTTPSPolicy) + from twisted.internet.test.test_endpoints import deterministicResolvingReactor + from twisted.internet.endpoints import HostnameEndpoint +-from twisted.test.proto_helpers import AccumulatingProtocol +-from twisted.test.iosim import IOPump, FakeTransport +-from twisted.test.test_sslverify import certificatesForAuthorityAndServer + from twisted.web.test.injectionhelpers import ( + MethodInjectionTestsMixin, + URIInjectionTestsMixin, + ) + from twisted.web.error import SchemeNotSupported + from twisted.logger import globalLogPublisher ++from zope.interface.declarations import implementer ++from zope.interface.verify import verifyObject ++ + + try: + from twisted.internet import ssl +@@ -2627,12 +2646,30 @@ class ProxyAgentTests(TestCase, FakeReac + self.assertEqual(agent._pool.connected, True) + + ++SENSITIVE_HEADERS = [ ++ b"authorization", ++ b"cookie", ++ b"cookie2", ++ b"proxy-authorization", ++ b"www-authenticate", ++] + +-class _RedirectAgentTestsMixin(object): ++if TYPE_CHECKING: ++ testMixinClass = TestCase ++else: ++ testMixinClass = object ++ ++ ++class _RedirectAgentTestsMixin(testMixinClass): + """ + Test cases mixin for L{RedirectAgentTests} and + L{BrowserLikeRedirectAgentTests}. + """ ++ ++ agent: IAgent ++ reactor: MemoryReactorClock ++ protocol: StubHTTPProtocol ++ + def test_noRedirect(self): + """ + L{client.RedirectAgent} behaves like L{client.Agent} if the response +@@ -2651,34 +2688,53 @@ class _RedirectAgentTestsMixin(object): + self.assertIdentical(response, result) + self.assertIdentical(result.previousResponse, None) + ++ def _testRedirectDefault( ++ self, ++ code: int, ++ crossScheme: bool = False, ++ crossDomain: bool = False, ++ requestHeaders: Optional[Headers] = None, ++ ) -> IRequest: + +- def _testRedirectDefault(self, code): + """ + When getting a redirect, L{client.RedirectAgent} follows the URL + specified in the L{Location} header field and make a new request. + + @param code: HTTP status code. + """ +- self.agent.request(b'GET', b'http://example.com/foo') ++ startDomain = b"example.com" ++ startScheme = b"https" if ssl is not None else b"http" ++ startPort = 80 if startScheme == b"http" else 443 ++ self.agent.request( ++ b"GET", startScheme + b"://" + startDomain + b"/foo", headers=requestHeaders ++ ) + + host, port = self.reactor.tcpClients.pop()[:2] + self.assertEqual(EXAMPLE_COM_IP, host) +- self.assertEqual(80, port) ++ self.assertEqual(startPort, port) + + req, res = self.protocol.requests.pop() + +- # If possible (i.e.: SSL support is present), run the test with a ++ # If possible (i.e.: TLS support is present), run the test with a + # cross-scheme redirect to verify that the scheme is honored; if not, + # let's just make sure it works at all. +- if ssl is None: +- scheme = b'http' +- expectedPort = 80 +- else: +- scheme = b'https' +- expectedPort = 443 + ++ targetScheme = startScheme ++ targetDomain = startDomain ++ targetPort = startPort ++ ++ if crossDomain: ++ if ssl is None: ++ raise SkipTest( ++ "Cross-scheme redirects can't be tested without TLS support." ++ ) ++ targetScheme = b"https" if startScheme == b"http" else b"https" ++ targetPort = 443 if startPort == 80 else 443 ++ ++ targetDomain = b"example.net" if crossDomain else startDomain + headers = http_headers.Headers( +- {b'location': [scheme + b'://example.com/bar']}) ++ {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]} ++ ) + response = Response((b'HTTP', 1, 1), code, b'OK', headers, None) + res.callback(response) + +@@ -2687,8 +2743,9 @@ class _RedirectAgentTestsMixin(object): + self.assertEqual(b'/bar', req2.uri) + + host, port = self.reactor.tcpClients.pop()[:2] +- self.assertEqual(EXAMPLE_COM_IP, host) +- self.assertEqual(expectedPort, port) ++ self.assertEqual(EXAMPLE_NET_IP if crossDomain else EXAMPLE_COM_IP, host) ++ self.assertEqual(targetPort, port) ++ return req2 + + + def test_redirect301(self): +@@ -2698,6 +2755,15 @@ class _RedirectAgentTestsMixin(object): + self._testRedirectDefault(301) + + ++ def test_redirect301Scheme(self): ++ """ ++ L{client.RedirectAgent} follows cross-scheme redirects. ++ """ ++ self._testRedirectDefault( ++ 301, ++ crossScheme=True, ++ ) ++ + def test_redirect302(self): + """ + L{client.RedirectAgent} follows redirects on status code 302. +@@ -2712,6 +2778,48 @@ class _RedirectAgentTestsMixin(object): + self._testRedirectDefault(307) + + ++ def test_headerSecurity(self): ++ """ ++ L{client.RedirectAgent} scrubs sensitive headers when redirecting. ++ """ ++ sensitiveHeaderValues = { ++ b"authorization": [b"sensitive-authnz"], ++ b"cookie": [b"sensitive-cookie-data"], ++ b"cookie2": [b"sensitive-cookie2-data"], ++ b"proxy-authorization": [b"sensitive-proxy-auth"], ++ b"wWw-auThentiCate": [b"sensitive-authn"], ++ } ++ otherHeaderValues = {b"x-random-header": [b"x-random-value"]} ++ allHeaders = Headers({**sensitiveHeaderValues, **otherHeaderValues}) ++ redirected = self._testRedirectDefault(301, requestHeaders=allHeaders) ++ ++ def normHeaders(headers: Headers) -> dict: ++ return {k.lower(): v for (k, v) in headers.getAllRawHeaders()} ++ ++ sameOriginHeaders = normHeaders(redirected.headers) ++ self.assertEquals( ++ sameOriginHeaders, ++ { ++ b"host": [b"example.com"], ++ **normHeaders(allHeaders), ++ }, ++ ) ++ ++ redirectedElsewhere = self._testRedirectDefault( ++ 301, ++ crossDomain=True, ++ requestHeaders=Headers({**sensitiveHeaderValues, **otherHeaderValues}), ++ ) ++ otherOriginHeaders = normHeaders(redirectedElsewhere.headers) ++ self.assertEquals( ++ otherOriginHeaders, ++ { ++ b"host": [b"example.com"], ++ **normHeaders(Headers(otherHeaderValues)), ++ }, ++ ) ++ ++ + def _testRedirectToGet(self, code, method): + """ + L{client.RedirectAgent} changes the method to I{GET} when getting diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,20 @@ +From ecc2ae81c831e58cf1725dfe2e5b6d2951c884a0 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 13:00:02 -0800 +Subject: [PATCH] assert on correct host + +--- + src/twisted/web/test/test_agent.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -2814,7 +2814,7 @@ class _RedirectAgentTestsMixin(testMixin + self.assertEquals( + otherOriginHeaders, + { +- b"host": [b"example.com"], ++ b"host": [b"example.net"], + **normHeaders(Headers(otherHeaderValues)), + }, + ) diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,240 @@ +From c1923d24b6a2752ea5d5686851427e0ec5757543 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:04:27 -0800 +Subject: [PATCH] tests for domain/port/scheme and fix + +--- + src/twisted/web/client.py | 61 +++++++++++++++++++++--------- + src/twisted/web/test/test_agent.py | 52 ++++++++++++++++++++----- + 2 files changed, 86 insertions(+), 27 deletions(-) + +--- a/src/twisted/web/client.py ++++ b/src/twisted/web/client.py +@@ -14,20 +14,17 @@ import warnings + + from urllib.parse import urljoin, urldefrag + from urllib.parse import urlunparse as _urlunparse ++from typing import Iterable + +-import zlib + from functools import wraps + +-from zope.interface import implementer +- ++import zlib + from twisted.python.compat import _PY3, networkString + from twisted.python.compat import nativeString, intToBytes, unicode, itervalues +-from twisted.python.deprecate import deprecatedModuleAttribute, deprecated ++from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString + from twisted.python.failure import Failure + from incremental import Version + +-from twisted.web.iweb import IPolicyForHTTPS, IAgentEndpointFactory +-from twisted.python.deprecate import getDeprecationWarningString + from twisted.web import http + from twisted.internet import defer, protocol, task, reactor + from twisted.internet.abstract import isIPv6Address +@@ -36,11 +33,12 @@ from twisted.internet.endpoints import H + from twisted.python.util import InsensitiveDict + from twisted.python.components import proxyForInterface + from twisted.web import error +-from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse + from twisted.web.http_headers import Headers + from twisted.logger import Logger + + from twisted.web._newclient import _ensureValidURI, _ensureValidMethod ++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH ++from zope.interface import implementer + + + +@@ -2089,6 +2087,15 @@ class ContentDecoderAgent(object): + + + ++_canonicalHeaderName = Headers()._canonicalNameCaps ++_defaultSensitiveHeaders = frozenset([ ++ b"Authorization", ++ b"Cookie", ++ b"Cookie2", ++ b"Proxy-Authorization", ++ b"WWW-Authenticate", ++]) ++ + @implementer(IAgent) + class RedirectAgent(object): + """ +@@ -2103,6 +2110,11 @@ class RedirectAgent(object): + @param redirectLimit: The maximum number of times the agent is allowed to + follow redirects before failing with a L{error.InfiniteRedirection}. + ++ @param sensitiveHeaderNames: An iterable of C{bytes} enumerating the names ++ of headers that must not be transmitted when redirecting to a different ++ origins. These will be consulted in addition to the protocol-specified ++ set of headers that contain sensitive information. ++ + @cvar _redirectResponses: A L{list} of HTTP status codes to be redirected + for I{GET} and I{HEAD} methods. + +@@ -2117,9 +2129,17 @@ class RedirectAgent(object): + _seeOtherResponses = [http.SEE_OTHER] + + +- def __init__(self, agent, redirectLimit=20): ++ def __init__( ++ self, ++ agent: IAgent, ++ redirectLimit: int = 20, ++ sensitiveHeaderNames: Iterable[bytes] = (), ++ ): + self._agent = agent + self._redirectLimit = redirectLimit ++ sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames) ++ sensitive.update(_defaultSensitiveHeaders) ++ self._sensitiveHeaderNames = sensitive + + + def request(self, method, uri, headers=None, bodyProducer=None): +@@ -2166,6 +2186,22 @@ class RedirectAgent(object): + response.code, b'No location header field', uri) + raise ResponseFailed([Failure(err)], response) + location = self._resolveLocation(uri, locationHeaders[0]) ++ if headers: ++ parsedURI = URI.fromBytes(uri) ++ parsedLocation = URI.fromBytes(location) ++ sameOrigin = ( ++ (parsedURI.scheme == parsedLocation.scheme) ++ and (parsedURI.host == parsedLocation.host) ++ and (parsedURI.port == parsedLocation.port) ++ ) ++ if not sameOrigin: ++ headers = Headers( ++ { ++ rawName: rawValue ++ for rawName, rawValue in headers.getAllRawHeaders() ++ if rawName not in self._sensitiveHeaderNames ++ } ++ ) + deferred = self._agent.request(method, location, headers) + def _chainResponse(newResponse): + newResponse.setPreviousResponse(response) +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -2693,6 +2693,7 @@ class _RedirectAgentTestsMixin(testMixin + code: int, + crossScheme: bool = False, + crossDomain: bool = False, ++ crossPort: bool = False, + requestHeaders: Optional[Headers] = None, + ) -> IRequest: + +@@ -2723,17 +2724,22 @@ class _RedirectAgentTestsMixin(testMixin + targetDomain = startDomain + targetPort = startPort + +- if crossDomain: ++ if crossScheme: + if ssl is None: + raise SkipTest( + "Cross-scheme redirects can't be tested without TLS support." + ) +- targetScheme = b"https" if startScheme == b"http" else b"https" +- targetPort = 443 if startPort == 80 else 443 ++ targetScheme = b"https" if startScheme == b"http" else b"http" ++ targetPort = 443 if startPort == 80 else 80 + ++ portSyntax = b'' ++ if crossPort: ++ targetPort = 8443 ++ portSyntax = b':8443' + targetDomain = b"example.net" if crossDomain else startDomain ++ locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar" + headers = http_headers.Headers( +- {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]} ++ {b"location": [locationValue]} + ) + response = Response((b'HTTP', 1, 1), code, b'OK', headers, None) + res.callback(response) +@@ -2778,9 +2784,10 @@ class _RedirectAgentTestsMixin(testMixin + self._testRedirectDefault(307) + + +- def test_headerSecurity(self): ++ def _sensitiveHeadersTest(self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict) -> None: + """ +- L{client.RedirectAgent} scrubs sensitive headers when redirecting. ++ L{client.RedirectAgent} scrubs sensitive headers when redirecting ++ between differing origins. + """ + sensitiveHeaderValues = { + b"authorization": [b"sensitive-authnz"], +@@ -2788,6 +2795,7 @@ class _RedirectAgentTestsMixin(testMixin + b"cookie2": [b"sensitive-cookie2-data"], + b"proxy-authorization": [b"sensitive-proxy-auth"], + b"wWw-auThentiCate": [b"sensitive-authn"], ++ b"x-custom-sensitive": [b"sensitive-custom"], + } + otherHeaderValues = {b"x-random-header": [b"x-random-value"]} + allHeaders = Headers({**sensitiveHeaderValues, **otherHeaderValues}) +@@ -2807,18 +2815,38 @@ class _RedirectAgentTestsMixin(testMixin + + redirectedElsewhere = self._testRedirectDefault( + 301, +- crossDomain=True, ++ **crossKwargs, + requestHeaders=Headers({**sensitiveHeaderValues, **otherHeaderValues}), + ) + otherOriginHeaders = normHeaders(redirectedElsewhere.headers) + self.assertEquals( + otherOriginHeaders, + { +- b"host": [b"example.net"], ++ b"host": [expectedHostHeader], + **normHeaders(Headers(otherHeaderValues)), + }, + ) + ++ def test_crossDomainHeaders(self) -> None: ++ """ ++ L{client.RedirectAgent} scrubs sensitive headers when redirecting ++ between differing domains. ++ """ ++ self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net') ++ ++ def test_crossPortHeaders(self) -> None: ++ """ ++ L{client.RedirectAgent} scrubs sensitive headers when redirecting ++ between differing ports. ++ """ ++ self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443') ++ ++ def test_crossSchemeHeaders(self) -> None: ++ """ ++ L{client.RedirectAgent} scrubs sensitive headers when redirecting ++ between differing schemes. ++ """ ++ self._sensitiveHeadersTest(crossScheme=True) + + def _testRedirectToGet(self, code, method): + """ +@@ -3045,7 +3073,9 @@ class RedirectAgentTests(TestCase, FakeR + @return: a new L{twisted.web.client.RedirectAgent} + """ + return client.RedirectAgent( +- self.buildAgentForWrapperTest(self.reactor)) ++ self.buildAgentForWrapperTest(self.reactor), ++ sensitiveHeaderNames=[b"X-Custom-sensitive"], ++ ) + + + def setUp(self): +@@ -3084,7 +3114,8 @@ class BrowserLikeRedirectAgentTests(Test + @return: a new L{twisted.web.client.BrowserLikeRedirectAgent} + """ + return client.BrowserLikeRedirectAgent( +- self.buildAgentForWrapperTest(self.reactor)) ++ self.buildAgentForWrapperTest(self.reactor), ++ sensitiveHeaderNames=[b"x-Custom-sensitive"]) + + + def setUp(self): diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,15 @@ +From 562dcff9c7dd846857932443a0a2935f74c2a2de Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:11:46 -0800 +Subject: [PATCH] topfile + +--- + src/twisted/topfiles/10294.bugfix | 1 + + 1 file changed, 1 insertion(+) + create mode 100644 src/twisted/topfiles/10294.bugfix + +--- /dev/null ++++ b/src/twisted/topfiles/10294.bugfix +@@ -0,0 +1 @@ ++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin. +\ No newline at end of file diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,222 @@ +From 4ae6732a30893178891ee3a383d8a737e0ec21eb Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:16:18 -0800 +Subject: [PATCH] reblackening + +--- + src/twisted/web/client.py | 41 +++++++++++++++-------- + src/twisted/web/test/test_agent.py | 52 ++++++++++++++++-------------- + 2 files changed, 55 insertions(+), 38 deletions(-) + +--- a/src/twisted/web/client.py ++++ b/src/twisted/web/client.py +@@ -11,17 +11,23 @@ from __future__ import division, absolut + import os + import collections + import warnings ++import zlib + +-from urllib.parse import urljoin, urldefrag +-from urllib.parse import urlunparse as _urlunparse + from typing import Iterable ++from urllib.parse import urldefrag, urljoin, urlunparse as _urlunparse ++ ++from zope.interface import implementer + + from functools import wraps + +-import zlib + from twisted.python.compat import _PY3, networkString + from twisted.python.compat import nativeString, intToBytes, unicode, itervalues +-from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString ++from twisted.python.deprecate import ( ++ deprecated, ++ deprecatedModuleAttribute, ++ getDeprecationWarningString, ++) ++ + from twisted.python.failure import Failure + from incremental import Version + +@@ -37,9 +43,14 @@ from twisted.web.http_headers import Hea + from twisted.logger import Logger + + from twisted.web._newclient import _ensureValidURI, _ensureValidMethod +-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH +-from zope.interface import implementer +- ++from twisted.web.iweb import ( ++ UNKNOWN_LENGTH, ++ IAgent, ++ IAgentEndpointFactory, ++ IBodyProducer, ++ IPolicyForHTTPS, ++ IResponse, ++) + + + def urlunparse(parts): +@@ -2088,13 +2099,16 @@ class ContentDecoderAgent(object): + + + _canonicalHeaderName = Headers()._canonicalNameCaps +-_defaultSensitiveHeaders = frozenset([ +- b"Authorization", +- b"Cookie", +- b"Cookie2", +- b"Proxy-Authorization", +- b"WWW-Authenticate", +-]) ++_defaultSensitiveHeaders = frozenset( ++ [ ++ b"Authorization", ++ b"Cookie", ++ b"Cookie2", ++ b"Proxy-Authorization", ++ b"WWW-Authenticate", ++ ] ++) ++ + + @implementer(IAgent) + class RedirectAgent(object): +@@ -2137,7 +2151,7 @@ class RedirectAgent(object): + ): + self._agent = agent + self._redirectLimit = redirectLimit +- sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames) ++ sensitive = {_canonicalHeaderName(each) for each in sensitiveHeaderNames} + sensitive.update(_defaultSensitiveHeaders) + self._sensitiveHeaderNames = sensitive + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -4,18 +4,13 @@ + """ + Tests for L{twisted.web.client.Agent} and related new client APIs. + """ +- ++import zlib + from io import BytesIO +-from twisted.test.iosim import FakeTransport, IOPump +-from twisted.test.proto_helpers import ( +- AccumulatingProtocol, +- EventLoggingObserver, +- MemoryReactorClock, +- StringTransport, +-) +-from twisted.test.test_sslverify import certificatesForAuthorityAndServer +-from typing import Optional, TYPE_CHECKING +-from unittest import skipIf, SkipTest ++from typing import TYPE_CHECKING, Optional ++from unittest import SkipTest, skipIf ++ ++from zope.interface.declarations import implementer ++from zope.interface.verify import verifyObject + + from twisted.trial.unittest import TestCase, SynchronousTestCase + from twisted.web import client, error, http_headers +@@ -34,25 +29,34 @@ from twisted.internet.defer import Defer + from twisted.internet.endpoints import TCP4ClientEndpoint + from twisted.internet.address import IPv4Address, IPv6Address + ++from twisted.test.iosim import FakeTransport, IOPump ++from twisted.test.proto_helpers import ( ++ AccumulatingProtocol, ++ EventLoggingObserver, ++ MemoryReactorClock, ++ StringTransport, ++) ++from twisted.test.test_sslverify import certificatesForAuthorityAndServer ++ + from twisted.web.client import ( ++ URI, + FileBodyProducer, + BrowserLikePolicyForHTTPS, + Request, +- HTTPConnectionPool, + HostnameCachingHTTPSPolicy, ++ HTTPConnectionPool, + ResponseDone, + _HTTP11ClientFactory, +- URI, + ) + + from twisted.web.iweb import ( ++ UNKNOWN_LENGTH, + IAgent, + IBodyProducer, + IPolicyForHTTPS, + IRequest, + IResponse, + IAgentEndpointFactory, +- UNKNOWN_LENGTH, + ) + from twisted.web.http_headers import Headers + from twisted.web._newclient import HTTP11ClientProtocol, Response +@@ -62,7 +66,6 @@ from twisted.web.iweb import IPolicyForH + from twisted.python.deprecate import getDeprecationWarningString + from incremental import Version + +-import zlib + from twisted.web.client import (BrowserLikePolicyForHTTPS, + HostnameCachingHTTPSPolicy) + from twisted.internet.test.test_endpoints import deterministicResolvingReactor +@@ -73,8 +76,6 @@ from twisted.web.test.injectionhelpers i + ) + from twisted.web.error import SchemeNotSupported + from twisted.logger import globalLogPublisher +-from zope.interface.declarations import implementer +-from zope.interface.verify import verifyObject + + + try: +@@ -2732,15 +2733,13 @@ class _RedirectAgentTestsMixin(testMixin + targetScheme = b"https" if startScheme == b"http" else b"http" + targetPort = 443 if startPort == 80 else 80 + +- portSyntax = b'' ++ portSyntax = b"" + if crossPort: + targetPort = 8443 +- portSyntax = b':8443' ++ portSyntax = b":8443" + targetDomain = b"example.net" if crossDomain else startDomain + locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar" +- headers = http_headers.Headers( +- {b"location": [locationValue]} +- ) ++ headers = http_headers.Headers({b"location": [locationValue]}) + response = Response((b'HTTP', 1, 1), code, b'OK', headers, None) + res.callback(response) + +@@ -2784,7 +2783,9 @@ class _RedirectAgentTestsMixin(testMixin + self._testRedirectDefault(307) + + +- def _sensitiveHeadersTest(self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict) -> None: ++ def _sensitiveHeadersTest( ++ self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict ++ ) -> None: + """ + L{client.RedirectAgent} scrubs sensitive headers when redirecting + between differing origins. +@@ -2832,14 +2833,16 @@ class _RedirectAgentTestsMixin(testMixin + L{client.RedirectAgent} scrubs sensitive headers when redirecting + between differing domains. + """ +- self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net') ++ self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b"example.net") + + def test_crossPortHeaders(self) -> None: + """ + L{client.RedirectAgent} scrubs sensitive headers when redirecting + between differing ports. + """ +- self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443') ++ self._sensitiveHeadersTest( ++ crossPort=True, expectedHostHeader=b"example.com:8443" ++ ) + + def test_crossSchemeHeaders(self) -> None: + """ diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,20 @@ +From 8a4b1f3e0e7d2316944064960d9dcb4556525845 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:16:48 -0800 +Subject: [PATCH] oops + +--- + src/twisted/{topfiles => newsfragments}/10294.bugfix | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + rename src/twisted/{topfiles => newsfragments}/10294.bugfix (100%) + +--- /dev/null ++++ b/src/twisted/newsfragments/10294.bugfix +@@ -0,0 +1 @@ ++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin. +\ No newline at end of file +--- a/src/twisted/topfiles/10294.bugfix ++++ /dev/null +@@ -1 +0,0 @@ +-twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin. +\ No newline at end of file diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,179 @@ +From 9d53c0f8b38340fe5ca3fa1a7df49347c2dea18a Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:37:22 -0800 +Subject: [PATCH] tell mypy enough that it can actually find bugs + +--- + src/twisted/web/test/test_agent.py | 88 +++++++++--------------------- + 1 file changed, 27 insertions(+), 61 deletions(-) + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -6,77 +6,54 @@ Tests for L{twisted.web.client.Agent} an + """ + import zlib + from io import BytesIO +-from typing import TYPE_CHECKING, Optional ++from twisted.test.iosim import FakeTransport, IOPump ++from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport ++from twisted.test.test_sslverify import certificatesForAuthorityAndServer ++from typing import List, Optional, TYPE_CHECKING, Tuple, cast + from unittest import SkipTest, skipIf + +-from zope.interface.declarations import implementer +-from zope.interface.verify import verifyObject + + from twisted.trial.unittest import TestCase, SynchronousTestCase + from twisted.web import client, error, http_headers +-from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed +-from twisted.web._newclient import ResponseNeverReceived, ResponseFailed +-from twisted.web._newclient import PotentialDataLoss + from twisted.internet import defer, task + from twisted.python.failure import Failure + from twisted.python.compat import cookielib, intToBytes, unicode + from twisted.python.components import proxyForInterface + from twisted.internet.task import Clock +-from twisted.internet.error import ConnectionRefusedError, ConnectionDone +-from twisted.internet.error import ConnectionLost ++from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError + from twisted.internet.protocol import Protocol, Factory + from twisted.internet.defer import Deferred, succeed, CancelledError + from twisted.internet.endpoints import TCP4ClientEndpoint + from twisted.internet.address import IPv4Address, IPv6Address + +-from twisted.test.iosim import FakeTransport, IOPump +-from twisted.test.proto_helpers import ( +- AccumulatingProtocol, +- EventLoggingObserver, +- MemoryReactorClock, +- StringTransport, +-) +-from twisted.test.test_sslverify import certificatesForAuthorityAndServer ++from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived ++from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, HostnameCachingHTTPSPolicy, Request, ResponseDone, URI, _HTTP11ClientFactory + +-from twisted.web.client import ( +- URI, +- FileBodyProducer, +- BrowserLikePolicyForHTTPS, +- Request, +- HostnameCachingHTTPSPolicy, +- HTTPConnectionPool, +- ResponseDone, +- _HTTP11ClientFactory, +-) +- +-from twisted.web.iweb import ( +- UNKNOWN_LENGTH, +- IAgent, +- IBodyProducer, +- IPolicyForHTTPS, +- IRequest, +- IResponse, +- IAgentEndpointFactory, +-) + from twisted.web.http_headers import Headers +-from twisted.web._newclient import HTTP11ClientProtocol, Response + + from twisted.internet.interfaces import IOpenSSLClientConnectionCreator + from twisted.web.iweb import IPolicyForHTTPS + from twisted.python.deprecate import getDeprecationWarningString + from incremental import Version + +-from twisted.web.client import (BrowserLikePolicyForHTTPS, +- HostnameCachingHTTPSPolicy) + from twisted.internet.test.test_endpoints import deterministicResolvingReactor + from twisted.internet.endpoints import HostnameEndpoint +-from twisted.web.test.injectionhelpers import ( +- MethodInjectionTestsMixin, +- URIInjectionTestsMixin, +-) + from twisted.web.error import SchemeNotSupported + from twisted.logger import globalLogPublisher + ++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH ++from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin ++from zope.interface.declarations import implementer ++from zope.interface.verify import verifyObject ++ ++# Creatively lie to mypy about the nature of inheritance, since dealing with ++# expectations of a mixin class is basically impossible (don't use mixins). ++if TYPE_CHECKING: ++ testMixinClass = TestCase ++ runtimeTestCase = object ++else: ++ testMixinClass = object ++ runtimeTestCase = TestCase + + try: + from twisted.internet import ssl +@@ -112,11 +89,10 @@ class StubHTTPProtocol(Protocol): + tuple consisting of the request and the L{Deferred} returned from the + request method is appended to this list. + """ +- def __init__(self): +- self.requests = [] ++ def __init__(self) -> None: ++ self.requests: List[Tuple[Request, Deferred[IResponse]]] = [] + self.state = 'QUIESCENT' + +- + def request(self, request): + """ + Capture the given request for later inspection. +@@ -2655,11 +2631,6 @@ SENSITIVE_HEADERS = [ + b"www-authenticate", + ] + +-if TYPE_CHECKING: +- testMixinClass = TestCase +-else: +- testMixinClass = object +- + + class _RedirectAgentTestsMixin(testMixinClass): + """ +@@ -2696,7 +2667,7 @@ class _RedirectAgentTestsMixin(testMixin + crossDomain: bool = False, + crossPort: bool = False, + requestHeaders: Optional[Headers] = None, +- ) -> IRequest: ++ ) -> Request: + + """ + When getting a redirect, L{client.RedirectAgent} follows the URL +@@ -2784,7 +2755,7 @@ class _RedirectAgentTestsMixin(testMixin + + + def _sensitiveHeadersTest( +- self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict ++ self, expectedHostHeader: bytes = b"example.com", **crossKwargs: bool + ) -> None: + """ + L{client.RedirectAgent} scrubs sensitive headers when redirecting +@@ -3066,8 +3037,9 @@ class _RedirectAgentTestsMixin(testMixin + + + +-class RedirectAgentTests(TestCase, FakeReactorAndConnectMixin, +- _RedirectAgentTestsMixin, AgentTestsMixin): ++class RedirectAgentTests( ++ FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase, ++): + """ + Tests for L{client.RedirectAgent}. + """ +@@ -3105,10 +3077,9 @@ class RedirectAgentTests(TestCase, FakeR + + + +-class BrowserLikeRedirectAgentTests(TestCase, +- FakeReactorAndConnectMixin, +- _RedirectAgentTestsMixin, +- AgentTestsMixin): ++class BrowserLikeRedirectAgentTests( ++ FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase ++): + """ + Tests for L{client.BrowserLikeRedirectAgent}. + """ diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,134 @@ +From adadd2c6a49a9b5f9b41a1f125004b68449d2fb5 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:40:20 -0800 +Subject: [PATCH] really need to fix my editor to agree with our black / isort + config + +--- + src/twisted/web/test/test_agent.py | 72 ++++++++++++++++++++++++------ + 1 file changed, 58 insertions(+), 14 deletions(-) + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -4,14 +4,14 @@ + """ + Tests for L{twisted.web.client.Agent} and related new client APIs. + """ ++ + import zlib + from io import BytesIO +-from twisted.test.iosim import FakeTransport, IOPump +-from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport +-from twisted.test.test_sslverify import certificatesForAuthorityAndServer +-from typing import List, Optional, TYPE_CHECKING, Tuple, cast ++from typing import TYPE_CHECKING, List, Optional, Tuple, cast + from unittest import SkipTest, skipIf + ++from zope.interface.declarations import implementer ++from zope.interface.verify import verifyObject + + from twisted.trial.unittest import TestCase, SynchronousTestCase + from twisted.web import client, error, http_headers +@@ -20,19 +20,18 @@ from twisted.python.failure import Failu + from twisted.python.compat import cookielib, intToBytes, unicode + from twisted.python.components import proxyForInterface + from twisted.internet.task import Clock +-from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError ++from twisted.internet.error import ( ++ ConnectionDone, ++ ConnectionLost, ++ ConnectionRefusedError, ++) ++ + from twisted.internet.protocol import Protocol, Factory + from twisted.internet.defer import Deferred, succeed, CancelledError + from twisted.internet.endpoints import TCP4ClientEndpoint + from twisted.internet.address import IPv4Address, IPv6Address +- +-from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived +-from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, HostnameCachingHTTPSPolicy, Request, ResponseDone, URI, _HTTP11ClientFactory +- + from twisted.web.http_headers import Headers +- + from twisted.internet.interfaces import IOpenSSLClientConnectionCreator +-from twisted.web.iweb import IPolicyForHTTPS + from twisted.python.deprecate import getDeprecationWarningString + from incremental import Version + +@@ -40,11 +39,48 @@ from twisted.internet.test.test_endpoint + from twisted.internet.endpoints import HostnameEndpoint + from twisted.web.error import SchemeNotSupported + from twisted.logger import globalLogPublisher ++from twisted.test.iosim import FakeTransport, IOPump ++from twisted.test.proto_helpers import ( ++ AccumulatingProtocol, ++ EventLoggingObserver, ++ MemoryReactorClock, ++ StringTransport, ++) ++from twisted.test.test_sslverify import certificatesForAuthorityAndServer ++ ++from twisted.web._newclient import ( ++ HTTP11ClientProtocol, ++ PotentialDataLoss, ++ RequestNotSent, ++ RequestTransmissionFailed, ++ Response, ++ ResponseFailed, ++ ResponseNeverReceived, ++) ++from twisted.web.client import ( ++ URI, ++ BrowserLikePolicyForHTTPS, ++ FileBodyProducer, ++ HostnameCachingHTTPSPolicy, ++ HTTPConnectionPool, ++ Request, ++ ResponseDone, ++ _HTTP11ClientFactory, ++) ++from twisted.web.iweb import ( ++ UNKNOWN_LENGTH, ++ IAgent, ++ IAgentEndpointFactory, ++ IBodyProducer, ++ IPolicyForHTTPS, ++ IRequest, ++ IResponse, ++) ++from twisted.web.test.injectionhelpers import ( ++ MethodInjectionTestsMixin, ++ URIInjectionTestsMixin, ++) + +-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH +-from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin +-from zope.interface.declarations import implementer +-from zope.interface.verify import verifyObject + + # Creatively lie to mypy about the nature of inheritance, since dealing with + # expectations of a mixin class is basically impossible (don't use mixins). +@@ -3038,7 +3074,10 @@ class _RedirectAgentTestsMixin(testMixin + + + class RedirectAgentTests( +- FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase, ++ FakeReactorAndConnectMixin, ++ _RedirectAgentTestsMixin, ++ AgentTestsMixin, ++ runtimeTestCase, + ): + """ + Tests for L{client.RedirectAgent}. +@@ -3078,7 +3117,10 @@ class RedirectAgentTests( + + + class BrowserLikeRedirectAgentTests( +- FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase ++ FakeReactorAndConnectMixin, ++ _RedirectAgentTestsMixin, ++ AgentTestsMixin, ++ runtimeTestCase, + ): + """ + Tests for L{client.BrowserLikeRedirectAgent}. diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch --- twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,28 @@ +From 4eda9eabbba8ba2ccc45daad7208ff3db25ac348 Mon Sep 17 00:00:00 2001 +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 23 Jan 2022 14:55:10 -0800 +Subject: [PATCH] lint fix + +--- + src/twisted/web/test/test_agent.py | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +--- a/src/twisted/web/test/test_agent.py ++++ b/src/twisted/web/test/test_agent.py +@@ -7,7 +7,7 @@ Tests for L{twisted.web.client.Agent} an + + import zlib + from io import BytesIO +-from typing import TYPE_CHECKING, List, Optional, Tuple, cast ++from typing import TYPE_CHECKING, List, Optional, Tuple + from unittest import SkipTest, skipIf + + from zope.interface.declarations import implementer +@@ -73,7 +73,6 @@ from twisted.web.iweb import ( + IAgentEndpointFactory, + IBodyProducer, + IPolicyForHTTPS, +- IRequest, + IResponse, + ) + from twisted.web.test.injectionhelpers import ( diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch --- twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,66 @@ +From de90dfe1519e996dd150de751c670f8e03daa089 Mon Sep 17 00:00:00 2001 +From: Adi Roiban <adi.roi...@chevah.com> +Date: Mon, 24 Jan 2022 19:09:04 +0000 +Subject: [PATCH] Initial fix for Twisted version string DoS. + +--- + src/twisted/conch/ssh/transport.py | 9 +++++++++ + src/twisted/conch/test/test_transport.py | 22 ++++++++++++++++++++++ + src/twisted/newsfragments/10284.bugfix | 2 ++ + 3 files changed, 33 insertions(+) + create mode 100644 src/twisted/newsfragments/10284.bugfix + +--- a/src/twisted/conch/ssh/transport.py ++++ b/src/twisted/conch/ssh/transport.py +@@ -692,6 +692,15 @@ class SSHTransportBase(protocol.Protocol + """ + self.buf = self.buf + data + if not self.gotVersion: ++ ++ if len(self.buf) > 4096: ++ self.sendDisconnect( ++ DISCONNECT_CONNECTION_LOST, ++ b"Peer version string longer than 4KB. " ++ b"Preventing a deny of service attack.", ++ ) ++ return ++ + if self.buf.find(b'\n', self.buf.find(b'SSH-')) == -1: + return + +--- a/src/twisted/conch/test/test_transport.py ++++ b/src/twisted/conch/test/test_transport.py +@@ -548,6 +548,28 @@ class BaseSSHTransportTests(BaseSSHTrans + self.assertRegex(softwareVersion, softwareVersionRegex) + + ++ def test_dataReceiveVersionNotSentMemoryDOS(self): ++ """ ++ When the peer is not sending its SSH version but keeps sending data, ++ the connection is disconnected after 4KB to prevent buffering to ++ much and running our of memory. ++ """ ++ sut = MockTransportBase() ++ sut.makeConnection(self.transport) ++ ++ sut.dataReceived(b"SSH-bla-bla-bla") ++ ++ sut.dataReceived(b"more-bla-bla-bla" * 100) ++ ++ self.assertFalse(self.transport.disconnecting) ++ ++ sut.dataReceived(b"more-bla-bla-bla" * 1000) ++ ++ # Once a lot of data is received without an SSH version string, ++ # the transport is disconnected. ++ self.assertTrue(self.transport.disconnecting) ++ self.assertIn(b"Preventing a deny of service attack", self.transport.value()) ++ + def test_sendPacketPlain(self): + """ + Test that plain (unencrypted, uncompressed) packets are sent +--- /dev/null ++++ b/src/twisted/newsfragments/10284.bugfix +@@ -0,0 +1,2 @@ ++twisted.conch.ssh.transport.SSHTransportBase now disconnects the remote peer if the ++SSH version string is not sent in the first 4096 bytes. diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch --- twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,49 @@ +From 9b98116372f5d211ddb9f68916d4ae73bf3c8da7 Mon Sep 17 00:00:00 2001 +From: Adi Roiban <adi.roi...@chevah.com> +Date: Mon, 24 Jan 2022 23:53:54 +0000 +Subject: [PATCH] Update after review. + +--- + src/twisted/conch/ssh/transport.py | 2 +- + src/twisted/conch/test/test_transport.py | 14 +++++++------- + 2 files changed, 8 insertions(+), 8 deletions(-) + +--- a/src/twisted/conch/ssh/transport.py ++++ b/src/twisted/conch/ssh/transport.py +@@ -697,7 +697,7 @@ class SSHTransportBase(protocol.Protocol + self.sendDisconnect( + DISCONNECT_CONNECTION_LOST, + b"Peer version string longer than 4KB. " +- b"Preventing a deny of service attack.", ++ b"Preventing a denial of service attack.", + ) + return + +--- a/src/twisted/conch/test/test_transport.py ++++ b/src/twisted/conch/test/test_transport.py +@@ -557,18 +557,18 @@ class BaseSSHTransportTests(BaseSSHTrans + sut = MockTransportBase() + sut.makeConnection(self.transport) + +- sut.dataReceived(b"SSH-bla-bla-bla") +- +- sut.dataReceived(b"more-bla-bla-bla" * 100) +- ++ # Data can be received over multiple chunks. ++ sut.dataReceived(b"SSH-2-Server-Identifier") ++ sut.dataReceived(b"1234567890" * 406) ++ sut.dataReceived(b"1235678") + self.assertFalse(self.transport.disconnecting) + +- sut.dataReceived(b"more-bla-bla-bla" * 1000) +- ++ # Here we are going over the limit. ++ sut.dataReceived(b"1234567") + # Once a lot of data is received without an SSH version string, + # the transport is disconnected. + self.assertTrue(self.transport.disconnecting) +- self.assertIn(b"Preventing a deny of service attack", self.transport.value()) ++ self.assertIn(b"Preventing a denial of service attack", self.transport.value()) + + def test_sendPacketPlain(self): + """ diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch --- twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,20 @@ +From a4523b444760f07e609636264a61a2a07ca0bde5 Mon Sep 17 00:00:00 2001 +From: Adi Roiban <adi.roi...@chevah.com> +Date: Tue, 8 Feb 2022 14:01:10 +0000 +Subject: [PATCH] Fix typo. + +--- + src/twisted/conch/test/test_transport.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/twisted/conch/test/test_transport.py ++++ b/src/twisted/conch/test/test_transport.py +@@ -551,7 +551,7 @@ class BaseSSHTransportTests(BaseSSHTrans + def test_dataReceiveVersionNotSentMemoryDOS(self): + """ + When the peer is not sending its SSH version but keeps sending data, +- the connection is disconnected after 4KB to prevent buffering to ++ the connection is disconnected after 4KB to prevent buffering too + much and running our of memory. + """ + sut = MockTransportBase() diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,128 @@ +From: Tom Most <t...@freecog.net> +Date: Sat, 5 Mar 2022 23:26:55 -0800 +Subject: Some tests for GHSA-c2jg-hw38-jrqq + +--- + src/twisted/web/test/test_http.py | 102 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 102 insertions(+) + +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index ab1cac2..4acc0e7 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1693,6 +1693,56 @@ class ParsingTests(unittest.TestCase): + ) + + ++ def test_headerStripWhitespace(self): ++ """ ++ Leading and trailing space and tab characters are stripped from ++ headers. Other forms of whitespace are preserved. ++ ++ See RFC 7230 section 3.2.3 and 3.2.4. ++ """ ++ processed = [] ++ ++ class MyRequest(http.Request): ++ def process(self): ++ processed.append(self) ++ self.finish() ++ ++ requestLines = [ ++ b"GET / HTTP/1.0", ++ b"spaces: spaces were stripped ", ++ b"tabs: \t\ttabs were stripped\t\t", ++ b"spaces-and-tabs: \t \t spaces and tabs were stripped\t \t", ++ b"line-tab: \v vertical tab was preserved\v\t", ++ b"form-feed: \f form feed was preserved \f ", ++ b"", ++ b"", ++ ] ++ ++ self.runRequest(b"\n".join(requestLines), MyRequest, 0) ++ [request] = processed ++ # All leading and trailing whitespace is stripped from the ++ # header-value. ++ self.assertEqual( ++ request.requestHeaders.getRawHeaders(b"spaces"), ++ [b"spaces were stripped"], ++ ) ++ self.assertEqual( ++ request.requestHeaders.getRawHeaders(b"tabs"), ++ [b"tabs were stripped"], ++ ) ++ self.assertEqual( ++ request.requestHeaders.getRawHeaders(b"spaces-and-tabs"), ++ [b"spaces and tabs were stripped"], ++ ) ++ self.assertEqual( ++ request.requestHeaders.getRawHeaders(b"line-tab"), ++ [b"\v vertical tab was preserved\v"], ++ ) ++ self.assertEqual( ++ request.requestHeaders.getRawHeaders(b"form-feed"), ++ [b"\f form feed was preserved \f"], ++ ) ++ + def test_tooManyHeaders(self): + """ + C{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the +@@ -2238,6 +2288,58 @@ Hello, + ]) + + ++ def test_contentLengthMalformed(self): ++ """ ++ A request with a non-integer C{Content-Length} header fails with a 400 ++ response without calling L{Request.process}. ++ """ ++ self.assertRequestRejected( ++ [ ++ b"GET /a HTTP/1.1", ++ b"Content-Length: MORE THAN NINE THOUSAND!", ++ b"Host: host.invalid", ++ b"", ++ b"", ++ b"x" * 9001, ++ ] ++ ) ++ ++ def test_contentLengthTooPositive(self): ++ """ ++ A request with a C{Content-Length} header that begins with a L{+} fails ++ with a 400 response without calling L{Request.process}. ++ ++ This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq. ++ """ ++ self.assertRequestRejected( ++ [ ++ b"GET /a HTTP/1.1", ++ b"Content-Length: +100", ++ b"Host: host.invalid", ++ b"", ++ b"", ++ b"x" * 100, ++ ] ++ ) ++ ++ def test_contentLengthNegative(self): ++ """ ++ A request with a C{Content-Length} header that is negative fails with ++ a 400 response without calling L{Request.process}. ++ ++ This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq. ++ """ ++ self.assertRequestRejected( ++ [ ++ b"GET /a HTTP/1.1", ++ b"Content-Length: -100", ++ b"Host: host.invalid", ++ b"", ++ b"", ++ b"x" * 200, ++ ] ++ ) ++ + def test_duplicateContentLengthsWithPipelinedRequests(self): + """ + Two pipelined requests, the first of which includes multiple diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,59 @@ +From: Tom Most <t...@freecog.net> +Date: Mon, 7 Mar 2022 00:02:55 -0800 +Subject: Replace obs-fold with a single space + +--- + src/twisted/web/http.py | 2 +- + src/twisted/web/test/test_http.py | 13 +++++++++---- + 2 files changed, 10 insertions(+), 5 deletions(-) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index 78f4a36..b5c0b25 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2157,7 +2157,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): + self.setRawMode() + elif line[0] in b' \t': + # Continuation of a multi line header. +- self.__header = self.__header + b'\n' + line ++ self.__header += b" " + line.lstrip(b" \t") + # Regular header line. + # Processing of header line is delayed to allow accumulating multi + # line headers. +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index 4acc0e7..e4ea2ef 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1644,7 +1644,12 @@ class ParsingTests(unittest.TestCase): + Line folded headers are handled by L{HTTPChannel} by replacing each + fold with a single space by the time they are made available to the + L{Request}. Any leading whitespace in the folded lines of the header +- value is preserved. ++ value is replaced with a single space, per: ++ ++ A server that receives an obs-fold in a request message ... MUST ++ ... replace each received obs-fold with one or more SP octets prior ++ to interpreting the field value or forwarding the message ++ downstream. + + See RFC 7230 section 3.2.4. + """ +@@ -1681,15 +1686,15 @@ class ParsingTests(unittest.TestCase): + ) + self.assertEqual( + request.requestHeaders.getRawHeaders(b"space"), +- [b"space space"], ++ [b"space space"], + ) + self.assertEqual( + request.requestHeaders.getRawHeaders(b"spaces"), +- [b"spaces spaces spaces"], ++ [b"spaces spaces spaces"], + ) + self.assertEqual( + request.requestHeaders.getRawHeaders(b"tab"), +- [b"t \ta \tb"], ++ [b"t a b"], + ) + + diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,21 @@ +From: Tom Most <t...@freecog.net> +Date: Mon, 7 Mar 2022 00:03:50 -0800 +Subject: Strip only spaces and tabs from header values + +--- + src/twisted/web/http.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index b5c0b25..1ff77ac 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2240,7 +2240,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): + return False + + header = header.lower() +- data = data.strip() ++ data = data.strip(b" \t") + + if not self._maybeChooseTransferDecoder(header, data): + return False diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,21 @@ +From: Tom Most <t...@freecog.net> +Date: Mon, 7 Mar 2022 00:32:14 -0800 +Subject: Reject non-digit Content-Length + +--- + src/twisted/web/http.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index 1ff77ac..e62e17b 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2186,6 +2186,8 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): + + # Can this header determine the length? + if header == b'content-length': ++ if not data.isdigit(): ++ return fail() + try: + length = int(data) + except ValueError: diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,69 @@ +From: Tom Most <t...@freecog.net> +Date: Sun, 13 Mar 2022 23:19:39 -0700 +Subject: Test for malformed chunk size and extensions + +--- + src/twisted/web/test/test_http.py | 36 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index e4ea2ef..9079279 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1299,6 +1299,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase): + self.assertEqual(L, [b'abc']) + + ++ def test_extensionsMalformed(self): ++ """ ++ L{_ChunkedTransferDecoder.dataReceived} raises ++ L{_MalformedChunkedDataError} when the chunk extension fields contain ++ invalid characters. ++ ++ This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq. ++ """ ++ for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]: ++ data = b"3; " + bytes((b,)) + b"\r\nabc\r\n" ++ p = http._ChunkedTransferDecoder( ++ lambda b: None, # pragma: nocov ++ lambda b: None, # pragma: nocov ++ ) ++ self.assertRaises(http._MalformedChunkedDataError, p.dataReceived, data) ++ ++ + def test_finish(self): + """ + L{_ChunkedTransferDecoder.dataReceived} interprets a zero-length +@@ -1376,6 +1393,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase): + self.assertEqual(successes, [True]) + + ++ def test_malformedChunkSizeHex(self): ++ """ ++ L{_ChunkedTransferDecoder.dataReceived} raises ++ L{_MalformedChunkedDataError} when the chunk size is prefixed with ++ "0x", as if it were a Python integer literal. ++ ++ This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq. ++ """ ++ p = http._ChunkedTransferDecoder( ++ lambda b: None, # pragma: nocov ++ lambda b: None, # pragma: nocov ++ ) ++ self.assertRaises( ++ http._MalformedChunkedDataError, p.dataReceived, b"0x3\r\nabc\r\n" ++ ) ++ ++ + + class ChunkingTests(unittest.TestCase, ResponseTestMixin): + +@@ -1387,6 +1421,8 @@ class ChunkingTests(unittest.TestCase, ResponseTestMixin): + chunked = b''.join(http.toChunk(s)) + self.assertEqual((s, b''), http.fromChunk(chunked)) + self.assertRaises(ValueError, http.fromChunk, b'-5\r\nmalformed!\r\n') ++ self.assertRaises(ValueError, http.fromChunk, b"0xa\r\nmalformed!\r\n") ++ self.assertRaises(ValueError, http.fromChunk, b"0XA\r\nmalformed!\r\n") + + def testConcatenatedChunks(self): + chunked = b''.join([b''.join(http.toChunk(t)) for t in self.strings]) diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,114 @@ +From: Tom Most <t...@freecog.net> +Date: Sun, 13 Mar 2022 23:51:52 -0700 +Subject: Reject malformed chunk sizes + +--- + src/twisted/web/http.py | 32 ++++++++++++++++++++++++++++--- + src/twisted/web/test/test_http.py | 40 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 69 insertions(+), 3 deletions(-) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index e62e17b..f9d61ab 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -338,7 +338,33 @@ def toChunk(data): + + + +-def fromChunk(data): ++def _ishexdigits(b: bytes) -> bool: ++ """ ++ Is the string case-insensitively hexidecimal? ++ ++ It must be composed of one or more characters in the ranges a-f, A-F ++ and 0-9. ++ """ ++ for c in b: ++ if c not in b'0123456789abcdefABCDEF': ++ return False ++ return bool(b) ++ ++ ++def _hexint(b: bytes) -> int: ++ """ ++ Decode a hexadecimal integer. ++ ++ Unlike L{int(b, 16)}, this raises L{ValueError} when the integer has ++ a prefix like C{b'0x'}, C{b'+'}, or C{b'-'}, which is desirable when ++ parsing network protocols. ++ """ ++ if not _ishexdigits(b): ++ raise ValueError(b) ++ return int(b, 16) ++ ++ ++def fromChunk(data: bytes): + """ + Convert chunk to string. + +@@ -350,7 +376,7 @@ def fromChunk(data): + byte string. + """ + prefix, rest = data.split(b'\r\n', 1) +- length = int(prefix, 16) ++ length = _hexint(prefix) + if length < 0: + raise ValueError("Chunk length must be >= 0, not %d" % (length,)) + if rest[length:length + 2] != b'\r\n': +@@ -1827,7 +1853,7 @@ class _ChunkedTransferDecoder(object): + line, rest = data.split(b'\r\n', 1) + parts = line.split(b';') + try: +- self.length = int(parts[0], 16) ++ self.length = _hexint(parts[0]) + except ValueError: + raise _MalformedChunkedDataError( + "Chunk-size must be an integer.") +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index 9079279..ba400c4 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -4341,3 +4341,43 @@ class HTTPClientSanitizationTests(unittest.SynchronousTestCase): + transport.value().splitlines(), + [b": ".join([sanitizedBytes, sanitizedBytes])] + ) ++ ++ ++class HexHelperTests(unittest.SynchronousTestCase): ++ """ ++ Test the L{http._hexint} and L{http._ishexdigits} helper functions. ++ """ ++ ++ badStrings = (b"", b"0x1234", b"feds", b"-123" b"+123") ++ ++ def test_isHex(self): ++ """ ++ L{_ishexdigits()} returns L{True} for nonempy bytestrings containing ++ hexadecimal digits. ++ """ ++ for s in (b"10", b"abcdef", b"AB1234", b"fed", b"123467890"): ++ self.assertIs(True, http._ishexdigits(s)) ++ ++ def test_decodes(self): ++ """ ++ L{_hexint()} returns the integer equivalent of the input. ++ """ ++ self.assertEqual(10, http._hexint(b"a")) ++ self.assertEqual(0x10, http._hexint(b"10")) ++ self.assertEqual(0xABCD123, http._hexint(b"abCD123")) ++ ++ def test_isNotHex(self): ++ """ ++ L{_ishexdigits()} returns L{False} for bytestrings that don't contain ++ hexadecimal digits, including the empty string. ++ """ ++ for s in self.badStrings: ++ self.assertIs(False, http._ishexdigits(s)) ++ ++ def test_decodeNotHex(self): ++ """ ++ L{_hexint()} raises L{ValueError} for bytestrings that can't ++ be decoded. ++ """ ++ for s in self.badStrings: ++ self.assertRaises(ValueError, http._hexint, s) diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,22 @@ +From: Tom Most <t...@freecog.net> +Date: Sun, 13 Mar 2022 23:55:26 -0700 +Subject: We should deprecate http.fromChunk + +--- + src/twisted/web/http.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index f9d61ab..a7fcbc1 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -368,6 +368,9 @@ def fromChunk(data: bytes): + """ + Convert chunk to string. + ++ Note that this function is not specification compliant: it doesn't handle ++ chunk extensions. ++ + @type data: C{bytes} + + @return: tuple of (result, remaining) - both C{bytes}. diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,100 @@ +From: Tom Most <t...@freecog.net> +Date: Sun, 27 Mar 2022 22:17:30 -0700 +Subject: Correct chunk extension byte validation + +Go back to the RFC to figure out the correct allowed ranges. +--- + src/twisted/web/http.py | 46 ++++++++++++++++++++++++++++++++++++++- + src/twisted/web/test/test_http.py | 8 ++++++- + 2 files changed, 52 insertions(+), 2 deletions(-) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index a7fcbc1..baf6b87 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -346,7 +346,7 @@ def _ishexdigits(b: bytes) -> bool: + and 0-9. + """ + for c in b: +- if c not in b'0123456789abcdefABCDEF': ++ if c not in b"0123456789abcdefABCDEF": + return False + return bool(b) + +@@ -1804,6 +1804,46 @@ class _IdentityTransferDecoder(object): + raise _DataLoss() + + ++_chunkExtChars = ( ++ b"\t !\"#$%&'()*+,-./0123456789:;<=>?@" ++ b"ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`" ++ b"abcdefghijklmnopqrstuvwxyz{|}~" ++ b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f" ++ b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" ++ b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" ++ b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" ++ b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf" ++ b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" ++ b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef" ++ b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" ++) ++""" ++Characters that are valid in a chunk extension. ++ ++See RFC 7230 section 4.1.1: ++ ++ chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) ++ ++ chunk-ext-name = token ++ chunk-ext-val = token / quoted-string ++ ++Section 3.2.6: ++ ++ token = 1*tchar ++ ++ tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" ++ / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" ++ / DIGIT / ALPHA ++ ; any VCHAR, except delimiters ++ ++ quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE ++ qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text ++ obs-text = %x80-FF ++ ++We don't check if chunk extensions are well-formed beyond validating that they ++don't contain characters outside this range. ++""" ++ + + class _ChunkedTransferDecoder(object): + """ +@@ -1860,6 +1900,10 @@ class _ChunkedTransferDecoder(object): + except ValueError: + raise _MalformedChunkedDataError( + "Chunk-size must be an integer.") ++ if len(parts) > 1 and parts[1].translate(None, _chunkExtChars) != b"": ++ raise _MalformedChunkedDataError( ++ f"Invalid characters in chunk extensions: {parts[1]!r}." ++ ) + if self.length == 0: + self.state = 'TRAILER' + else: +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index ba400c4..5e31cee 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1307,7 +1307,13 @@ class ChunkedTransferEncodingTests(unittest.TestCase): + + This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq. + """ +- for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]: ++ invalidControl = ( ++ b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\n\x0b\x0c\r\x0e\x0f" ++ b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" ++ ) ++ invalidDelimiter = b"\\" ++ invalidDel = b"\x7f" ++ for b in invalidControl + invalidDelimiter + invalidDel: + data = b"3; " + bytes((b,)) + b"\r\nabc\r\n" + p = http._ChunkedTransferDecoder( + lambda b: None, # pragma: nocov diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch --- twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,38 @@ +From: Tom Most <t...@freecog.net> +Date: Fri, 1 Apr 2022 20:47:59 -0700 +Subject: Address review feedback + +--- + src/twisted/web/http.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index baf6b87..f5088ab 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -348,7 +348,7 @@ def _ishexdigits(b: bytes) -> bool: + for c in b: + if c not in b"0123456789abcdefABCDEF": + return False +- return bool(b) ++ return b != b"" + + + def _hexint(b: bytes) -> int: +@@ -1820,14 +1820,14 @@ _chunkExtChars = ( + """ + Characters that are valid in a chunk extension. + +-See RFC 7230 section 4.1.1: ++See RFC 7230 section 4.1.1:: + + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + + chunk-ext-name = token + chunk-ext-val = token / quoted-string + +-Section 3.2.6: ++And section 3.2.6:: + + token = 1*tchar + diff -Nru twisted-20.3.0/debian/patches/series twisted-20.3.0/debian/patches/series --- twisted-20.3.0/debian/patches/series 2021-04-24 12:36:24.000000000 -0400 +++ twisted-20.3.0/debian/patches/series 2022-05-05 09:59:26.000000000 -0400 @@ -22,3 +22,26 @@ 0023-Merge-9801-rodrigc-cgi-Change-import-of-cgi.parse_qs.patch 0024-fixed-corrupted-iqmp-value-in-test-RSA-key.patch 0025-Skip-failing-twisted.web.test.test_http.QueryArgumen.patch +CVE-2022-21712-1.patch +CVE-2022-21712-2.patch +CVE-2022-21712-3.patch +CVE-2022-21712-4.patch +CVE-2022-21712-5.patch +CVE-2022-21712-6.patch +CVE-2022-21712-7.patch +CVE-2022-21712-8.patch +CVE-2022-21712-9.patch +CVE-2022-21712-10.patch +CVE-2022-21716-1.patch +CVE-2022-21716-2.patch +CVE-2022-21716-3.patch +CVE-2022-24801-1.patch +CVE-2022-24801-2.patch +CVE-2022-24801-3.patch +CVE-2022-24801-4.patch +CVE-2022-24801-5.patch +CVE-2022-24801-6.patch +CVE-2022-24801-7.patch +CVE-2022-24801-8.patch +CVE-2022-24801-9.patch +Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch diff -Nru twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch --- twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch 1969-12-31 20:00:00.000000000 -0400 +++ twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch 2022-05-05 09:59:26.000000000 -0400 @@ -0,0 +1,37 @@ +From: Glyph <gl...@twistedmatrix.com> +Date: Sun, 27 Mar 2022 18:03:49 -0700 +Subject: remove spurious test for illegal whitespace in xmlns + +Origin: upstream, https://github.com/twisted/twisted/commit/6b4bbf9040abd8e5c8feae026c4a6483c7f72506 +--- + src/twisted/newsfragments/10318.misc | 0 + src/twisted/words/test/test_domish.py | 12 ------------ + 2 files changed, 12 deletions(-) + create mode 100644 src/twisted/newsfragments/10318.misc + +diff --git a/src/twisted/newsfragments/10318.misc b/src/twisted/newsfragments/10318.misc +new file mode 100644 +index 0000000..e69de29 +diff --git a/src/twisted/words/test/test_domish.py b/src/twisted/words/test/test_domish.py +index cd16e3a..4e37171 100644 +--- a/src/twisted/words/test/test_domish.py ++++ b/src/twisted/words/test/test_domish.py +@@ -338,18 +338,6 @@ class DomishStreamTestsMixin: + self.assertEqual(self.elements[0].child2.uri, '') + + +- def test_namespaceWithWhitespace(self): +- """ +- Whitespace in an xmlns value is preserved in the resulting node's C{uri} +- attribute. +- """ +- xml = b"<root xmlns:foo=' bar baz '><foo:bar foo:baz='quux'/></root>" +- self.stream.parse(xml) +- self.assertEqual(self.elements[0].uri, " bar baz ") +- self.assertEqual( +- self.elements[0].attributes, {(" bar baz ", "baz"): "quux"}) +- +- + def test_attributesWithNamespaces(self): + """ + Attributes with namespace are parsed without Exception.