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 무한 지속
  • 세 단계로 구성된 테스트 스크립트 실행
    1. 모니터링 단계: 오버플로 35분 전부터 5분 전까지 TIME_WAIT 수를 10초 간격으로 기록
    2. 폭발 단계: 오버플로 전후 10분 동안 2초마다 약 15개의 짧은 TCP 연결 생성
    3. 관찰 단계: 연결 생성 중단 후 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를 복구하는 우회 해결책을 개발 중임
Read Entire Article