diff --git a/src/time_zone_info.cc b/src/time_zone_info.cc index 8820762..fe5dd28 100644 --- a/src/time_zone_info.cc +++ b/src/time_zone_info.cc @@ -269,6 +269,13 @@ inline civil_second YearShift(const civil_second& cs, year_t shift) { bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, const std::string& abbr, std::uint_least8_t* index) { + // Keep synthesized types within a day of UTC, matching the bound the reader + // enforces on file-supplied types (see Load). ParsePosixSpec() accepts an + // std/dst offset field up to 24:59:59, so a future spec can otherwise reach + // here with an out-of-range offset the TZif path would have rejected. + if (utc_offset <= -kSecsPerDay || utc_offset >= kSecsPerDay) { + return false; + } std::size_t type_index = 0; std::size_t abbr_index = abbreviations_.size(); for (; type_index != transition_types_.size(); ++type_index) { diff --git a/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc index a386879..60dc15d 100644 --- a/src/time_zone_lookup_test.cc +++ b/src/time_zone_lookup_test.cc @@ -1080,6 +1080,14 @@ std::unique_ptr ExtendedTestFactory( return std::unique_ptr(new StringZoneInfoSource( MakeExtendedTzif(0, -5 * 3600, "EST", "EST5EDT,M3.2.0,M11.1.0"))); } + if (name == "test:ExtendedOversizeOffset") { + // The DST offset field 24:30:00 (88200s) is more than a day from UTC. + // The reader rejects such offsets in file-supplied types, so the type + // synthesized from the POSIX footer must be rejected too. + return std::unique_ptr(new StringZoneInfoSource( + MakeExtendedTzif(0, -5 * 3600, std::string{"EST", 4}, + "EST5EDT24:30:00,M3.2.0,M11.1.0"))); + } return fallback(name); } @@ -1122,6 +1130,19 @@ TEST(TimeZoneEdgeCase, ExtendedOverlappingRules) { cctz_extension::zone_info_source_factory = prev_factory; } +// A POSIX footer whose std/dst offset field is a day or more from UTC yields +// a transition type the TZif reader would reject; loading must fail rather +// than build a zone with an out-of-range offset. +TEST(TimeZoneEdgeCase, ExtendedOversizeOffset) { + auto prev_factory = cctz_extension::zone_info_source_factory; + cctz_extension::zone_info_source_factory = ExtendedTestFactory; + + time_zone tz; + EXPECT_FALSE(load_time_zone("test:ExtendedOversizeOffset", &tz)); + + cctz_extension::zone_info_source_factory = prev_factory; +} + // Looking up the maximum time in an extended zone must fold back through the // 400-year cycle without overflowing when BreakTime() computes the shift. TEST(TimeZoneEdgeCase, ExtendedFarFuture) {