From d4a3777d6247044addfde71044ce2f34f4c9d961 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sat, 11 Jul 2026 14:37:20 +0800 Subject: [PATCH] gh-153603: Fix out-of-bounds read in the ISO-2022 decoder for an unknown charset 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. --- Lib/test/test_multibytecodec.py | 18 ++++++++++++++++++ ...6-07-11-13-24-49.gh-issue-153603.YbKlry.rst | 3 +++ Modules/cjkcodecs/_codecs_iso2022.c | 13 +++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-11-13-24-49.gh-issue-153603.YbKlry.rst 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; }