From: Bert JW Regeer <bertjw@regeer.org>
Date: Sat, 12 Mar 2022 18:32:24 -0700
Subject: Be more strict in parsing Content-Length

Validate that we are only parsing digits and nothing else. RFC7230 is
explicit in that the Content-Length can only exist of 1*DIGIT and may
not include any additional sign information.

The Python int() function parses `+10` as `10` which means we were more
lenient than the standard intended.

Part of CVE-2022-24761
---
 src/waitress/parser.py | 12 ++++++------
 tests/test_parser.py   | 24 ++++++++++++++++++++++++
 2 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/src/waitress/parser.py b/src/waitress/parser.py
index 765fe59..acaf494 100644
--- a/src/waitress/parser.py
+++ b/src/waitress/parser.py
@@ -22,6 +22,7 @@ from io import BytesIO
 from waitress.buffers import OverflowableBuffer
 from waitress.compat import tostr, unquote_bytes_to_wsgi, urlparse
 from waitress.receiver import ChunkedReceiver, FixedStreamReceiver
+from waitress.rfc7230 import HEADER_FIELD_RE, ONLY_DIGIT_RE
 from waitress.utilities import (
     BadRequest,
     RequestEntityTooLarge,
@@ -29,8 +30,6 @@ from waitress.utilities import (
     ServerNotImplemented,
     find_double_newline,
 )
-from .rfc7230 import HEADER_FIELD
-
 
 class ParsingError(Exception):
     pass
@@ -209,7 +208,7 @@ class HTTPRequestParser(object):
 
         headers = self.headers
         for line in lines:
-            header = HEADER_FIELD.match(line)
+            header = HEADER_FIELD_RE.match(line)
 
             if not header:
                 raise ParsingError("Invalid header")
@@ -299,11 +298,12 @@ class HTTPRequestParser(object):
                 self.connection_close = True
 
         if not self.chunked:
-            try:
-                cl = int(headers.get("CONTENT_LENGTH", 0))
-            except ValueError:
+            cl = headers.get("CONTENT_LENGTH", "0")
+
+            if not ONLY_DIGIT_RE.match(cl.encode("latin-1")):
                 raise ParsingError("Content-Length is invalid")
 
+            cl = int(cl)
             self.content_length = cl
             if cl > 0:
                 buf = OverflowableBuffer(self.adj.inbuf_overflow)
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 91837c7..eabf353 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -194,6 +194,30 @@ class TestHTTPRequestParser(unittest.TestCase):
         else:  # pragma: nocover
             self.assertTrue(False)
 
+    def test_parse_header_bad_content_length_plus(self):
+        from waitress.parser import ParsingError
+
+        data = b"GET /foobar HTTP/8.4\r\ncontent-length: +10\r\n"
+
+        try:
+            self.parser.parse_header(data)
+        except ParsingError as e:
+            self.assertIn("Content-Length is invalid", e.args[0])
+        else:  # pragma: nocover
+            self.assertTrue(False)
+
+    def test_parse_header_bad_content_length_minus(self):
+        from waitress.parser import ParsingError
+
+        data = b"GET /foobar HTTP/8.4\r\ncontent-length: -10\r\n"
+
+        try:
+            self.parser.parse_header(data)
+        except ParsingError as e:
+            self.assertIn("Content-Length is invalid", e.args[0])
+        else:  # pragma: nocover
+            self.assertTrue(False)
+
     def test_parse_header_multiple_content_length(self):
         from waitress.parser import ParsingError
 
