macOSに潜んでいた49日間の時限爆弾 — TCPネットワーキングが完全停止するカーネルバグの全貌
(photon.codes)macOS XNUカーネルで、正確に49日17時間2分47秒連続稼働した後、TCPネットワーキングが完全に麻痺するバグが発見された。原因は、カーネル内部のTCPタイムスタンプカウンタ tcp_now の32ビット整数オーバーフローだ。ping は応答し続けるが、新しいTCP接続はまったく確立できなくなり、現時点で唯一の解決策は再起動である。
発見の経緯
Photon は iMessage サービスの状態を監視する Mac サーバーフリートを 24/7 で運用していた。2026年3月30日、最後の再起動から正確に49.7日が経過した時点で、複数のマシンが静かに新規TCP接続を拒否し始めた。ping は正常で、既存の接続も維持されていたが、新しいソケットを開くあらゆる試みが失敗した。
サービスを復旧するために再起動した後、チームは数日以内に同じしきい値へ到達するマシン2台(A、B)を選び、ライブ実験を設計した。
バグの技術的な仕組み
問題のカウンタ tcp_now
XNUカーネルの tcp_now は、起動後の経過時間をミリ秒単位で数える32ビット符号なし整数だ。32ビットで表現できる最大値は 4,294,967,295ms で、これを換算すると正確に49日17時間2分47秒になる。
なぜカウンタが「凍り付く」のか?
tcp_now の更新コードには、「時計が逆行しないように」する単純なガードがある:
if (tmp < current_tcp_now) {
os_atomic_cmpxchg(&tcp_now, tmp, current_tcp_now, ...);
}
オーバーフローの瞬間、新たに計算された current_tcp_now は 0 近辺へ巻き戻る一方、既存の tmp は最大値近辺にある。条件 tmp < current_tcp_now が永遠に falseになり、tcp_now はその値で停止したままになる。カーネルのTCPクロックが止まってしまうのだ。
TIME_WAIT が期限切れにならない理由
TCP接続が閉じられる際、カーネルは有効期限を tcp_now + 30秒 として記録する。ガベージコレクタが定期的にスキャンし、tcp_now >= 有効期限 であれば接続を解放する。ところが tcp_now が凍結すると、この条件が決して真にならず、TIME_WAIT 接続が永久に回収されなくなる。
実験結果
チームは、オーバーフロー前後それぞれ5分間、毎秒複数の短命TCP接続を生成しながら TIME_WAIT 数を観察した。
| 区間 | 状態 |
|---|---|
| オーバーフロー前 | TIME_WAIT は約200件で安定して維持(30秒ごとに正常に期限切れ) |
| オーバーフロー直後 | 期限切れが止まり、TIME_WAIT の単調増加が始まる |
| 接続生成停止から84秒後 | 0 になるはずの TIME_WAIT が逆に増加(2,828 → 2,837) |
| オーバーフロー後9.5時間 | Machine A: 4,888件、Machine B: 8,217件 — 1件も回収されず |
9.5時間後には SYN_SENT 状態の接続も 3,000件以上たまり、Machine B のロードアベレージは 49.74 まで急上昇した。
影響を受ける環境
一般消費者向けの Mac は、OSアップデートで49日より前に再起動することが多いため、影響は小さい。しかし、次の環境は高リスクだ:
- 長時間無停止のサーバーフリート
- macOS CI/CD ビルドサーバー(Jenkins、GitHub Actions セルフホストランナー)
- Mac Pro ワークステーション(レンダリング・コンパイルを長時間実行)
- リモート管理のコロケーション Mac
- Mac mini のビルドファーム・テストインフラ
現在および今後の対応
チームは現在、再起動せずに凍結した tcp_now を直接修正するワークアラウンドを開発中だ。それまでの暫定対策は1つだけである:
49日17時間2分47秒に達する前に再起動をスケジュールせよ。
類似する歴史的バグ
このバグは、長い系譜を持つ整数オーバーフローバグの一群に属する。Windows 95/98 の49.7日クラッシュ、2038年問題(Y2K38)、GPS週番号ロールオーバー、パックマン256面キルスクリーンはいずれも同系統だ。
9件のコメント
最近はmacOSも49日をきっちり迎えるんですね
wwwww
zzzzz
時刻の問題というと、Y2Kを思い出しますね.. 🤖..
人間は同じ過ちを繰り返します
49日になる前に、本当に再起動しないといけないんですね。
これは本来、時刻を絶対値の
<で比較してはいけないんですよね..if ((int32_t)(tmp - current_tcp_now) < 0) {
os_atomic_cmpxchg(&tcp_now, tmp, current_tcp_now, ...);
}
こうして2つの値の差を見るべきなのに... 人間はいつも同じミスをしてしまいますね.
こういうのを見ると、2038年には本当に大騒ぎになるのかもしれませんね
うわ、これは本当にありえないですね……。
AWS や GitHub の Mac インスタンスは、これまでどうして問題がなかったんでしょうか……?