From b9442991673479080314c7c6ae5938d69a79b79c Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Sat, 25 Apr 2026 10:36:53 +0800 Subject: [PATCH] Utils: reject out-of-range years in ParseServerTime cctz::parse into a microseconds time_point internally multiplies the parsed seconds-since-epoch by 1,000,000 in signed int64. Years past ~292k overflow, which is UB under UBSan or -ftrapv builds. In plain production builds the overflow silently wraps and buffer playback shows a garbage timestamp. Reject anything with a year longer than 5 digits before calling into cctz. 5 digits covers every realistic IRCv3 @time tag. --- src/Utils.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 95d6db71..fe726c3f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -568,10 +568,21 @@ CString CUtils::FormatServerTime(const timeval& tv) { timeval CUtils::ParseServerTime(const CString& sTime) { using namespace std::chrono; - system_clock::time_point tp; - cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), &tp); struct timeval tv; memset(&tv, 0, sizeof(tv)); + // Reject obviously out-of-range years up front so we don't hand cctz + // an input whose internal `seconds * 1_000_000` conversion would + // overflow signed int64 (UB). A 5-digit year is plenty for any + // legitimate IRCv3 @time tag. + CString sYear = sTime.Token(0, false, "-"); + if (sYear.length() > 5) { + return tv; + } + system_clock::time_point tp; + if (!cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), + &tp)) { + return tv; + } microseconds usec = duration_cast(tp.time_since_epoch()); tv.tv_sec = usec.count() / 1000000; tv.tv_usec = usec.count() % 1000000;