]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-145883: Fix two heap-buffer-overflows in `_zoneinfo` (#145885)
authorStan Ulbrych <stan@python.org>
Sat, 4 Apr 2026 12:29:17 +0000 (13:29 +0100)
committerGitHub <noreply@github.com>
Sat, 4 Apr 2026 12:29:17 +0000 (13:29 +0100)
Lib/test/test_zoneinfo/test_zoneinfo.py
Lib/zoneinfo/_common.py
Lib/zoneinfo/_zoneinfo.py
Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst [new file with mode: 0644]
Modules/_zoneinfo.c
Tools/build/compute-changes.py

index aaab4709464fd01cc25258087d45b6929b8f6dbc..7502b120825fbce1f64017eb341d6ea9eca0243e 100644 (file)
@@ -741,6 +741,38 @@ class WeirdZoneTest(ZoneInfoTestBase):
         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.
 
index 59f3f0ce853f74ee86096d86fb67f980bc9a8cd3..98668c15d8bf94b67c0649a58d3f071071296efd 100644 (file)
@@ -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 = ()
index bd3fefc6c9d95998c389f0e207a3c7432feb763a..7063eb6a9025ac25e0266174ca1fb72774f446b0 100644 (file)
@@ -338,7 +338,7 @@ class ZoneInfo(tzinfo):
             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 (file)
index 0000000..2c17768
--- /dev/null
@@ -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`.
index aa0b1302cb2fc6df4250407a1e956ebe2c3e12a1..eaffd020ed97c09cb028f418017e09ddbd301590 100644 (file)
@@ -1075,7 +1075,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",
@@ -2081,7 +2081,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
index c15dc599f993f3e7cca3bc74eab5efe361c6816c..4870388da0d8a51414aa371f781185b49d05ab83 100644 (file)
@@ -99,6 +99,9 @@ LIBRARY_FUZZER_PATHS = frozenset({
     Path("Modules/pyexpat.c"),
     # zipfile
     Path("Lib/zipfile/"),
+    # zoneinfo
+    Path("Lib/zoneinfo/"),
+    Path("Modules/_zoneinfo.c"),
 })