1. 개요
한 업데이트 리비전(rev248) 배포 직후, 다수 테스트 VM에서 에이전트가 전원 종료된 뒤 자동 복구되지 않는 사고가 발생했다. 수집본(한 테스트 VM의 ProgramData) 분석으로 원인을 확정했다: 업데이트 설치 직후 스토리지 급정지로 설치된 orange.exe의 일부 블록이 디스크에 기록되지 못했고(쓰기 유실), 손상된 바이너리가 시작 즉시 크래시하여 기존 복구 체계까지 무력화됐다. 이 글은 원인 분석 결과와 재발 방지 설계를 정리한다.
2. 원인 분석
타임라인 (KST)
| 시각 | 사건 |
|---|---|
| 오전 | 블루스크린 KERNEL_DATA_INPAGE_ERROR(0x7A), 상태 c000000e(장치 응답 없음). 에이전트 자체 분석기 분류 "disk" — 스토리지 계층의 I/O 실패가 이미 진행 중 |
| 11:20:52 | 1.6.242.248 패키지 다운로드 완료. 인스톨러 SHA256이 매니페스트와 일치 — 다운로드 구간은 무결 |
| 11:21:08~35 | 에이전트가 인스톨러(ORANGE.x64.exe) 실행, NSIS 압축 해제 진행 |
| 11:21:54 | 마지막 timeline 기록. 11:24:18 마지막 파일 기록을 끝으로 모든 기록 중단 — 설치 직후 급정지. DB WAL 미반영 잔존도 정황 일치 |
| +3일 | VM 재기동. 이후 10분 간격으로 orange.exe 크래시 덤프 반복(WER 보관 5개 제한으로 최근 5개만 잔존) |
손상 증거
- 설치된
orange.exe는 버전 리소스·PE 헤더·파일 크기 모두 정상이나, 코드 섹션 9.3MB 중 약 1MB가 원본과 불일치. 불일치는 파일 오프셋 4KB 경계에 정렬된 5개 연속 구간(각 132~260KB). - 손상 구간 내용은 원본 파일 어디에도 존재하지 않는 이질 데이터 → 잘못 쓴 것이 아니라, 쓴 내용이 디스크에 도달하지 못해 이전 클러스터 데이터가 남은 쓰기 유실(lost write) 패턴.
- 크래시 덤프 5개 모두 동일: 프로세스 시작 1~2초, 스레드 1개, 로더의 TLS(스레드 지역 저장소) 초기화 콜백 첫 실행에서 즉사(main 진입 전). C 런타임 잠금이 초기화되기 전이라 abort가 재귀하다 스택 고갈로 종료.
- 다운로드된 인스톨러는 해시 무결 확인 → 손상은 설치(압축 해제 쓰기) 구간에서 발생.
기존 복구 체계가 무력화된 이유
워치도그(10분 주기 예약 작업)는 서비스 정지 감지 시 orange.exe --recover를 실행한다. 복구 실행자가 손상 대상 자신이라 3일간 10분마다 크래시 덤프만 쌓였다. Good 버전 롤백 로직(CheckStabilityAtStart)은 main 진입 후 동작하므로 이번 손상 유형에서는 도달 자체가 불가능했다.
3. 재발 방지 설계 (확정)
3-1. 다운로드 전 여유 공간 확인
업데이트 파일 다운로드 직전 대상 드라이브를 GetDiskFreeSpaceEx로 확인. 필요량 = 파일 크기 + 여유분 256MB(설치 시 압축 해제 산출물 고려). 부족 시 로그를 남기고 해당 파일을 건너뛴다.
3-2. 설치 완료 마킹 강화 (인스톨러)
쓰기 완료 → FlushFileBuffers 반환값 확인 → 해시 재검증 → 통과 후에만 설치 완료 마킹·서비스 기동. 검증 전에 머신이 죽으면 다음 부팅에서 "완료 마크 없음"으로 감지된다.
3-3. 워치도그 자기완결 베이크 + Good 재설치
워치도그 게이트 스크립트를 등록 시점에 자기완결형으로 생성한다. 베이크 값: orange.exe 기대 해시(PACKAGE MainSHA256), Good 백업 설치본 경로와 해시. 런타임에 매니페스트·레지스트리를 참조하지 않아 동반 손상의 영향을 받지 않는다. 동작:
- 서비스 정지 감지 →
orange.exe해시 대조 - 일치하면 기존
--recover - 불일치하면 Good 설치본 해시·코드서명 검증 후
/S재설치 - 재설치 전 여유 공간 512MB 미만이면 이벤트로그 기록 후 정지(다음 10분 주기에 재평가)
최초 설치(orange.nsi)도 동일 512MB 게이트: UI 설치는 메시지박스+중단, 무인(/S) 설치는 로그+비정상 종료코드.
3-4. PACKAGE 매니페스트에 MainSHA256 라벨 필드 추가
최상위 MainSHA256·MainFastHash 필드로 코드사인 완료된 본체(orange.exe)의 해시를 명시한다. 기존 최상위 SHA256 배열은 라벨 없는 순서 관례라 소비 측 혼동 여지가 있어 명시 필드로 해소. 빌드 시점 생성 값이므로 "실행 중 즉석 계산 해시는 신뢰 불가" 문제도 함께 해소된다. 실측으로 서명 상태 Valid 본체 해시가 매니페스트에 실리는 것을 확인했다.
4. 변경 범위
| 파일 | 변경 내용 |
|---|---|
agent/include/CPackage.h | GetPackageFileInfo 본체 블록에서 MainSHA256·MainFastHash 생성, 재생성 대비 removeMember 추가. 다음 패키징 실행부터 자동 반영 |
agent/yagent21/CAgent2.cpp | 다운로드 직전 여유 공간 확인(파일 크기 + 256MB), 부족 시 건너뜀 |
agent/yagent21/CAgentRecover.h | 워치도그 게이트 스크립트에 기대 해시·Good 경로·Good 해시 베이크, 불일치 시 Good 재설치, 512MB 게이트, 이벤트로그 기록 |
agent/instrument/orange.nsi | 최초 설치 512MB 확인, 설치 완료 전 flush + 재해시 검증 |
전제: MakePackage 실행은 orange.exe 코드사인 이후여야 MainSHA256이 서명본 해시가 된다(현행 순서가 이미 충족).
5. 리뷰 포인트 (교차검증 반영 핵심)
- 베이크 갱신 순서: flush+재해시를 통과한 값만 베이크하고, 작업 등록 후 재조회로 반영 확인.
- 뮤텍스 직렬화: 워치도그 재설치도
Global\ORANGE_INSTALLER를 원자적으로 획득. 자식 인스톨러와의 소유권 전달 방식과 보유 시간 상한 정의 필요. - 재설치 트리거 확장: 해시 불일치 또는 연속 기동 실패 N회 — EXE는 정상인데 의존 DLL이 손상된 변종까지 커버.
- 무한 루프 차단: 서버가 계속 깨진 패키지를 제공하면 롤백→재업데이트→크래시 반복 가능. 버전별 실패 횟수 기록, 임계 초과 시 해당 버전 업데이트 보류.
- 복구 성공 판정:
/S프로세스 시작이 아니라 종료코드 + 설치 후 해시 재확인 + 서비스 Running까지 확인.
설치 직후 해시 검증은
FlushFileBuffers이후에만 의미가 있다. flush 전 검증은 파일 캐시를 읽으므로 이번 사고 같은 쓰기 유실을 탐지하지 못한다.
장기 과제: PowerShell 실행이 정책으로 차단되는 고객 환경에서는 예약 작업 기반 복구가 무력화된다. 독립 네이티브 복구 실행 파일로의 전환을 별도 검토한다.
Orange Platform 엔드포인트 에이전트의 업데이트 무결성·자가복구 사건 분석 리포트입니다.