From b764373776a7b3bba195d19e8e6e9b57290dce60 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 12 Mar 2026 17:28:15 +0000 Subject: [PATCH 1/3] Fix two heap-buffer-overflows in `zoneinfo` --- .gitattributes | 1 + .../test_zoneinfo/data/tzif_invalid_lookahead | Bin 0 -> 288 bytes .../test_zoneinfo/data/tzif_invalid_trans_idx | Bin 0 -> 289 bytes Lib/test/test_zoneinfo/test_zoneinfo.py | 10 ++++++++++ Lib/zoneinfo/_common.py | 4 ++++ Lib/zoneinfo/_zoneinfo.py | 2 +- Modules/_zoneinfo.c | 4 ++-- 7 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 Lib/test/test_zoneinfo/data/tzif_invalid_lookahead create mode 100644 Lib/test/test_zoneinfo/data/tzif_invalid_trans_idx diff --git a/.gitattributes b/.gitattributes index 0dac0f84927119..ffe5ac029d53c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,7 @@ *.zip binary # Specific binary files +Lib/test/test_zoneinfo/data/tzif_* binary PC/classicAppCompat.* binary # Text files that should not be subject to eol conversion diff --git a/Lib/test/test_zoneinfo/data/tzif_invalid_lookahead b/Lib/test/test_zoneinfo/data/tzif_invalid_lookahead new file mode 100644 index 0000000000000000000000000000000000000000..a1a4327ab93af7be92ace7983cadf4feac292cf7 GIT binary patch literal 288 zcmWHE%1mQm;9xLffP()J@EyiyU|<3=nHd-az!C^?Mg|5(pj;B=0Ajp0Ujxvh3qP+r zFftXefvk6U03yvD7+4q>>^&Gbe0)O~v<*xcLV#|Dx;M>;1EdBDAWjE53=IAw+zR#s g56mYFd{Bdch8Y_`jQa-z3|ux~VLM$DD`NvL0H<3@!T zGqW@@ur#(XF*61tV{;QT19KB&Lvu3=BSS+IQ_~I+pyK~$LVyl2QH)hIjIFrHj${>> kiOoKSIV9SbjbWb=s(srtfKHJ(o-_ODzXR?6K&}D;0O`a?`Tzg` literal 0 HcmV?d00001 diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index a5dea802a9898d..bb8ba5fef8be61 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -741,6 +741,16 @@ def test_empty_zone(self): with self.assertRaises(ValueError): self.klass.from_file(zf) + def test_invalid_transition_index(self): + with open(DATA_DIR / "tzif_invalid_trans_idx", "rb") as f: + with self.assertRaises(ValueError): + self.klass.from_file(f) + + def test_transition_lookahead_out_of_bounds(self): + with open(DATA_DIR / "tzif_invalid_lookahead", "rb") as f: + zi = self.klass.from_file(f) + 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..c12b23b6c83472 100644 --- a/Lib/zoneinfo/_common.py +++ b/Lib/zoneinfo/_common.py @@ -71,6 +71,10 @@ def load_data(fobj): trans_list_utc = () trans_idx = () + for idx in trans_idx: + if idx >= typecnt: + raise ValueError(f"Invalid transition index found while reading TZif: {idx}") + # Read the ttinfo struct, (utoff, isdst, abbrind) if typecnt: utcoff, isdst, abbrind = zip( 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/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 39671d1ab51dfa..bd151101723cd7 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -1065,7 +1065,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", @@ -2063,7 +2063,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 From 0f27a0762ed2c5ee740201aab3976a6f110120bc Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 12 Mar 2026 21:05:40 +0000 Subject: [PATCH 2/3] Mimize testcase + blurb --- .../test_zoneinfo/data/tzif_invalid_trans_idx | Bin 289 -> 59 bytes ...026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-03-12-21-01-48.gh-issue-145883.lUvXcc.rst diff --git a/Lib/test/test_zoneinfo/data/tzif_invalid_trans_idx b/Lib/test/test_zoneinfo/data/tzif_invalid_trans_idx index f6dec2a7cf2f32d1fac8da53777a1f380c16e5f3..b438f996727780faa34b23db7ce31609aace7ad3 100644 GIT binary patch literal 59 ccmWHE%1mRx4;X>cAj|?%24Vm~Xoxcd09g3~aR2}S literal 289 zcmWHE%1kq4zzUe5qA(gs5et+p45bAc7#NJ${H}=1+2b(fbjXn@|NsAIFg7tTvM@3> zGqW@@ur#(XF*61tV{;QT19KB&Lvu3=BSS+IQ_~I+pyK~$LVyl2QH)hIjIFrHj${>> kiOoKSIV9SbjbWb=s(srtfKHJ(o-_ODzXR?6K&}D;0O`a?`Tzg` 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`. From bf3f3a5261c54d38c27583aee5fd6b684142f03a Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 12 Mar 2026 21:09:44 +0000 Subject: [PATCH 3/3] Add zoneinfo to compute-changes.py --- Tools/build/compute-changes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py index 4d92b083026b27..7f2b46cefcc20b 100644 --- a/Tools/build/compute-changes.py +++ b/Tools/build/compute-changes.py @@ -98,6 +98,9 @@ Path("Modules/pyexpat.c"), # zipfile Path("Lib/zipfile/"), + # zoneinfo + Path("Lib/zoneinfo/"), + Path("Modules/_zoneinfo.c"), })