풀이 날짜: 2026-09-15 (사용자 확정). 익스플로잇 성공은 2026-09-16 사용자 확인에 따른다. 아래 실행 결과는 보존된 원본 기록이며 이번 감사에서 새로 실행한 결과가 아니다.

잠금이 있어도 남는 객체 수명 오류

vuln_ioctl은 모든 요청을 global_lock으로 직렬화하고 인덱스를 검사한다. 그러나 슬롯에 저장된 포인터의 유효성은 잠금과 별개다. CMD_FREE 분기의 실제 코드는 다음과 같다.

if (!objs[idx]) {
    ret = -EINVAL;
    break;
}
kfree(objs[idx]);
// objs[idx] = NULL;

해제 후 NULL로 만드는 줄이 주석이므로, 다음 요청이 보는 슬롯에는 살아 있는 객체를 가리키지 않는 포인터가 남는다. 따라서 동시성 경쟁이 없어도 참조의 수명 계약이 깨진다.

상태 전이와 관찰의 구분

슬롯의 상태 FREE의 처리 이후 슬롯 상태
비어 있음 오류 반환 비어 있음
live 객체 참조 실제 객체 해제 dangling 포인터
dangling 포인터 non-NULL 검사를 통과할 수 있음 포인터의 현재 소유권은 보장되지 않음

ALLOC도 기존 슬롯의 소유권을 확인하지 않고 새 포인터를 대입한다. 최종 ANALYSIS는 다른 커널 객체와의 메모리 재사용을 별도로 분석했다. 연속 double-free가 hardened SLUB 검사에서 panic을 냈다는 관찰은 잘못된 상태 전이의 근거이지만, 그 자체를 최종 exploit 성공으로 부르지는 않는다.

무엇을 성공 증거로 보았는가

로컬 분석은 객체 수명 오류에서 비롯된 메모리 소유권 혼동과 지연된 정리 작업의 관찰을 구분한다. FLAG_READ_AUDIT는 단순한 console 문자열 대신 실제 파일 읽기 반환 길이와 출력 버퍼의 관계를 점검한다. 출력의 최대 길이를 읽기 결과에 맞춰야 하며, 우연히 뒤에 있던 NUL 바이트나 남은 버퍼 내용에 기대면 결과 해석이 잘못될 수 있다.

따라서 '읽기 호출이 끝남', '원하는 바이트를 읽음', '그 바이트가 console에 출력됨'은 별개의 확인 항목이다. 이는 전체 solver가 성공했는지 판정하는 데 필요한 검증 논리다.

원격 실패 기록의 정정

REMOTE_GAP_ANALYSIS는 Illegal instruction이 최종 단계 도달을 입증하지 않는다고 명시한다. 당시 여러 실패 경로가 같은 종료 형태를 만들 수 있었고, 로컬에서도 더 이른 검사 단계의 실패가 확인되었다. 같은 종료 메시지로 실패 위치를 단정한 해석을 폐기해야 한다.

주소 재사용에 대한 관찰, 지연 작업 실행 여부, callback 이후 결과는 각각 근거가 필요하다. 특히 syscall 성공만으로 예상 주소·내용의 일치까지 증명된 것은 아니라는 제한을 보존한다. 원격 실패·반환값 불인정 이력을 삭제하지 않는다.

결과와 identity

사용자는 2026-09-16에 익스플로잇 성공을 확인했다. GATES와 로컬 로그에는 성공 근거가 있지만, 보존된 실패 분석을 공식 원격 flag 수락 기록으로 바꾸지 않는다. 이번 감사는 기존 자료의 정적 대조이며 새 실행은 없다.

예시 flag 파일이 다른 문제와 같더라도 source와 challenge identity는 다르다. msgbox는 Aglio Olio 및 Miko와 별도의 레코드다. .lab 로그와 .analysis의 대용량 분석 자료도 staging에 보존한다.

근거 자료

  • sources/msgbox/vuln-src/vuln.c
  • sources/msgbox/ANALYSIS.md
  • sources/msgbox/FLAG_READ_AUDIT.md
  • sources/msgbox/REMOTE_GAP_ANALYSIS.md
  • sources/msgbox/GATES.md

원본 문서·solver·검증 로그는 전달 staging에 보존되어 있다. 이 경로들은 provenance이며 CMS의 다운로드 첨부 링크가 아니다.