Map

macOS TCP Time Bomb: 49-Day Kernel Overflow

Wiki summarynetworkingmacoskernelbugs โ†ณ show in map Markdown
title
macOS TCP Time Bomb: 49-Day Kernel Overflow
type
summary
summary
XNU kernel's 32-bit tcp_now overflows after 49.7 days, freezing TIME_WAIT and killing TCP
tags
networking, macos, kernel, bugs
created
2026-04-07
updated
2026-04-07

Photon's team discovered that every Mac has a hidden expiration date. After exactly 49 days, 17 hours, 2 minutes, and 47 seconds of continuous uptime, a 32-bit unsigned integer overflow in the XNU kernel freezes the internal TCP timestamp clock. TIME_WAIT connections stop expiring, ephemeral ports exhaust, and eventually all new TCP connections fail. Ping still works. The only fix is a reboot.

The overflow

The XNU kernel tracks TCP time with tcp_now, a uint32_t incremented at millisecond granularity in calculate_tcp_clock():

current_tcp_now = (uint32_t)now.tv_sec * 1000 + now.tv_usec / TCP_RETRANSHZ_TO_USEC;

uint32_t tmp = os_atomic_load(&tcp_now, relaxed);
if (tmp < current_tcp_now) {
    os_atomic_cmpxchg(&tcp_now, tmp, current_tcp_now, relaxed);
}

2^32 milliseconds = 49.7 days. When the multiplication exceeds uint32_t max, the cast wraps current_tcp_now back to near zero. The monotonicity guard (if (tmp < current_tcp_now)) compares the old value (~4.29 billion) against the new wrapped value (~5,000). The comparison is false. The cmpxchg never fires. tcp_now freezes at its last pre-overflow value and never advances again.

Why TIME_WAIT breaks

When a TCP connection enters TIME_WAIT, the kernel records tcp_now + 30000 (2 ร— MSL, 30 seconds on macOS) as the expiration timestamp. The garbage collector checks TSTMP_GEQ(tcp_now, timer), which uses signed modular arithmetic: ((int)((a)-(b)) >= 0).

With tcp_now frozen, this check is permanently false for any connection created after the overflow โ€” its timer wrapped past uint32_t max to a small number, and frozen_value - small_number is a large negative. The connection never gets reclaimed.

The cascade

The failure is silent and progressive:

  1. Minutes: TIME_WAIT connections stop expiring. Low-traffic machines may not notice.
  2. Hours: TIME_WAIT accumulates into thousands. Ephemeral ports (49152โ€“65535, ~16K on macOS) start running out.
  3. Port exhaustion: New outbound connections can't bind a port. They enter SYN_SENT and fail. Existing ESTABLISHED connections keep working.
  4. System load spikes: The kernel burns CPU scanning the ever-growing TIME_WAIT queue. One test machine hit load 49.74.
  5. TCP is dead: Only ICMP works because it doesn't use TCP ports or the TCP timer subsystem.

No kernel panic, no error log, no crash report. The system looks healthy until TCP stops.

Live reproduction

Photon ran a controlled experiment on two Mac machines approaching the 49.7-day mark in their iMessage monitoring fleet. They generated ~15 short TCP connections every 2 seconds before and after the overflow.

Before overflow: TIME_WAIT reached dynamic equilibrium at ~200 (7.5 connections/sec ร— 30s lifetime). Creation and reclamation in balance.

After overflow: TIME_WAIT climbed monotonically. Machine B hit 2,828 at script stop, 8,217 nine hours later. Not a single connection was ever reclaimed. Machine B's load average spiked to 49.74 with 3,315 SYN_SENT connections stuck at the first step of the handshake.

The integer overflow family

This bug belongs to a well-known class:

  • Windows 95/98 โ€” 32-bit millisecond tick counter overflowed at 49.7 days, causing system hangs
  • Y2K38 โ€” Unix signed 32-bit time_t overflows January 19, 2038
  • GPS Week Number Rollover โ€” 10-bit counter overflows every 19.7 years
  • Pac-Man kill screen โ€” 8-bit level counter overflows at 256

The pattern: code assumes a counter only goes up, doesn't handle the wrap. Works for years. Fails at the exact mathematical boundary.

Who's affected

Any Mac with >49.7 days uptime and any TCP traffic. Most consumer Macs reboot within that window for system updates. High-risk scenarios:

  • Long-running server fleets (Mac minis, Mac Pros)
  • CI/CD build servers (Jenkins, self-hosted GitHub Actions runners)
  • Colocated Macs managed remotely
  • Workstations running long renders or simulations

Community reports

Multiple prior reports match the symptoms exactly โ€” TCP fails, ICMP works, reboot fixes it, happens after weeks of uptime โ€” but none identified the root cause:

  • Apple Community threads #250867747 and #252991075
  • Podman issue #12495 (macOS 12 VM losing TCP after extended uptime)

The article traces the bug to a specific line in XNU's bsd/netinet/tcp_var.h and bsd/netinet/tcp_timer.c, with the full causal chain from microuptime() through calculate_tcp_clock() to tcp_gc().

Related: tcp-congestion-control covers TCP internals. The TIME_WAIT mechanism and MSL values discussed here are part of the same TCP state machine that 13kb-tcp-slow-start touches on from the congestion control angle.