diff -Nru twisted-22.1.0/debian/changelog twisted-22.1.0/debian/changelog --- twisted-22.1.0/debian/changelog 2022-05-04 01:36:26.000000000 +0000 +++ twisted-22.1.0/debian/changelog 2022-08-11 02:24:30.000000000 +0000 @@ -1,3 +1,17 @@ +twisted (22.1.0-2ubuntu2.3) jammy-security; urgency=medium + + * SECURITY UPDATE: Parsing of HTTP request headers was found to be + not fully compliant with RFC 7230 specifications, which could + result in HTTP request smuggling for certain multi-server + configurations + - debian/patches/CVE-2022-24801-*.patch: Ensure only permitted characters + are present in Content-Length headers, improve parsing of Chunk Length + values and fix stripping of whitespace in HTTP headers in + src/twisted/web/http.py and src/twisted/web/test/test_http.py + - CVE-2022-24801 + + -- Ray Veldkamp Thu, 11 Aug 2022 12:24:30 +1000 + twisted (22.1.0-2ubuntu2.1) jammy-security; urgency=medium * SECURITY UPDATE: Parsing of SSH version identifier field during an SSH diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-001.patch twisted-22.1.0/debian/patches/CVE-2022-24801-001.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-001.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-001.patch 2022-05-09 09:57:02.000000000 +0000 @@ -0,0 +1,127 @@ +From 22b067793cbcd0fb5dee04cfd9115fa85a7ca110 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sat, 5 Mar 2022 23:26:55 -0800 +Subject: [PATCH] Some tests for GHSA-c2jg-hw38-jrqq + +--- + src/twisted/web/test/test_http.py | 102 ++++++++++++++++++++++++++++++ + 1 file changed, 102 insertions(+) + +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1751,6 +1751,56 @@ class ParsingTests(unittest.TestCase): + [b"t \ta \tb"], + ) + ++ 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 +@@ -2315,6 +2365,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-22.1.0/debian/patches/CVE-2022-24801-002.patch twisted-22.1.0/debian/patches/CVE-2022-24801-002.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-002.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-002.patch 2022-05-09 09:57:43.000000000 +0000 @@ -0,0 +1,56 @@ +From 79ee8c564ca0d4c2910c8859e0a6014d2dc40005 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Mon, 7 Mar 2022 00:02:55 -0800 +Subject: [PATCH] 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(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2245,7 +2245,7 @@ class HTTPChannel(basic.LineReceiver, po + 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. +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1703,7 +1703,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. + """ +@@ -1740,15 +1745,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"], + ) + + def test_headerStripWhitespace(self): diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-003.patch twisted-22.1.0/debian/patches/CVE-2022-24801-003.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-003.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-003.patch 2022-05-09 09:58:01.000000000 +0000 @@ -0,0 +1,20 @@ +From c3a4e1d015740c1d87a3ec7d57570257e75b0062 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Mon, 7 Mar 2022 00:03:50 -0800 +Subject: [PATCH] Strip only spaces and tabs from header values + +--- + src/twisted/web/http.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2326,7 +2326,7 @@ class HTTPChannel(basic.LineReceiver, po + return False + + header = header.lower() +- data = data.strip() ++ data = data.strip(b" \t") + + if not self._maybeChooseTransferDecoder(header, data): + return False diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-004.patch twisted-22.1.0/debian/patches/CVE-2022-24801-004.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-004.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-004.patch 2022-05-09 09:58:14.000000000 +0000 @@ -0,0 +1,20 @@ +From 8ebfa8f6577431226e109ff98ba48f5152a2c416 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Mon, 7 Mar 2022 00:32:14 -0800 +Subject: [PATCH] Reject non-digit Content-Length + +--- + src/twisted/web/http.py | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -2273,6 +2273,8 @@ class HTTPChannel(basic.LineReceiver, po + + # 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-22.1.0/debian/patches/CVE-2022-24801-005.patch twisted-22.1.0/debian/patches/CVE-2022-24801-005.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-005.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-005.patch 2022-05-09 09:58:26.000000000 +0000 @@ -0,0 +1,66 @@ +From f22d0d9c889822adb7eaf84b42a20ff5f7c4d421 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sun, 13 Mar 2022 23:19:39 -0700 +Subject: [PATCH] Test for malformed chunk size and extensions + +--- + src/twisted/web/test/test_http.py | 34 +++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1279,6 +1279,22 @@ class ChunkedTransferEncodingTests(unitt + p.dataReceived(b"3; x-foo=bar\r\nabc\r\n") + 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_oversizedChunkSizeLine(self): + """ + L{_ChunkedTransferDecoder.dataReceived} raises +@@ -1334,6 +1350,22 @@ class ChunkedTransferEncodingTests(unitt + http._MalformedChunkedDataError, p.dataReceived, b"-3\r\nabc\r\n" + ) + ++ 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" ++ ) ++ + def test_malformedChunkEnd(self): + r""" + L{_ChunkedTransferDecoder.dataReceived} raises +@@ -1446,6 +1478,8 @@ class ChunkingTests(unittest.TestCase, R + 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-22.1.0/debian/patches/CVE-2022-24801-006.patch twisted-22.1.0/debian/patches/CVE-2022-24801-006.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-006.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-006.patch 2022-05-26 06:39:31.000000000 +0000 @@ -0,0 +1,124 @@ +Backport of the following upsteam patch: + +From 0275152f147506c82868ff1dabd9bf655ab67946 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sun, 13 Mar 2022 23:51:52 -0700 +Subject: [PATCH] Reject malformed chunk sizes + +--- + src/twisted/web/http.py | 35 +++++++++++++++++++++++---- + src/twisted/web/test/test_http.py | 40 +++++++++++++++++++++++++++++++ + 2 files changed, 71 insertions(+), 4 deletions(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -108,7 +108,7 @@ import tempfile + import time + import warnings + from io import BytesIO +-from typing import Callable ++from typing import AnyStr, Callable, Optional, Tuple + from urllib.parse import ( + ParseResultBytes, + unquote_to_bytes as unquote, +@@ -410,7 +410,33 @@ def toChunk(data): + return (networkString(f"{len(data):x}"), b"\r\n", data, b"\r\n") + + +-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) -> Tuple[bytes, bytes]: + """ + Convert chunk to string. + +@@ -422,7 +448,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": +@@ -1882,8 +1908,9 @@ class _ChunkedTransferDecoder: + endOfLengthIndex = self._buffer.find(b";", 0, eolIndex) + if endOfLengthIndex == -1: + endOfLengthIndex = eolIndex ++ rawLength = self._buffer[0:endOfLengthIndex] + try: +- length = int(self._buffer[0:endOfLengthIndex], 16) ++ length = _hexint(rawLength) + except ValueError: + raise _MalformedChunkedDataError("Chunk-size must be an integer.") + +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -4380,3 +4380,43 @@ class HTTPClientSanitizationTests(unitte + 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-22.1.0/debian/patches/CVE-2022-24801-007.patch twisted-22.1.0/debian/patches/CVE-2022-24801-007.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-007.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-007.patch 2022-05-26 06:40:00.000000000 +0000 @@ -0,0 +1,21 @@ +From 2a5763d5b168372abb591c0eb6323ed4dfe8a4fc Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sun, 13 Mar 2022 23:55:26 -0700 +Subject: [PATCH] We should deprecate http.fromChunk + +--- + src/twisted/web/http.py | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -440,6 +440,9 @@ def fromChunk(data: bytes) -> Tuple[byte + """ + 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-22.1.0/debian/patches/CVE-2022-24801-008.patch twisted-22.1.0/debian/patches/CVE-2022-24801-008.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-008.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-008.patch 2022-05-26 06:40:55.000000000 +0000 @@ -0,0 +1,22 @@ +From 696bfeaf5a1fa7ff952f860c89e2bdcfacef7d7a Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sun, 13 Mar 2022 23:57:23 -0700 +Subject: [PATCH] Remove unreachable branch + +--- + src/twisted/web/http.py | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -1917,9 +1917,7 @@ class _ChunkedTransferDecoder: + except ValueError: + raise _MalformedChunkedDataError("Chunk-size must be an integer.") + +- if length < 0: +- raise _MalformedChunkedDataError("Chunk-size must not be negative.") +- elif length == 0: ++ if length == 0: + self.state = "TRAILER" + else: + self.state = "BODY" diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-009.patch twisted-22.1.0/debian/patches/CVE-2022-24801-009.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-009.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-009.patch 2022-05-27 06:05:46.000000000 +0000 @@ -0,0 +1,100 @@ +From fa9caa54d63399b4ccdfbf0429ba1b504ccc7c89 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Sun, 27 Mar 2022 22:17:30 -0700 +Subject: [PATCH] Correct chunk extension byte validation + +Go back to the RFC to figure out the correct allowed ranges. +--- + src/twisted/web/http.py | 49 ++++++++++++++++++++++++++++++- + src/twisted/web/test/test_http.py | 8 ++++- + 2 files changed, 55 insertions(+), 2 deletions(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -418,7 +418,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) + +@@ -1818,6 +1818,47 @@ class _IdentityTransferDecoder: + maxChunkSizeLineLength = 1024 + + ++_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: + """ + Protocol for decoding I{chunked} Transfer-Encoding, as defined by RFC 7230, +@@ -1917,6 +1958,12 @@ class _ChunkedTransferDecoder: + except ValueError: + raise _MalformedChunkedDataError("Chunk-size must be an integer.") + ++ ext = self._buffer[endOfLengthIndex + 1 : eolIndex] ++ if ext and ext.translate(None, _chunkExtChars) != b"": ++ raise _MalformedChunkedDataError( ++ f"Invalid characters in chunk extensions: {ext!r}." ++ ) ++ + if length == 0: + self.state = "TRAILER" + else: +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -1287,7 +1287,13 @@ class ChunkedTransferEncodingTests(unitt + + 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-22.1.0/debian/patches/CVE-2022-24801-010.patch twisted-22.1.0/debian/patches/CVE-2022-24801-010.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-010.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-010.patch 2022-05-27 06:06:11.000000000 +0000 @@ -0,0 +1,14 @@ +From c6d6a3ea2a819a99f17b54b08b7fd2c75587a8a7 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Wed, 30 Mar 2022 23:16:20 -0700 +Subject: [PATCH] Add newsfragment + +--- + src/twisted/web/newsfragments/10323.bugfix | 1 + + 1 file changed, 1 insertion(+) + create mode 100644 src/twisted/web/newsfragments/10323.bugfix + +--- /dev/null ++++ b/src/twisted/web/newsfragments/10323.bugfix +@@ -0,0 +1 @@ ++Correct several defects in HTTP request parsing that could permit HTTP request smugling: 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. GHSA-c2jg-hw38-jrqq diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-011.patch twisted-22.1.0/debian/patches/CVE-2022-24801-011.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-011.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-011.patch 2022-05-27 06:06:28.000000000 +0000 @@ -0,0 +1,14 @@ +From fbb001e9a66bdc97507e930388a7999fca323706 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Thu, 31 Mar 2022 21:47:23 -0700 +Subject: [PATCH] Add CVE to the newsfragment + +--- + src/twisted/web/newsfragments/10323.bugfix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/twisted/web/newsfragments/10323.bugfix ++++ b/src/twisted/web/newsfragments/10323.bugfix +@@ -1 +1 @@ +-Correct several defects in HTTP request parsing that could permit HTTP request smugling: 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. GHSA-c2jg-hw38-jrqq ++Correct several defects in HTTP request parsing that could permit HTTP request smugling: 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. Addresses CVE-2022-24801 and GHSA-c2jg-hw38-jrqq. diff -Nru twisted-22.1.0/debian/patches/CVE-2022-24801-012.patch twisted-22.1.0/debian/patches/CVE-2022-24801-012.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-012.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-012.patch 2022-05-27 06:08:14.000000000 +0000 @@ -0,0 +1,37 @@ +From 2bbd6c89110f0d44d2bb109c14d787f65bca9df8 Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Fri, 1 Apr 2022 20:47:59 -0700 +Subject: [PATCH] Address review feedback + +--- + src/twisted/web/http.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -420,7 +420,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: +@@ -1834,14 +1834,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-22.1.0/debian/patches/CVE-2022-24801-013.patch twisted-22.1.0/debian/patches/CVE-2022-24801-013.patch --- twisted-22.1.0/debian/patches/CVE-2022-24801-013.patch 1970-01-01 00:00:00.000000000 +0000 +++ twisted-22.1.0/debian/patches/CVE-2022-24801-013.patch 2022-05-27 06:08:53.000000000 +0000 @@ -0,0 +1,14 @@ +From a5138047c6bccb23eae2c1ecf4fa2fe17890872c Mon Sep 17 00:00:00 2001 +From: Tom Most +Date: Fri, 1 Apr 2022 21:38:48 -0700 +Subject: [PATCH] Revise newsfragment + +--- + src/twisted/web/newsfragments/10323.bugfix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/twisted/web/newsfragments/10323.bugfix ++++ b/src/twisted/web/newsfragments/10323.bugfix +@@ -1 +1 @@ +-Correct several defects in HTTP request parsing that could permit HTTP request smugling: 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. Addresses CVE-2022-24801 and GHSA-c2jg-hw38-jrqq. ++twisted.web.http had several several defects in HTTP request parsing that could permit HTTP request smuggling. It now disallows signed Content-Length headers, forbids illegal characters in chunked extensions, forbids 0x prefix to chunk lengths, and only strips spaces and horizontal tab characters from header values. These changes address CVE-2022-24801 and GHSA-c2jg-hw38-jrqq. diff -Nru twisted-22.1.0/debian/patches/series twisted-22.1.0/debian/patches/series --- twisted-22.1.0/debian/patches/series 2022-05-04 01:35:21.000000000 +0000 +++ twisted-22.1.0/debian/patches/series 2022-05-27 06:08:30.000000000 +0000 @@ -19,3 +19,16 @@ CVE-2022-21716-1.patch CVE-2022-21716-2.patch CVE-2022-21716-3.patch +CVE-2022-24801-001.patch +CVE-2022-24801-002.patch +CVE-2022-24801-003.patch +CVE-2022-24801-004.patch +CVE-2022-24801-005.patch +CVE-2022-24801-006.patch +CVE-2022-24801-007.patch +CVE-2022-24801-008.patch +CVE-2022-24801-009.patch +CVE-2022-24801-010.patch +CVE-2022-24801-011.patch +CVE-2022-24801-012.patch +CVE-2022-24801-013.patch