Skip to content

Year 2038: parse wire timestamps at full time_t width instead of atoi() - #122

Open
MrIron-no wants to merge 2 commits into
UndernetIRC:mainfrom
MrIron-no:fix/y2038-atoi-timestamps
Open

MrIron-no wants to merge 2 commits into
UndernetIRC:mainfrom
MrIron-no:fix/y2038-atoi-timestamps

Conversation

@MrIron-no

Copy link
Copy Markdown
Contributor

Fixes #119.

Summary

Server-link timestamps were parsed with atoi(), which returns int and
therefore saturates at INT_MAX (2038-01-19 03:14:07 UTC) — the truncation
happens inside atoi(), before the value is widened into its time_t
destination, so it breaks even on a 64-bit time_t system.

The fix is input-only and protocol-neutral: ircd_snprintf's %Tu already
emits a full time_t, and timestamps travel as decimal ASCII, so a wide parse
is forward-compatible with existing peers and needs no protocol change.

Changes

New helper. time_t atotime(const char *) in ircd_string.c parses at full
width via strtoll().

~22 timestamp parses converted from atoi() to atotime():
server link start/timestamp (m_server), nick lastnick (m_nick,
s_user), channel creation TS (CREATE/JOIN/BURST/DESTRUCT), the MODE
recv_ts (channel.c), TOPIC times, INVITE, SETTIME, G-line
expire/lastmod/lifetime, jupe lastmod, and the RPING lag sample (its
local int widened to time_t).

Related 32-bit truncation. acc_id and acc_flags are uint64_t but were
parsed with atoi(); use strtoull() so account ids above 2^31 survive.

Left unchanged: non-time atoi() calls (ports, hop counts, protocol
numbers, op-levels, counts, umode masks, CAP version, millisecond RTT deltas).

Build guard (configure.ac). The fix assumes a 64-bit time_t. Added a
check that measures sizeof(time_t), tries the glibc remedy
(-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64) if it is smaller, and then warns by
default or fails under --enable-y2038-strict (for CI/release builds).
configure is generated and regenerated via autogen.sh, so only
configure.ac changes here.

Tests

Added an atotime() regression test to ircd_string_t asserting that a
post-2038 value (2200000000, 2039-09-13) round-trips intact on a 64-bit
time_t build, guarded so a legitimately 32-bit time_t build skips rather
than fails.

Validation

Built and run through the regular build (after ./autogen.sh):

  • Full ircd binary builds clean, no new warnings.
  • ircd_string_t passes; the post-2038 value is preserved (atoi would turn
    it into -2094967296).
  • configure reports the 64-bit case silently; the warn and
    --enable-y2038-strict error paths were both exercised and fire correctly.

atoi() returns int, so every server-link timestamp parsed with it
saturates at INT_MAX (2038-01-19 03:14:07 UTC), truncating before the
value is ever widened into its time_t destination. This breaks even on a
64-bit time_t system: the loss happens inside atoi().

The fix is input-only and protocol-neutral. ircd_snprintf's %Tu already
emits a full time_t, and timestamps travel as decimal ASCII, so a wide
parse is forward-compatible with existing peers and needs no protocol
change.

Add a helper time_t atotime(const char*) that parses at full width via
strtoll(), and convert every timestamp parse that feeds a time_t:

  - server link start/timestamp (m_server)
  - nick lastnick (m_nick, s_user)
  - channel creation TS (CREATE/JOIN/BURST/DESTRUCT)
  - MODE channel TS recv_ts (channel.c)
  - TOPIC times (m_topic), INVITE ts (m_invite), SETTIME (m_settime)
  - G-line expire/lastmod/lifetime (m_gline)
  - jupe expire_off/lastmod (m_jupe)
  - the RPING lag sample (m_rping; its local int is widened to time_t)

Also fix a related 32-bit truncation: acc_id and acc_flags are uint64_t
but were parsed with atoi(); use strtoull() so account ids above 2^31
survive.

Non-time atoi() calls (ports, hop counts, protocol numbers, op-levels,
counts, umode masks, CAP version, millisecond RTT deltas) are left
unchanged.

Add an atotime() regression test to ircd_string_t asserting that a
post-2038 value (2200000000, 2039-09-13) round-trips intact on a 64-bit
time_t build, guarded so a legitimately 32-bit time_t build skips rather
than fails.

Closes UndernetIRC#119.
The atotime() timestamp fix relies on a 64-bit time_t; on a 32-bit
time_t the wire timestamps still overflow in 2038, one step later. Add a
configure check that measures sizeof(time_t) and, if it is under 8 bytes,
tries the glibc remedy (-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64) before
giving up. If a 64-bit time_t still cannot be obtained it warns by
default, or fails when --enable-y2038-strict is given (useful for CI and
release builds).

configure is generated (gitignored) and regenerated via autogen.sh, so
only configure.ac changes.

Relates to UndernetIRC#119.
@MrIron-no
MrIron-no force-pushed the fix/y2038-atoi-timestamps branch from 19bf078 to 0648d58 Compare September 15, 2026 11:25
@MrIron-no

Copy link
Copy Markdown
Contributor Author

Force-pushed an amend: the first pass missed three timestamp atoi() sites that issue #119 had listed — channel.c MODE recv_ts, s_user.c nick lastnick (both sites), and all of m_jupe.c (expire_off/lastmod). They are now converted to atotime(), and the commit message is corrected to match the actual diff. No timestamp atoi() remains; build and the atotime unit test are green.

MrIron-no added a commit to MrIron-no/ircu2 that referenced this pull request Oct 6, 2026
atotime() arrives with the Year 2038 change (UndernetIRC#122), which this branch
does not carry; use atoi() like the surrounding timestamp parsing until
then.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Year 2038: server-link timestamps parsed with atoi() truncate at INT_MAX (protocol-neutral, fixable now)

1 participant