Skip to content

Commit 64207c9

Browse files
[3.14] gh-145883: Fix two heap-buffer-overflows in _zoneinfo (GH-145885) (#148087)
(cherry picked from commit fe9befc) Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 61c919c commit 64207c9

File tree

6 files changed

+44
-3
lines changed

6 files changed

+44
-3
lines changed

Lib/test/test_zoneinfo/test_zoneinfo.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,38 @@ def test_empty_zone(self):
741741
with self.assertRaises(ValueError):
742742
self.klass.from_file(zf)
743743

744+
def test_invalid_transition_index(self):
745+
STD = ZoneOffset("STD", ZERO)
746+
DST = ZoneOffset("DST", ONE_H, ONE_H)
747+
748+
zf = self.construct_zone([
749+
ZoneTransition(datetime(2026, 3, 1, 2), STD, DST),
750+
ZoneTransition(datetime(2026, 11, 1, 2), DST, STD),
751+
], after="", version=1)
752+
753+
data = bytearray(zf.read())
754+
timecnt = struct.unpack_from(">l", data, 32)[0]
755+
idx_offset = 44 + timecnt * 4
756+
data[idx_offset + 1] = 2 # typecnt is 2, so index 2 is OOB
757+
f = io.BytesIO(bytes(data))
758+
759+
with self.assertRaises(ValueError):
760+
self.klass.from_file(f)
761+
762+
def test_transition_lookahead_out_of_bounds(self):
763+
STD = ZoneOffset("STD", ZERO)
764+
DST = ZoneOffset("DST", ONE_H, ONE_H)
765+
EXT = ZoneOffset("EXT", ONE_H)
766+
767+
zf = self.construct_zone([
768+
ZoneTransition(datetime(2026, 3, 1), STD, DST),
769+
ZoneTransition(datetime(2026, 6, 1), DST, EXT),
770+
ZoneTransition(datetime(2026, 9, 1), EXT, DST),
771+
], after="")
772+
773+
zi = self.klass.from_file(zf)
774+
self.assertIsNotNone(zi)
775+
744776
def test_zone_very_large_timestamp(self):
745777
"""Test when a transition is in the far past or future.
746778

Lib/zoneinfo/_common.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def load_data(fobj):
6767
f">{timecnt}{time_type}", fobj.read(timecnt * time_size)
6868
)
6969
trans_idx = struct.unpack(f">{timecnt}B", fobj.read(timecnt))
70+
71+
if max(trans_idx) >= typecnt:
72+
raise ValueError("Invalid transition index found while reading TZif: "
73+
f"{max(trans_idx)}")
7074
else:
7175
trans_list_utc = ()
7276
trans_idx = ()

Lib/zoneinfo/_zoneinfo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ def _utcoff_to_dstoff(trans_idx, utcoffsets, isdsts):
338338
if not isdsts[comp_idx]:
339339
dstoff = utcoff - utcoffsets[comp_idx]
340340

341-
if not dstoff and idx < (typecnt - 1):
341+
if not dstoff and idx < (typecnt - 1) and i + 1 < len(trans_idx):
342342
comp_idx = trans_idx[i + 1]
343343

344344
# If the following transition is also DST and we couldn't
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`zoneinfo`: Fix heap buffer overflow reads from malformed TZif data.
2+
Found by OSS Fuzz, issues :oss-fuzz:`492245058` and :oss-fuzz:`492230068`.

Modules/_zoneinfo.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ load_data(zoneinfo_state *state, PyZoneInfo_ZoneInfo *self, PyObject *file_obj)
10721072
}
10731073

10741074
trans_idx[i] = (size_t)cur_trans_idx;
1075-
if (trans_idx[i] > self->num_ttinfos) {
1075+
if (trans_idx[i] >= self->num_ttinfos) {
10761076
PyErr_Format(
10771077
PyExc_ValueError,
10781078
"Invalid transition index found while reading TZif: %zd",
@@ -2078,7 +2078,7 @@ utcoff_to_dstoff(size_t *trans_idx, long *utcoffs, long *dstoffs,
20782078
dstoff = utcoff - utcoffs[comp_idx];
20792079
}
20802080

2081-
if (!dstoff && idx < (num_ttinfos - 1)) {
2081+
if (!dstoff && idx < (num_ttinfos - 1) && i + 1 < num_transitions) {
20822082
comp_idx = trans_idx[i + 1];
20832083

20842084
// If the following transition is also DST and we couldn't find

Tools/build/compute-changes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
Path("Modules/pyexpat.c"),
9999
# zipfile
100100
Path("Lib/zipfile/"),
101+
# zoneinfo
102+
Path("Lib/zoneinfo/"),
103+
Path("Modules/_zoneinfo.c"),
101104
})
102105

103106

0 commit comments

Comments
 (0)