diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 1e71b72dc87685..81fce3f4155afe 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -14,6 +14,7 @@ import struct import tempfile import unittest +import sys from datetime import date, datetime, time, timedelta, timezone from functools import cached_property @@ -2253,6 +2254,43 @@ def _Pacific_Kiritimati(): _ZONEDUMP_DATA = None _FIXED_OFFSET_ZONES = None +class CZoneInfoSanityTest(CZoneInfoTest): + """gh-125318: custom non-PyDateTime types could cause out-of-bounds read""" + + class CustomDateTime: + def __init__(self, year, month, day, hour=0, minute=0, second=0): + self.ordinal = date(year, month, day).toordinal() + self.hour = hour + self.minute = minute + self.second = second + + def toordinal(self): + return self.ordinal + + def _spray(self, depth): + if depth == 0: + return None + self._spray(depth - 1) + + def test_find_ttinfo_sanity(self): + RECURSION_LIMIT = 1000000 + SPRAY_TIMES = 10000 + CHECK_TIMES = 10000 + + # spray some garbage on the stack + saved_limit = sys.getrecursionlimit() + sys.setrecursionlimit(RECURSION_LIMIT) + self._spray(SPRAY_TIMES) + sys.setrecursionlimit(saved_limit) + + for _ in range(CHECK_TIMES): + zi = self.klass("UTC") + dt = self.CustomDateTime(2024, 10, 22, 10, 24, 20) + try: + zi.utcoffset(dt) + except MemoryError: + continue + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-10-22-10-27-33.gh-issue-125824._oesZh.rst b/Misc/NEWS.d/next/Library/2024-10-22-10-27-33.gh-issue-125824._oesZh.rst new file mode 100644 index 00000000000000..546a2b5e2c9fb2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-22-10-27-33.gh-issue-125824._oesZh.rst @@ -0,0 +1 @@ +Add sanity check in :mod:`zoneinfo` to mitigate out-of-bounds read. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 47e40125cf8091..f6c669b2fcec3d 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -2205,7 +2205,15 @@ find_ttinfo(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *dt) } unsigned char fold = PyDateTime_DATE_GET_FOLD(dt); - assert(fold < 2); + + // gh-125318: out-of-bounds sanity check on non-PyDateTime types + if (fold >= 2) { + PyErr_Format(PyExc_MemoryError, + "find_ttinfo: sanity check failed, fold = %d, expected " + "only 0 or 1", fold); + return NULL; + } + int64_t *local_transitions = self->trans_list_wall[fold]; size_t num_trans = self->num_transitions;