From: Serhiy Storchaka Date: Tue, 7 Jul 2026 18:38:25 +0000 (+0300) Subject: [3.13] gh-143921: Reject NUL, CR and LF in IMAP commands (GH-143922, GH-153067) ... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=71926d943c05bde79bd2a866933103541d91b6a2;p=thirdparty%2FPython%2Fcpython.git [3.13] gh-143921: Reject NUL, CR and LF in IMAP commands (GH-143922, GH-153067) (GH-153137) (GH-153287) Combined backport of GH-143922, which rejected all control characters, and GH-153067, which narrowed the check to NUL, CR and LF. Other control characters are valid in quoted strings and are sent quoted. (cherry picked from commit 6262704b134db2a4ba12e85ecfbd968534f28b45) (cherry picked from commit d0921efb665aff26b378f495e5ff84f7e3fe649d) (cherry picked from commit 298182272a740ce2016aee2f54acbd0bba1944c1) Co-authored-by: Seth Michael Larson Co-authored-by: Claude Fable 5 --- diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 503e3c9cb8b7..0223a4ad9e2b 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -132,6 +132,9 @@ Untagged_status = re.compile( # We compile these in _mode_xxx. _Literal = br'.*{(?P\d+)}$' _Untagged_status = br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?' +# Only NUL, CR and LF are unsafe (they cannot be represented even in +# a quoted string); other control characters are sent quoted. +_control_chars = re.compile(b'[\x00\r\n]') _non_astring_char = re.compile(br'[(){ \x00-\x1f\x7f-\xff%*\\"]') _non_list_char = re.compile(br'[(){ \x00-\x1f\x7f-\xff\\"]') _quoted = re.compile(br'"(?:[^"\\]|\\.)*+"') @@ -1047,6 +1050,8 @@ class IMAP4: if arg is None: continue if isinstance(arg, str): arg = bytes(arg, self._encoding) + if _control_chars.search(arg): + raise ValueError("NUL, CR and LF not allowed in commands") data = data + b' ' + arg literal = self.literal diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 9c1dcb1e299b..49a8c1a1b817 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1626,6 +1626,22 @@ class NewIMAPTestsMixin: with self.assertRaises(AttributeError): client.NONEXISTENT + def test_control_characters(self): + client, server = self._setup(SimpleIMAPHandler) + client.login('user', 'pass') + for c in '\0\r\n': + with self.assertRaises(ValueError): + client.select(f'a{c}b') + # Other control characters are valid in a quoted string and can + # occur in mailbox names returned by the server, so the client + # must be able to send them back. + for c in support.control_characters_c0(): + if c in '\0\r\n': + continue + typ, _ = client.select(f'a{c}b') + self.assertEqual(typ, 'OK') + self.assertEqual(server.is_selected, [f'"a{c}b"']) + class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase): imap_class = imaplib.IMAP4 diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst new file mode 100644 index 000000000000..de62e020a95e --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst @@ -0,0 +1,2 @@ +Reject NUL, CR and LF characters in IMAP commands. Other control +characters are allowed and sent quoted.