# macOS TCP Time Bomb: 49-Day Kernel Overflow

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()`:

```c
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.
