You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Timestamps arriving on server links are parsed with atoi(), which
returns int. On every platform ircu targets that is 32 bits, so the
parse saturates at 2,147,483,647 — 2038-01-19 03:14:07 UTC. This is
a genuine Year 2038 bug, and it bites even on a 64-bit time_t
system, because the truncation happens inside atoi() before the
value is ever widened into the time_t it is assigned to:
time_tcreation=atoi(parv[2]); /* truncated at INT_MAX, then widened */
Good news: the fix is input-only and protocol-neutral, so it can be
done today, independent of the P11 series. It is not a wire-format
change.
Why it needs no protocol change
The output side is already 64-bit clean: ircd_snprintf's %Tu
reads a full time_t via va_arg (ircd_snprintf.c:1821, :1844),
so timestamps already go onto the wire as complete decimal strings. A
post-2038 value like 2147483648 transmits fine today.
The wire format is decimal ASCII, which has no 32-bit ceiling. A fixed
receiver reading 2147483648 with a wide parse just works, regardless
of the sender's age. There is no flag day.
There is already precedent for the correct idiom in the tree — m_sline.c:128:
lastmod= (time_t)strtoll(parv[2], NULL, 10);
and s_user.c:1516 even carries the comment "pity we don't have
strtoul everywhere." This issue is about finishing that conversion.
Fix
Replace atoi() with (time_t)strtoll(str, NULL, 10) at every site
that parses an epoch (or a relative offset that becomes one), ideally
behind a small helper — e.g. time_t atotime(const char *) next to the
other string helpers — so the intent is legible and future audits are
grep-able.
link lag sample — also widen the local int timestamp
G-line expire and jupe expire_off arrive as relative offsets and are
made absolute (abs_expire adds CurrentTime); they still overflow if
the offset alone exceeds INT_MAX, and lastmod/lifetime are
absolute, so all of them should be widened.
A second, related 32-bit truncation (not strictly 2038)
acc_id is declared uint64_t (struct.h:91) but parsed with atoi()
at s_user.c:1262 and m_account.c:135, so account IDs above 2^31
truncate today. Parse with strtoull. (acc_flags at s_user.c:1270 / m_account.c:138 is a flag word — widen for consistency, low risk.)
Explicitly out of scope — leave as atoi()
About 40 remaining atoi() calls parse non-time values and have no 2038
exposure: TCP ports (listener.c, m_connect.c, m_uping.c, uping.c), hop counts, protocol numbers (m_server.c:94-96, ircd.c:785), op-levels (channel.c:3159,3181), CAP version
(m_cap.c:274), WHOWAS max, umode mask (s_user.c:1517),
debug/oper-class levels, config ints (ircd_netconf.c), and the
millisecond RTT deltas (m_pong, m_ping, m_asll, opercmds — these
are differences, not epochs). No change needed.
What belongs to the P11 roll-out
Nothing about 2038 requires P11, but two items are worth pinning to it:
Add a regression test. There is currently no coverage that a
post-2038 timestamp survives. Feed a NICK/CREATE/MODE/burst
line carrying a TS like 2200000000 (year 2039) and assert it
round-trips through parse and re-emit unchanged. That test fails on
today's atoi() code and locks in the fix; it should assert on the
receiving (leaf) side, not just locally.
Build caveat
The parse fix assumes a 64-bit time_t, which holds on any 64-bit
build. On a 32-bit target with glibc >= 2.34 you additionally need -D_TIME_BITS=64 (with -D_FILE_OFFSET_BITS=64), or time_t itself is
32-bit and the wide parse overflows one step later. A configure check
that warns on a 32-bit time_t build would be a reasonable follow-up,
separate from and lower priority than the atoi() work.
Suggested sequencing
Now, independent of P11: convert the ~27 timestamp sites (and acc_id) to the wide parse. Backward-compatible, no negotiation.
Summary
Timestamps arriving on server links are parsed with
atoi(), whichreturns
int. On every platform ircu targets that is 32 bits, so theparse saturates at 2,147,483,647 — 2038-01-19 03:14:07 UTC. This is
a genuine Year 2038 bug, and it bites even on a 64-bit
time_tsystem, because the truncation happens inside
atoi()before thevalue is ever widened into the
time_tit is assigned to:Good news: the fix is input-only and protocol-neutral, so it can be
done today, independent of the P11 series. It is not a wire-format
change.
Why it needs no protocol change
ircd_snprintf's%Tureads a full
time_tviava_arg(ircd_snprintf.c:1821,:1844),so timestamps already go onto the wire as complete decimal strings. A
post-2038 value like
2147483648transmits fine today.receiver reading
2147483648with a wide parse just works, regardlessof the sender's age. There is no flag day.
There is already precedent for the correct idiom in the tree —
m_sline.c:128:and
s_user.c:1516even carries the comment "pity we don't havestrtoul everywhere." This issue is about finishing that conversion.
Fix
Replace
atoi()with(time_t)strtoll(str, NULL, 10)at every sitethat parses an epoch (or a relative offset that becomes one), ideally
behind a small helper — e.g.
time_t atotime(const char *)next to theother string helpers — so the intent is legible and future audits are
grep-able.
Timestamp sites to widen (~27 across 13 files)
m_server.c:565,566,729,730start_timestamp,timestampm_squit.c:90m_nick.c:310,316lastnicks_user.c:539,620cli_lastnickm_create.c:124,m_join.c:343m_burst.c:228,m_destruct.c:102channel.c:3523recv_ts— theMODEchannel TS (see #114)m_topic.c:203,207m_invite.c:276m_settime.c:120,220SETTIMEtarget timem_gline.c:223,294,299,301,306expire/lastmod/lifetimem_jupe.c:150,151,213,218lastmod(absolute) andexpire_offm_rping.c:150int timestampG-line
expireand jupeexpire_offarrive as relative offsets and aremade absolute (
abs_expireaddsCurrentTime); they still overflow ifthe offset alone exceeds
INT_MAX, andlastmod/lifetimeareabsolute, so all of them should be widened.
A second, related 32-bit truncation (not strictly 2038)
acc_idis declareduint64_t(struct.h:91) but parsed withatoi()at
s_user.c:1262andm_account.c:135, so account IDs above 2^31truncate today. Parse with
strtoull. (acc_flagsats_user.c:1270/m_account.c:138is a flag word — widen for consistency, low risk.)Explicitly out of scope — leave as
atoi()About 40 remaining
atoi()calls parse non-time values and have no 2038exposure: TCP ports (
listener.c,m_connect.c,m_uping.c,uping.c), hop counts, protocol numbers (m_server.c:94-96,ircd.c:785), op-levels (channel.c:3159,3181), CAP version(
m_cap.c:274), WHOWASmax, umode mask (s_user.c:1517),debug/oper-class levels, config ints (
ircd_netconf.c), and themillisecond RTT deltas (
m_pong,m_ping,m_asll,opercmds— theseare differences, not epochs). No change needed.
What belongs to the P11 roll-out
Nothing about 2038 requires P11, but two items are worth pinning to it:
MODEtimestamp (Make the channel timestamp mandatory onMODE(M) in the next protocol revision — the current optional-TS heuristic can be tricked into settingcreationtimeto a garbage value #114) and the burst ban setter/timestamp (Burst ban setter and timestamp in P11 — ban metadata is currently discarded on every relink #116)introduce new epoch fields; write their parsers with the wide path so
they are not re-audited later.
post-2038 timestamp survives. Feed a
NICK/CREATE/MODE/burstline carrying a TS like
2200000000(year 2039) and assert itround-trips through parse and re-emit unchanged. That test fails on
today's
atoi()code and locks in the fix; it should assert on thereceiving (leaf) side, not just locally.
Build caveat
The parse fix assumes a 64-bit
time_t, which holds on any 64-bitbuild. On a 32-bit target with glibc >= 2.34 you additionally need
-D_TIME_BITS=64(with-D_FILE_OFFSET_BITS=64), ortime_titself is32-bit and the wide parse overflows one step later. A
configurecheckthat warns on a 32-bit
time_tbuild would be a reasonable follow-up,separate from and lower priority than the
atoi()work.Suggested sequencing
acc_id) to the wide parse. Backward-compatible, no negotiation.MODE(M) in the next protocol revision — the current optional-TS heuristic can be tricked into settingcreationtimeto a garbage value #114 /Burst ban setter and timestamp in P11 — ban metadata is currently discarded on every relink #116, and add the post-2038 round-trip test.
Happy to open the PR for the conversion if there's agreement.