[3.13] gh-153603: Fix out-of-bounds read in the ISO-2022 decoder for an unknown charset (GH-153604) (GH-155961)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/f0e236e50e1b7a7752fbb80441ceec7034fd8718
commit: f0e236e50e1b7a7752fbb80441ceec7034fd8718
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-17T13:27:28Z
summary:

[3.13] gh-153603: Fix out-of-bounds read in the ISO-2022 decoder for an unknown charset (GH-153604) (GH-155961)

The designation-table scan compiled its terminator only under Py_DEBUG, so a
release build walked off the table for an unknown charset set via setstate().
Make the terminator unconditional and report the byte as undecodable.
(cherry picked from commit f40043e0953323675843a3c275511596f30c80e9)

Co-authored-by: tonghuaroot (童话) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-11-13-24-49.gh-issue-153603.YbKlry.rst
M Lib/test/test_multibytecodec.py
M Modules/cjkcodecs/_codecs_iso2022.c

diff --git a/Lib/test/test_multibytecodec.py b/Lib/test/test_multibytecodec.py
index 1b55f1e70b32f5f..01cc654626be180 100644
--- a/Lib/test/test_multibytecodec.py
+++ b/Lib/test/test_multibytecodec.py
@@ -306,6 +306,24 @@ def test_setstate_validates_input(self):
         self.assertRaises(TypeError, decoder.setstate, (b"1234", "invalid"))
         self.assertRaises(UnicodeDecodeError, decoder.setstate, (b"123456789", 0))
 
+    def test_setstate_invalid_designation(self):
+        # gh-153603: an unknown charset designation in the state must not crash
+        # the decoder.  0xff is not a registered charset mark and 0x21 ('!') is
+        # a GL byte that triggers the designation lookup.
+        for name in ('iso-2022-jp', 'iso-2022-kr'):
+            with self.subTest(codec=name):
+                decoder = codecs.getincrementaldecoder(name)()
+                decoder.setstate((b'', 0xff))
+                with self.assertRaises(UnicodeDecodeError) as cm:
+                    decoder.decode(b'!', final=True)
+                self.assertEqual(cm.exception.reason,
+                                 'illegal multibyte sequence')
+                self.assertEqual((cm.exception.start, cm.exception.end), (0, 1))
+                # One illegal byte is reported, so error handlers still work.
+                decoder = codecs.getincrementaldecoder(name)(errors='replace')
+                decoder.setstate((b'', 0xff))
+                self.assertEqual(decoder.decode(b'!', final=True), '\ufffd')
+
 class Test_StreamReader(unittest.TestCase):
     def test_bug1728403(self):
         try:
diff --git a/Misc/NEWS.d/next/Library/2026-07-11-13-24-49.gh-issue-153603.YbKlry.rst b/Misc/NEWS.d/next/Library/2026-07-11-13-24-49.gh-issue-153603.YbKlry.rst
new file mode 100644
index 000000000000000..5c60a302883931c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-11-13-24-49.gh-issue-153603.YbKlry.rst
@@ -0,0 +1,3 @@
+Fix a crash in the ISO-2022 decoders when decoding a byte after an unknown
+charset designation is set via the decoder's ``setstate`` method.
+Patch by tonghuaroot.
diff --git a/Modules/cjkcodecs/_codecs_iso2022.c b/Modules/cjkcodecs/_codecs_iso2022.c
index bdbaca2c42189bf..7055914b5560aec 100644
--- a/Modules/cjkcodecs/_codecs_iso2022.c
+++ b/Modules/cjkcodecs/_codecs_iso2022.c
@@ -533,15 +533,16 @@ DECODER(iso2022)
                         dsg = dsgcache;
                 else {
                     for (dsg = CONFIG_DESIGNATIONS;
-                         dsg->mark != charset
-#ifdef Py_DEBUG
-                            && dsg->mark != '\0'
-#endif
-                         ; dsg++)
+                         dsg->mark != charset && dsg->mark != '\0';
+                         dsg++)
                     {
                         /* noop */
                     }
-                    assert(dsg->mark != '\0');
+                    if (dsg->mark == '\0') {
+                        /* Unknown charset designation from a corrupt
+                           setstate(); no width to trust, report one byte. */
+                        return 1;
+                    }
                     dsgcache = dsg;
                 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.