CUtils::ParseServerTime hands an attacker-controlled IRCv3 @time= tag straight to cctz::parse without any range validation. cctz internally calls chrono::time_point_cast<microseconds> which multiplies the parsed seconds by 1,000,000. Years past roughly 292,000 (when seconds since the epoch exceeds INT64_MAX / 1_000_000) cause signed overflow inside libc++.
tp.time_since_epoch() is in seconds; the cast at line 575 (and equivalents inside cctz when it joins seconds + fractional) does seconds * 1_000_000 in int64.
Reachability
@time tags arrive from the IRC server ZNC connects to and end up parsed via CMessage::InitTime. A malicious or buggy IRC server can send any year in the tag value.
Production impact: arm64/x86 just wrap the multiply, so tv_sec becomes garbage and the message ends up with a wrong timestamp in buffer playback. No crash, no memory corruption.
UBSan / -ftrapv builds: hard abort. Concretely this fires under CIFuzz on any PR that touches the time-parsing path.
Could send a PR with the bound check + a regression test.
`CUtils::ParseServerTime` hands an attacker-controlled IRCv3 `@time=` tag straight to `cctz::parse` without any range validation. cctz internally calls `chrono::time_point_cast<microseconds>` which multiplies the parsed seconds by 1,000,000. Years past roughly 292,000 (when seconds since the epoch exceeds `INT64_MAX / 1_000_000`) cause signed overflow inside libc++.
```cpp
// src/Utils.cpp
569: timeval CUtils::ParseServerTime(const CString& sTime) {
570: using namespace std::chrono;
571: system_clock::time_point tp;
572: cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), &tp);
573: struct timeval tv;
574: memset(&tv, 0, sizeof(tv));
575: microseconds usec = duration_cast<microseconds>(tp.time_since_epoch());
576: tv.tv_sec = usec.count() / 1000000;
577: tv.tv_usec = usec.count() % 1000000;
578: return tv;
579: }
```
`tp.time_since_epoch()` is in seconds; the cast at line 575 (and equivalents inside cctz when it joins seconds + fractional) does `seconds * 1_000_000` in `int64`.
## Reachability
`@time` tags arrive from the IRC server ZNC connects to and end up parsed via `CMessage::InitTime`. A malicious or buggy IRC server can send any year in the tag value.
Production impact: arm64/x86 just wrap the multiply, so `tv_sec` becomes garbage and the message ends up with a wrong timestamp in buffer playback. No crash, no memory corruption.
UBSan / `-ftrapv` builds: hard abort. Concretely this fires under CIFuzz on any PR that touches the time-parsing path.
## Reproduction
```cpp
// poc_servertime.cpp
#include <cstdio>
#include <znc/Utils.h>
int main() {
timeval tv = CUtils::ParseServerTime("202620260-5-25T08:00:00Z");
fprintf(stderr, "tv_sec=%lld tv_usec=%ld\n",
(long long)tv.tv_sec, (long)tv.tv_usec);
}
```
Build:
```bash
clang++ -std=c++17 -fsanitize=address,undefined -g -O1 \
-I include -I build/include -include znc/zncconfig.h \
poc_servertime.cpp build/src/libznc.a \
-lpthread -ldl -framework CoreFoundation -L/opt/homebrew/lib -largon2 \
-o poc_servertime
UBSAN_OPTIONS=halt_on_error=0:print_stacktrace=1 ./poc_servertime
```
Output:
```
__chrono/duration.h:93:79: runtime error: signed integer overflow:
6394015664380800 * 1000000 cannot be represented in type 'long long'
#0 ... chrono::__duration_cast::operator()
#3 cctz::detail::join_seconds<long long, 1000000>
#4 cctz::parse<...>
#5 CUtils::ParseServerTime Utils.cpp:572
#6 main
tv_sec=-7004529196414 tv_usec=-410752
```
## Fix
Validate the seconds range before the cast (or catch it after). Simplest:
```cpp
timeval CUtils::ParseServerTime(const CString& sTime) {
using namespace std::chrono;
system_clock::time_point tp;
if (!cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), &tp))
return {};
auto secs = duration_cast<seconds>(tp.time_since_epoch()).count();
constexpr int64_t kMaxSec = INT64_MAX / 1'000'000;
if (secs > kMaxSec || secs < -kMaxSec) return {};
...
}
```
Could send a PR with the bound check + a regression test.
<!-- github-sync-id:4326795779 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
CUtils::ParseServerTimehands an attacker-controlled IRCv3@time=tag straight tocctz::parsewithout any range validation. cctz internally callschrono::time_point_cast<microseconds>which multiplies the parsed seconds by 1,000,000. Years past roughly 292,000 (when seconds since the epoch exceedsINT64_MAX / 1_000_000) cause signed overflow inside libc++.tp.time_since_epoch()is in seconds; the cast at line 575 (and equivalents inside cctz when it joins seconds + fractional) doesseconds * 1_000_000inint64.Reachability
@timetags arrive from the IRC server ZNC connects to and end up parsed viaCMessage::InitTime. A malicious or buggy IRC server can send any year in the tag value.Production impact: arm64/x86 just wrap the multiply, so
tv_secbecomes garbage and the message ends up with a wrong timestamp in buffer playback. No crash, no memory corruption.UBSan /
-ftrapvbuilds: hard abort. Concretely this fires under CIFuzz on any PR that touches the time-parsing path.Reproduction
Build:
Output:
Fix
Validate the seconds range before the cast (or catch it after). Simplest:
Could send a PR with the bound check + a regression test.