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

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/f40043e0953323675843a3c275511596f30c80e9
commit: f40043e0953323675843a3c275511596f30c80e9
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-17T16:02:53+03:00
summary:

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

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.

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 d7a233377bdb023..6b032fc8604eefd 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 b1984df2695b172..e79e6e768445552 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.