https://github.com/python/cpython/commit/df89a705d03bf6d7b6477f7a63cfdfb07278766e
commit: df89a705d03bf6d7b6477f7a63cfdfb07278766e
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-04-04T17:14:19+01:00
summary:

[3.13] gh-145883: Fix two heap-buffer-overflows in `_zoneinfo` (GH-145885) 
(#148086)

(cherry picked from commit fe9befc1ca7eac36749ec358969464334381b9f9)

Co-authored-by: Stan Ulbrych <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst
M Lib/test/test_zoneinfo/test_zoneinfo.py
M Lib/zoneinfo/_common.py
M Lib/zoneinfo/_zoneinfo.py
M Modules/_zoneinfo.c
M Tools/build/compute-changes.py

diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py 
b/Lib/test/test_zoneinfo/test_zoneinfo.py
index 26fc500930f1a1..9483c2fc512e0d 100644
--- a/Lib/test/test_zoneinfo/test_zoneinfo.py
+++ b/Lib/test/test_zoneinfo/test_zoneinfo.py
@@ -741,6 +741,38 @@ def test_empty_zone(self):
         with self.assertRaises(ValueError):
             self.klass.from_file(zf)
 
+    def test_invalid_transition_index(self):
+        STD = ZoneOffset("STD", ZERO)
+        DST = ZoneOffset("DST", ONE_H, ONE_H)
+
+        zf = self.construct_zone([
+            ZoneTransition(datetime(2026, 3, 1, 2), STD, DST),
+            ZoneTransition(datetime(2026, 11, 1, 2), DST, STD),
+        ], after="", version=1)
+
+        data = bytearray(zf.read())
+        timecnt = struct.unpack_from(">l", data, 32)[0]
+        idx_offset = 44 + timecnt * 4
+        data[idx_offset + 1] = 2  # typecnt is 2, so index 2 is OOB
+        f = io.BytesIO(bytes(data))
+
+        with self.assertRaises(ValueError):
+            self.klass.from_file(f)
+
+    def test_transition_lookahead_out_of_bounds(self):
+        STD = ZoneOffset("STD", ZERO)
+        DST = ZoneOffset("DST", ONE_H, ONE_H)
+        EXT = ZoneOffset("EXT", ONE_H)
+
+        zf = self.construct_zone([
+            ZoneTransition(datetime(2026, 3, 1), STD, DST),
+            ZoneTransition(datetime(2026, 6, 1), DST, EXT),
+            ZoneTransition(datetime(2026, 9, 1), EXT, DST),
+        ], after="")
+
+        zi = self.klass.from_file(zf)
+        self.assertIsNotNone(zi)
+
     def test_zone_very_large_timestamp(self):
         """Test when a transition is in the far past or future.
 
diff --git a/Lib/zoneinfo/_common.py b/Lib/zoneinfo/_common.py
index 59f3f0ce853f74..98668c15d8bf94 100644
--- a/Lib/zoneinfo/_common.py
+++ b/Lib/zoneinfo/_common.py
@@ -67,6 +67,10 @@ def load_data(fobj):
             f">{timecnt}{time_type}", fobj.read(timecnt * time_size)
         )
         trans_idx = struct.unpack(f">{timecnt}B", fobj.read(timecnt))
+
+        if max(trans_idx) >= typecnt:
+            raise ValueError("Invalid transition index found while reading 
TZif: "
+                            f"{max(trans_idx)}")
     else:
         trans_list_utc = ()
         trans_idx = ()
diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py
index bd3fefc6c9d959..7063eb6a9025ac 100644
--- a/Lib/zoneinfo/_zoneinfo.py
+++ b/Lib/zoneinfo/_zoneinfo.py
@@ -338,7 +338,7 @@ def _utcoff_to_dstoff(trans_idx, utcoffsets, isdsts):
             if not isdsts[comp_idx]:
                 dstoff = utcoff - utcoffsets[comp_idx]
 
-            if not dstoff and idx < (typecnt - 1):
+            if not dstoff and idx < (typecnt - 1) and i + 1 < len(trans_idx):
                 comp_idx = trans_idx[i + 1]
 
                 # If the following transition is also DST and we couldn't
diff --git 
a/Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst 
b/Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst
new file mode 100644
index 00000000000000..2c17768c5189da
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst
@@ -0,0 +1,2 @@
+:mod:`zoneinfo`: Fix heap buffer overflow reads from malformed TZif data.
+Found by OSS Fuzz, issues :oss-fuzz:`492245058` and :oss-fuzz:`492230068`.
diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c
index 25cc4042ce92a3..ac584ea01cd246 100644
--- a/Modules/_zoneinfo.c
+++ b/Modules/_zoneinfo.c
@@ -1073,7 +1073,7 @@ load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo 
*self, PyObject *file_obj)
         }
 
         trans_idx[i] = (size_t)cur_trans_idx;
-        if (trans_idx[i] > self->num_ttinfos) {
+        if (trans_idx[i] >= self->num_ttinfos) {
             PyErr_Format(
                 PyExc_ValueError,
                 "Invalid transition index found while reading TZif: %zd",
@@ -2079,7 +2079,7 @@ utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long 
*dstoffs,
             dstoff = utcoff - utcoffs[comp_idx];
         }
 
-        if (!dstoff && idx < (num_ttinfos - 1)) {
+        if (!dstoff && idx < (num_ttinfos - 1) && i + 1 < num_transitions) {
             comp_idx = trans_idx[i + 1];
 
             // If the following transition is also DST and we couldn't find
diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py
index 37410dcd971d4e..4fc6909f5ac775 100644
--- a/Tools/build/compute-changes.py
+++ b/Tools/build/compute-changes.py
@@ -96,6 +96,9 @@
     Path("Modules/pyexpat.c"),
     # zipfile
     Path("Lib/zipfile/"),
+    # zoneinfo
+    Path("Lib/zoneinfo/"),
+    Path("Modules/_zoneinfo.c"),
 })
 
 

_______________________________________________
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]

Reply via email to