Endcrawl에서 프로세스 그룹을 종료하려고 실행한 sudo kill -TERM -1490947
이 init을 제외한 모든 프로세스를 종료 함. Ubuntu 24.04 LTS의 kill
이 음수 PID를 -1
로 잘못 해석한 것이 원인이었음
지난 10여 년간 음수 PID 처리 버그 네 종류 가 Ubuntu 14, 16, 18, 22, 24 LTS에 포함됐으며, 한 오류를 고치는 과정에서 다른 오류가 발생하거나 과거 오류가 되살아남
핵심 문제는 getopt
의 옵션 묶음 처리 와 kill
의 인자 문법이 충돌한다는 점임. 한 자리와 여러 자리 음수 PID에서 인자 소비 시점이 달라져 무동작, 전체 종료, 세그멘테이션 오류, 셸 종료로 이어짐
kill
에는 신호 이름, 신호 번호, 음수 프로세스 그룹 ID가 함께 쓰이므로, 인자 위치에 따른 파싱 으로 옵션 묶음과 재정렬을 배제하는 편이 더 단순한 해법임
--
로 옵션과 PID를 구분 하고 최소 권한으로 실행 하는 것이 권장됨. 문제가 있는 버전에서도 이 구분자는 올바르게 처리되며, 공개 시점에는 Ubuntu 24용 수정도 진행 중임
렌더링 취소가 전체 서비스 장애로 번진 과정
2026년 9월 24일 아침, Endcrawl 에서 대형 7k 테스트 렌더링 을 취소하려고 sudo kill -TERM -1490947
을 실행하자 모든 서비스가 즉시 중단됨
개별 프로세스를 종료해 다른 프로세스의 실패를 유도하는 대신, 관련 프로세스 그룹 전체에 동시에 신호를 보내려던 작업이었음
첫 장애를 수습한 뒤 두 번째 장애도 발생했으며, 원인을 이해하지 못하면 재발을 막을 수 없다는 판단으로 다른 일을 멈추고 사후 분석에 착수함
원인은 오타나 애플리케이션의 신호 처리 오류가 아니라 kill(1)
의 버그 였음
약 50년 된 도구이고 명세 도 단순하고 안정적인데, 조사 결과 여러 Ubuntu LTS에 걸친 연속적인 오류가 드러남
음수 PID와 프로세스 그룹 신호
프로세스 그룹 ID (PGID) 는 서로 관련된 프로세스 묶음을 식별함. kill
에 양수를 주면 단일 프로세스에, 음수를 주면 해당 그룹의 모든 프로세스에 신호를 보냄
프로세스 그룹 신호는 드문 기능이 아님
Ctrl+C
를 누르면 터미널이 포그라운드 프로세스 그룹에 SIGINT
를 보냄
대화형 셸은 가장 단순한 명령을 제외하면 새 프로세스 그룹에서 명령을 실행하며, 이는 작업 제어 )도 가능하게 함
daemontools에 추가된 그룹 신호 기능 은 svc -+h
로 다중 프로세스 서비스를 재시작할 때 고아 프로세스를 피하도록 함
Ubuntu 24.04에서 전체 종료가 발생한 이유
Ubuntu 24.04 LTS의 /bin/kill
은 procps 4.0.4 가 제공함. 문제는 정수 오버플로나 언더플로가 아니라, 인식하지 못한 옵션을 처리하는 코드에 있었음
getopt(3)
는 알 수 없는 옵션을 만나면 ?
를 반환하고 해당 문자 하나를 optopt
에 저장 함
-1490947
을 처리할 때 optopt
에는 전체 숫자가 아니라 첫 문자 '1'
만 들어감
코드의 pid = (long)('0' - optopt)
는 이를 -1
로 변환함
kill(-1, sig)
은 호출자가 신호를 보낼 권한이 있는 모든 프로세스에 신호를 보내되 PID 1인 init은 제외함
명령을 root로 실행했기 때문에 의도한 렌더링 그룹이 아니라 init을 제외한 모든 프로세스가 종료됨
음수 PID 지원과 procps의 변화
Unix에는 1973년까지 kill(1)
이 생겼지만, 지금과 같은 프로세스 그룹이 발전하는 데는 시간이 더 걸렸으며 1986년 Unix V9 에서 음수 PID를 통한 그룹 종료가 가능해짐
확인한 범위에서는 BSD 계열의 음수 PID 종료는 그동안 정상 동작해 왔음
Linux는 초기에 BSD 사용자 공간 코드를 많이 가져왔지만, 1990년대 후반 법적 문제로 도구들을 다시 작성해야 했음
음수 PID 처리는 2013년까지는 정상적으로 작동했던 것으로 보임. 이후 기존의 argv
직접 파싱을 getopt
로 전환 하면서 문제가 시작됨
네 종류의 버그와 Ubuntu LTS별 동작
Ubuntu 14.04 LTS / procps 3.3.9: 한 자리 음수 PID를 처리하지 못함
kill -HUP -1
을 실행해도 아무 일도 일어나지 않음
원인은 getopt
의 옵션 묶음 처리 임. -123
은 -1
, -2
, -3
처럼 처리되며, 마지막 문자까지 읽어야 optind
가 다음 인자로 이동함
여러 자리 음수에서는 첫 숫자에서 파싱 루프를 빠져나와도 전체 인자가 남아 PID 처리 코드로 전달됨
한 자리 음수에서는 이미 인자가 소비되어 optind
가 이동했으므로, PID 처리 코드가 받을 인자가 없어짐
Ubuntu 16.04 LTS / procps 3.3.10: 여러 자리 음수 PID가 전체 종료로 이어짐
한 자리 음수 처리 문제를 고치면서 optopt
의 숫자 하나로 PID를 만드는 코드가 들어감
그 결과 kill -TERM -12345
가 -1
로 처리 됨. Ubuntu 24에서 장애를 일으킨 것과 같은 버그임
이 코드가 Ubuntu 24 시기에 다시 등장한 정확한 이유는 확인되지 않음
Ubuntu 18.04 LTS / procps 3.3.12: 한 자리 음수 PID에서 세그멘테이션 오류가 발생함
여러 자리 음수를 고치기 위해 optopt
대신 atoi(argv[optind])
로 전체 인자를 읽도록 변경함
여러 자리 음수는 정상 처리되지만, kill -TERM -1
에서는 이미 인자를 소비한 optind
가 인자 배열 끝을 넘어가 세그멘테이션 오류가 발생함
Ubuntu 22.04 LTS / procps 3.3.17: 여러 자리 음수 PID가 셸을 종료함
배열 끝을 넘어가지 않도록 atoi(argv[optind-1])
로 바꾸면서 한 자리 음수는 정상 처리됨
그러나 kill -TERM -123
에서는 옵션 묶음 때문에 optind
가 아직 증가하지 않았고, 앞선 신호 인자 제거 처리까지 거쳐 argv[optind-1]
이 문자열 "kill"
을 가리킴
atoi(3)
는 숫자가 아닌 "kill"
을 0
으로 변환하며, PID 0에 대한 kill
시스템 호출은 호출자의 프로세스 그룹 전체에 신호를 보냄
이 단순 명령을 셸이 별도 프로세스 그룹으로 분리하지 않아, 예상치 못하게 셸까지 종료됨
kill
에는 위치 기반 파싱이 더 적합한 이유
optind
는 묶음의 마지막 옵션을 처리할 때만 증가 하며, 이 동작은 직관적이지 않음. 관련 코드 기여자들이 이를 이해했다는 근거를 찾지 못했고, 장애 조사에서도 면밀한 검토가 필요했음
놀라운 동작을 피하는 것이 바람직하지만, getopt
에서는 옵션 묶음 처리를 끌 수 없음
kill
은 -KILL
같은 신호 이름, -9
같은 신호 번호, 음수 PGID 를 모두 받는 특수한 도구임
인자 어디든 옵션이나 PID로 볼 수 있다면 kill -1 -9
는 PGID 9에 SIGHUP
을 보내는 명령으로 읽힐 수 있음
반대로 -1
대상 전체에 SIGKILL
을 보내거나, 두 음수 대상을 기본 신호 SIGTERM
으로 처리하는 해석도 가능해짐
해법은 인자의 위치에 따라 argv
를 파싱 하고, 이 문맥에서 실수 가능성을 높이는 옵션 묶음과 옵션 재정렬을 포기하는 것임
POSIX 명세의 신호 지정 위치와 이후 PID/PGID 인자 구분을 따르면 파싱을 단순화할 수 있음
FreeBSD의 kill.c
는 전통적인 Unix처럼 위치 기반 파싱을 사용하며, Linux의 kill
과 달리 명령줄 파싱 버그가 없었음
근본 원인은 getopt
를 반드시 써야 한다는 접근 에 있음. 일반적인 명령줄 파싱 표준화가 유용하더라도 kill
에는 적합하지 않을 수 있음
procps도 이미 argv
를 직접 수정하고 있음
procps는 getopt_long
을 호출하기 전에 skill_sig_option
으로 인자 배열을 직접 순회함
-KILL
, -9
처럼 처음 발견한 신호 지정 인자를 찾음
memmove
로 해당 인자를 배열에서 제거하고 인자 수를 줄여, getopt
가 그 인자를 처리하지 않게 함
신호를 찾지 못하면 기본값인 SIGTERM
을 사용함
따라서 직접적인 argv
파싱 은 이미 구현의 일부이며, 위치 기반 파싱으로 단순화하는 작업은 완전히 새로운 접근을 도입하는 것이 아님
사용자가 적용할 수 있는 방어책
--
로 옵션과 PID/PGID를 구분 해야 함
kill -TERM -1490947
대신 kill -TERM -- -1490947
을 사용함
후자는 앞서 나열한 버그가 있는 procps 버전에서도 올바르게 파싱됨
최소 권한으로 신호를 보내는 것 이 권장됨
당시 sudo kill
대신 sudo -u render kill
을 사용했다면, 머신 전체가 아니라 진행 중인 렌더링만 종료됐을 것임
작고 오래됐으며 널리 쓰이는 중요한 도구라도, 도구 자체의 버그에 대비한 방어적 사용이 필요했던 사례임
공개 시점에는 Ubuntu 24용 버그 수정 이 진행 중임
개별 수정과 별개로, procps의 kill
을 더 단순한 구조로 다시 작성하는 것이 권장됨
댓글과 토론