]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-143921: Reject NUL, CR and LF in IMAP commands (GH-143922, GH-153067) ...
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 7 Jul 2026 17:13:02 +0000 (20:13 +0300)
committerGitHub <noreply@github.com>
Tue, 7 Jul 2026 17:13:02 +0000 (20:13 +0300)
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 <seth@python.org>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Lib/imaplib.py
Lib/test/test_imaplib.py
Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst [new file with mode: 0644]

index 4f5e4fc5b9e569b545aa1771f80113ee395b2733..ac443ac915ce4fc3f2c3bdaf5b520222bf1afc5c 100644 (file)
@@ -131,6 +131,9 @@ Untagged_status = re.compile(
 # We compile these in _mode_xxx.
 _Literal = br'.*{(?P<size>\d+)}$'
 _Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
+# 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
index 4e44666800e7935966c5ec9b8b691564d7271bd1..8a71b6f89393865ca8496f08908340d1a7d257be 100644 (file)
@@ -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 (file)
index 0000000..de62e02
--- /dev/null
@@ -0,0 +1,2 @@
+Reject NUL, CR and LF characters in IMAP commands. Other control
+characters are allowed and sent quoted.