From bf0624160668e0593272866977896c93956b22e1 Mon Sep 17 00:00:00 2001 From: superboy-zjc <1826599908@qq.com> Date: Thu, 18 Dec 2025 01:17:47 -0800 Subject: [PATCH 1/3] gh-142781: Fix type confusion in zoneinfo weak cache Validate the types returned from _weak_cache.get() and _weak_cache.setdefault() to prevent type confusion when a ZoneInfo subclass provides a misbehaving cache implementation. --- Lib/test/test_zoneinfo/test_zoneinfo.py | 27 +++++++++++++++++++ ...-12-18-00-14-16.gh-issue-142781.gcOeYF.rst | 3 +++ Modules/_zoneinfo.c | 18 +++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 8f3ca59c9ef5ed..fae7c6c264f28c 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1575,6 +1575,33 @@ class EvilZoneInfo(self.klass): class CZoneInfoCacheTest(ZoneInfoCacheTest): module = c_zoneinfo + def test_weak_cache_get_type_confusion(self): + class EvilCache: + def get(self, key, default=None): + return 1337 + + class EvilZoneInfo(self.klass): + pass + + EvilZoneInfo._weak_cache = EvilCache() + + with self.assertRaises(TypeError): + EvilZoneInfo("America/Los_Angeles") + + def test_weak_cache_setdefault_type_confusion(self): + class EvilCache: + def get(self, key, default=None): + return default + def setdefault(self, key, value): + return 1337 + + class EvilZoneInfo(self.klass): + pass + + EvilZoneInfo._weak_cache = EvilCache() + + with self.assertRaises(TypeError): + EvilZoneInfo("America/Los_Angeles") class ZoneInfoPickleTest(TzPathUserMixin, ZoneInfoTestBase): module = py_zoneinfo diff --git a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst new file mode 100644 index 00000000000000..27b6423f7279a6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst @@ -0,0 +1,3 @@ +Fix a type confusion in zoneinfo where a malicious override of _weak_cache +in a zoneinfo subclass can cause memory corruption and crash the +interpreter. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index e07dfd19efa06d..341b69602fccd9 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -327,6 +327,15 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) return NULL; } + if (instance != Py_None && !PyObject_TypeCheck(instance, type)) { + const char *e = "%s._weak_cache.get() returned %s, expected %s"; + PyErr_Format(PyExc_TypeError, e, + type->tp_name, Py_TYPE(instance)->tp_name, type->tp_name); + Py_DECREF(instance); + Py_DECREF(weak_cache); + return NULL; + } + if (instance == Py_None) { Py_DECREF(instance); PyObject *tmp = zoneinfo_new_instance(state, type, key); @@ -342,6 +351,15 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) Py_DECREF(weak_cache); return NULL; } + + if (!PyObject_TypeCheck(instance, type)) { + const char *e = "%s._weak_cache.setdefault() returned %s, expected %s"; + PyErr_Format(PyExc_TypeError, e, + type->tp_name, Py_TYPE(instance)->tp_name, type->tp_name); + Py_DECREF(instance); + Py_DECREF(weak_cache); + return NULL; + } ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE; } From 1c015211b827e1060d3934de5b95dec3eeb4c604 Mon Sep 17 00:00:00 2001 From: superboy-zjc <1826599908@qq.com> Date: Sun, 28 Dec 2025 22:04:55 -0800 Subject: [PATCH 2/3] gh-142781: address review comments by refactoring TypeError message, rewriting NEWS and improving unit test --- Lib/test/test_zoneinfo/test_zoneinfo.py | 32 +++++++++++-------- ...-12-18-00-14-16.gh-issue-142781.gcOeYF.rst | 5 ++- Modules/_zoneinfo.c | 10 +++--- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index fae7c6c264f28c..464a8a52cbe926 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1575,33 +1575,37 @@ class EvilZoneInfo(self.klass): class CZoneInfoCacheTest(ZoneInfoCacheTest): module = c_zoneinfo - def test_weak_cache_get_type_confusion(self): - class EvilCache: + def test_inconsistent_weak_cache_get(self): + class Cache: def get(self, key, default=None): return 1337 - class EvilZoneInfo(self.klass): + class ZI(self.klass): pass + # Must set AFTER class creation to override __init_subclass__ + ZI._weak_cache = Cache() - EvilZoneInfo._weak_cache = EvilCache() - - with self.assertRaises(TypeError): - EvilZoneInfo("America/Los_Angeles") + with self.assertRaises(TypeError) as te: + ZI("America/Los_Angeles") + # Heap type objects' tp_name should just be the type name, i.e. ZI + self.assertEqual(str(te.exception), "expected ZI, got int") - def test_weak_cache_setdefault_type_confusion(self): - class EvilCache: + def test_inconsistent_weak_cache_setdefault(self): + class Cache: def get(self, key, default=None): return default def setdefault(self, key, value): return 1337 - class EvilZoneInfo(self.klass): + class ZI(self.klass): pass + # Must set AFTER class creation to override __init_subclass__ + ZI._weak_cache = Cache() - EvilZoneInfo._weak_cache = EvilCache() - - with self.assertRaises(TypeError): - EvilZoneInfo("America/Los_Angeles") + with self.assertRaises(TypeError) as te: + ZI("America/Los_Angeles") + # Heap type objects' tp_name should just be the type name, i.e. ZI + self.assertEqual(str(te.exception), "expected ZI, got int") class ZoneInfoPickleTest(TzPathUserMixin, ZoneInfoTestBase): module = py_zoneinfo diff --git a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst index 27b6423f7279a6..e859dbe35be274 100644 --- a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst +++ b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst @@ -1,3 +1,2 @@ -Fix a type confusion in zoneinfo where a malicious override of _weak_cache -in a zoneinfo subclass can cause memory corruption and crash the -interpreter. +:mod:`zoneinfo`: fix a crash when instantiating :class:`~zoneinfo.ZoneInfo` +objects for which the internal class-level cache is inconsistent. \ No newline at end of file diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 341b69602fccd9..6387e533f5c8d0 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -328,9 +328,8 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) } if (instance != Py_None && !PyObject_TypeCheck(instance, type)) { - const char *e = "%s._weak_cache.get() returned %s, expected %s"; - PyErr_Format(PyExc_TypeError, e, - type->tp_name, Py_TYPE(instance)->tp_name, type->tp_name); + PyErr_Format(PyExc_TypeError, "expected %s, got %s", + type->tp_name, Py_TYPE(instance)->tp_name); Py_DECREF(instance); Py_DECREF(weak_cache); return NULL; @@ -353,9 +352,8 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) } if (!PyObject_TypeCheck(instance, type)) { - const char *e = "%s._weak_cache.setdefault() returned %s, expected %s"; - PyErr_Format(PyExc_TypeError, e, - type->tp_name, Py_TYPE(instance)->tp_name, type->tp_name); + PyErr_Format(PyExc_TypeError, "expected %s, got %s", + type->tp_name, Py_TYPE(instance)->tp_name); Py_DECREF(instance); Py_DECREF(weak_cache); return NULL; From 812e54e6274b5e59e7b15baa7e6d169b41953aa1 Mon Sep 17 00:00:00 2001 From: superboy-zjc <1826599908@qq.com> Date: Sun, 28 Dec 2025 22:16:22 -0800 Subject: [PATCH 3/3] gh-142781: fix NEWS Lint issue --- .../next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst index e859dbe35be274..772e05766c5c69 100644 --- a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst +++ b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst @@ -1,2 +1,2 @@ :mod:`zoneinfo`: fix a crash when instantiating :class:`~zoneinfo.ZoneInfo` -objects for which the internal class-level cache is inconsistent. \ No newline at end of file +objects for which the internal class-level cache is inconsistent.