From f7884490d21232ab574f88b972fa056ec8336cf0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 7 Jul 2026 20:13:02 +0300 Subject: [PATCH] [3.13] gh-143921: Reject NUL, CR and LF in IMAP commands (GH-143922, GH-153067) (GH-153137) 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 --- Lib/imaplib.py | 5 +++++ Lib/test/test_imaplib.py | 16 ++++++++++++++++ ...026-01-16-11-41-06.gh-issue-143921.AeCOor.rst | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 503e3c9cb8b7770..0223a4ad9e2b027 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -132,6 +132,9 @@ # 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 @@ def _command(self, name, *args): 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 9c1dcb1e299bf73..49a8c1a1b817c82 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1626,6 +1626,22 @@ def test_uppercase_command_names(self): 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 000000000000000..de62e020a95e6b2 --- /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.