From 298182272a740ce2016aee2f54acbd0bba1944c1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 7 Jul 2026 20:13:02 +0300 Subject: [PATCH] [3.14] 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) 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 4f5e4fc5b9e5..ac443ac915ce 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -131,6 +131,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'"(?:[^"\\]|\\.)*+"') @@ -1155,6 +1158,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 4e44666800e7..8a71b6f89393 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1741,6 +1741,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"']) + # property tests def test_file_property_should_not_be_accessed(self): 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. -- 2.47.3