macOS에서 49.7일 후 TCP 네트워킹이 중단되는 버그 발견
6 hours ago
3
- macOS의 TCP 타임스탬프 카운터(tcp_now) 가 부팅 후 약 49.7일이 지나면 32비트 오버플로로 인해 내부 TCP 시계가 정지함
- 이로 인해 TIME_WAIT 상태의 연결이 만료되지 않고 누적되어 임시 포트가 해제되지 않음
- 시간이 지나면 임시 포트 고갈로 인해 새로운 TCP 연결이 모두 실패하고, 기존 연결만 유지됨
- ICMP(ping)은 정상 작동하지만, TCP 전체 기능이 마비되어 재부팅 외에는 복구 불가
- 장기 가동되는 macOS 서버·빌드 머신·CI 환경은 49일 17시간 주기로 이 문제에 노출되며, 커널 수정 전까지 주기적 재부팅 필요
배경: TCP의 기본 개념
- TCP 연결은 종료 시 즉시 사라지지 않고 TIME_WAIT 상태에 들어가며, 이는 지연된 패킷 처리와 신뢰성 있는 종료를 위한 단계임
- 오래된 패킷이 새 연결로 잘못 해석되는 것을 방지하고, 마지막 ACK 손실 시 재전송을 처리하기 위함
- TIME_WAIT 지속 시간은 2 × MSL(Maximum Segment Lifetime) 로 정의되며, macOS에서는 약 30초로 설정되어 있음
-
MSL은 TCP 세그먼트가 네트워크에서 생존할 수 있는 최대 시간으로, RFC 793에서는 2분으로 정의되었으나 현대 시스템에서는 훨씬 짧게 설정됨
-
32비트 부호 없는 정수 오버플로는 값이 최대치(4,294,967,295)를 넘으면 0으로 되돌아가는 현상이며, macOS의 TCP 타임스탬프(tcp_now)는 부팅 이후 밀리초 단위로 증가하는 32비트 카운터로, 49일 17시간 2분 47.296초 후 오버플로 발생
발견: 49.7일 후 TCP 연결 중단 현상
- Photon의 iMessage 모니터링용 Mac 서버들이 24/7로 운영되며, 2026년 3월 30일 부팅 후 정확히 49.7일이 지난 시점에 새 TCP 연결이 모두 실패하는 현상 발생
- 기존 연결과 ICMP(ping)는 정상 작동했으나, 새로운 TCP 소켓 생성이 불가능
- 원인은 XNU 커널의 TCP 타임스탬프 카운터(tcp_now) 오버플로로, 단조 증가 검증 로직이 wraparound 이후 갱신을 차단하여 내부 TCP 시계가 정지
- TIME_WAIT 연결이 만료되지 않아 임시 포트가 해제되지 않고 누적, 결국 재부팅 외에는 복구 불가
- 재부팅 후 동일한 현상이 49.7일 주기로 반복됨
실험 설계: 오버플로 전후의 TCP 동작 비교
- 가설: 오버플로 이후 TIME_WAIT 가비지 컬렉션이 멈춘다면, 오버플로 전후의 단기 TCP 연결 생성 패턴에 차이가 나타남
- 오버플로 전: TIME_WAIT 30초 후 정상 만료
- 오버플로 후: TIME_WAIT 무한 지속
- 세 단계로 구성된 테스트 스크립트 실행
-
모니터링 단계: 오버플로 35분 전부터 5분 전까지 TIME_WAIT 수를 10초 간격으로 기록
-
폭발 단계: 오버플로 전후 10분 동안 2초마다 약 15개의 짧은 TCP 연결 생성
-
관찰 단계: 연결 생성 중단 후 TIME_WAIT 변화를 모니터링
결과: 오버플로 이후 TIME_WAIT 정체
- 오버플로 전에는 TIME_WAIT 수가 0~200 사이에서 안정적으로 순환하며 정상적인 회수 동작 확인
- 오버플로 직후부터 TIME_WAIT 수가 계속 증가하며, 더 이상 만료되지 않음
- Machine B의 경우 2,828개의 TIME_WAIT 연결이 84초 후에도 하나도 회수되지 않았고, 이후에도 지속적으로 누적
- Machine A 또한 수동 확인 결과 TIME_WAIT 수가 단조 증가, 복구 불가 상태
근본 원인: XNU 커널의 tcp_now 32비트 오버플로
-
tcp_now는 bsd/netinet/tcp_var.h에 정의된 밀리초 단위 32비트 카운터로, 부팅 이후 경과 시간을 추적
-
calculate_tcp_clock() 함수에서 (uint32_t)now.tv_sec * 1000 연산이 49.7일 이후 최대값을 초과하며 wraparound 발생
-
if (tmp < current_tcp_now) 조건문으로 인해, 오버플로 시 기존 값이 새 값보다 커져 갱신이 차단되고 tcp_now가 영구 정지
- TIME_WAIT 만료 검사는 tcp_now를 기준으로 수행되므로, 시계가 멈추면 만료 조건이 항상 거짓이 되어 회수 불가
연쇄 효과: TCP 전체 기능 정지로 확산
-
수 분 후: TIME_WAIT 회수 중단, 단기 연결이 많은 워크로드에서 점진적 문제 발생
-
수 시간 후: TIME_WAIT 수천 개 누적, 임시 포트 고갈
-
포트 고갈 후: 새로운 TCP 연결이 SYN_SENT 상태에서 실패, 기존 연결만 유지
-
CPU 부하 급증: 커널이 TIME_WAIT 큐를 계속 스캔하며 부하 증가
-
결과적으로 TCP 완전 마비, ICMP만 정상 작동
- 유일한 복구 방법은 재부팅, 이후 다시 49.7일 카운트 재시작
추가 증거 및 관련 사례
-
RFC 7323은 1ms 단위 32비트 타임스탬프의 부호 비트 래핑이 약 24.8일마다 발생함을 명시
- macOS의 경우 전체 32비트 오버플로(49.7일)로, RFC에서 다루는 원격 타임스탬프 문제와는 별개의 로컬 커널 결함
- Apple 커뮤니티 및 오픈소스 프로젝트에서 동일 증상 다수 보고
- TCP 연결 불가, ping 정상, 재부팅만 해결, 수주간 가동 후 발생
- Podman issue #12495 등에서 동일한 패턴 확인
- 공통점: TCP만 실패, ICMP 정상, 재부팅 필요, 수주 단위 발생 주기
영향 범위
-
49일 17시간 이상 연속 가동된 macOS 시스템에서 발생 가능
- 일반 사용자는 주기적 업데이트로 재부팅되어 영향 적음
- 고위험 환경
- 장기 가동 서버 플릿
- macOS 기반 CI/CD 빌드 서버
- Mac Pro 워크스테이션
- 원격 관리형 코로케이션 Mac
- 빌드 팜·테스트 인프라용 Mac mini 클러스터
재현 절차
- 부팅 시각으로부터 오버플로 예상 시점 계산
- 오버플로 전후 TIME_WAIT 수를 모니터링
- 오버플로 시점에 다수의 짧은 TCP 연결 생성
- 2분 후 TIME_WAIT 수가 감소하지 않으면 버그 재현 성공
9.5시간 후 관찰된 시스템 상태
- TIME_WAIT 연결이 단 한 개도 회수되지 않고 지속 증가
- SYN_SENT 상태의 실패 연결이 3,000개 이상 누적
- 기존 연결만 유지되고 신규 연결 불가
- Machine B의 평균 부하가 49.74까지 상승, 커널이 TIME_WAIT 큐 스캔에 과도한 CPU 사용
결론
- 단 하나의 32비트 정수와 if (tmp < current_tcp_now) 조건문이 49.7일 후 TCP 전체를 정지시키는 시한폭탄으로 작동
- 개발·테스트·코드 리뷰 단계에서는 발견되기 어려운 유형의 결함이며, 실제 운영 환경에서만 드러남
- Photon은 여러 서버에서 동일 현상을 재현했고, 오버플로 전에는 정상 회수, 이후에는 TIME_WAIT 누적이 명확히 확인됨
-
tcp_now가 멈추면 커널의 TCP 시계가 정지하며, 시스템은 겉보기엔 정상이나 TCP 포트가 모두 소진됨
- 장기 가동 macOS 시스템 관리자는 49일 17시간 2분 47초를 기억해야 하며,
재부팅 주기 조정 또는 커널 수정 전까지 주기적 재부팅이 필요
- Photon은 현재 재부팅 없이 tcp_now를 복구하는 우회 해결책을 개발 중임
-
Homepage
-
Tech blog
- macOS에서 49.7일 후 TCP 네트워킹이 중단되는 버그 발견