압축 파일을 검사했는데, 설치기는 같은 파일을 보고 있을까

같은 종이 상자를 서로 다른 두 창으로 읽는 문서와 색 블록
같은 종이 상자를 서로 다른 두 창으로 읽는 문서와 색 블록
주제를 표현한 개념 삽화. Google Gemini 이미지 기능을 사용했습니다.

파일을 검사할 때 가장 먼저 떠올리는 것은 내용이 허용된 것인지다. 그런데 검사기가 읽은 내용과 나중에 설치하는 도구가 읽은 내용이 다르다면 어떨까. 같은 파일을 받았다는 사실만으로 같은 파일 목록을 해석했다고 말할 수는 없다. 검증과 사용 사이에 어떤 해석 규칙이 있는지까지 살펴야 한다.

Astral의 tar-codec 소개는 오래된 tar 형식의 여러 계열과 확장 규칙이 파서마다 다르게 처리될 수 있음을 설명한다. 팀은 형식 계열을 분리하고 읽기와 파일시스템 추출의 층을 나눈 설계를 소개한다. 이는 개발진의 공개 설명이며, 이 글에서 라이브러리를 실행하거나 안전성을 독립 감사한 결과는 아니다.

함께 읽은 공개 원문

이 글에서는 원문의 공격 예시나 바이트 배열을 재현하지 않는다. 대신 파일을 받는 서비스를 만드는 사람이 검사 단계와 실제 사용 단계의 계약을 어떻게 적을지 생각해 보려 한다. 특정 패키지를 쓰면 모든 문제가 사라진다는 결론보다, 지금 무엇을 비교하고 있는지 명확히 하는 것이 목적이다.

해시가 같다는 말 다음의 질문

가상의 내부 도구가 업로드된 자료 묶음을 검사한 뒤 다른 작업자에게 넘긴다고 해 보자. 전송 전후의 해시가 같다면 같은 바이트를 전달했다는 확인에 도움이 된다. 그러나 두 프로그램이 그 바이트를 서로 다른 규칙으로 읽는다면 해시 확인만으로 해석 차이는 드러나지 않는다.

비유하자면 같은 문장을 받았다는 것과 같은 뜻으로 읽었다는 것은 별개다. 파일 형식에도 어느 값이 우선하는지, 어느 부분이 다음 항목에 영향을 미치는지 같은 규칙이 있다. 원문은 tar의 확장 상태를 통해 그 복잡함을 보여 준다. 우리 서비스에서는 그 상태를 직접 모두 구현하기 전에 어떤 해석기를 쓰는지부터 확인할 수 있다.

검수 메모에는 원본의 식별값, 검사기 이름과 버전, 실제 소비자의 이름과 버전을 나란히 적는다. 두 단계가 같은 라이브러리를 쓴다는 말만으로 끝내지 않고 설정과 사용 방식도 확인한다. 같은 도구라도 목록만 보는 경우와 디스크에 쓰는 경우는 다음 행동이 다르기 때문이다.

예를 들어 검사 결과에 문서 두 개가 보였다고 기록했다면 실제 추출 뒤에도 기대한 경로의 두 문서가 있는지 확인할 항목이 필요하다. 여기서 숫자 둘은 설명용 가정이다. 경로 표기, 항목 종류, 중복 이름을 어떻게 취급하는지 정책이 없다면 무엇이 일치해야 하는지도 모호해진다.

읽어 낸 목록과 디스크에 남는 결과

자료 묶음의 목록을 읽는 과정과 그 목록을 실제 파일로 만드는 과정은 구분해서 생각할 수 있다. 목록에서 보인 이름을 그대로 경로로 사용하는지, 허용된 디렉터리 안에 결과가 남는지, 연결 항목이나 이미 있는 파일을 어떻게 처리하는지는 추출 단계의 질문이다.

작은 검토표에는 단계마다 입력과 허용 결과를 적어 볼 수 있다. 검사 단계는 어떤 항목을 받아들이고 무엇을 거절하는가. 추출 단계는 어느 위치에 어떤 종류의 파일을 만들 수 있는가. 다음 처리 단계는 그중 어떤 파일을 사용해도 되는가. 한 단계의 통과 표시를 뒤의 모든 행동에 대한 허락으로 넓히지 않는다.

거절한 입력을 성공처럼 처리하지 않는 것도 중요하다. 형식이 모호해서 읽지 못한 것과, 정상적으로 읽었는데 필요한 문서가 없는 것은 다른 결과다. 둘 다 빈 목록으로 기록하면 운영 중 왜 자료가 사라졌는지 구분하기 어렵다. 실패 이유를 남겨야 다음 사람이 안전하게 이어받을 수 있다.

다만 실제 공격 자료를 운영 서비스에 넣어 시험하자는 제안은 아니다. 승인된 격리 환경과 기존의 신뢰할 수 있는 테스트 자료, 공식 도구의 검증 방법 안에서 확인해야 한다. 민감한 파일이나 실제 고객 자료를 필요 없이 옮기지 않고도 경계와 기대 동작을 검토할 수 있다.

호환성을 넓히는 일에도 비용이 있다

여러 형식을 모두 받아들이면 사용자에게 편할 수 있다. 하지만 서로 다른 규칙을 섞어 대충 받아들이는 것이 항상 좋은 호환성은 아니다. 모호한 입력을 어느 프로그램은 받아들이고 어느 프로그램은 거절하면, 서비스 안에서 같은 자료의 뜻이 달라질 수 있다.

따라서 현재 필요한 형식과 오래된 예외를 구분해 기록하는 편이 좋겠다. 실제 이용 중인 자료를 확인하지 않은 채 지원 범위를 줄이면 정상 작업을 막을 수 있다. 반대로 근거 없는 예외를 계속 추가하면 검사기가 사실상 무엇을 보증하는지 설명하기 어려워진다. 바꿀 때에는 이전에 성공한 자료와 거절해야 할 자료를 함께 비교해야 한다.

패키지의 설치와 빌드는 또 다른 신뢰 문제다. 아카이브를 정확히 읽었다고 해서 그 안의 프로그램을 실행해도 된다는 뜻은 아니다. 형식 검증, 내용에 대한 정책, 실행 권한을 나누어 봐야 한다. 원문 역시 파서의 차이와 소스 배포의 실행 맥락을 구분하므로 이 부분을 섞어 읽지 않으려 한다.

이전 SQLite 글에서 장애의 모양을 먼저 정해 보았듯, 여기서도 ‘안전한 파일’이라는 큰 말 대신 어떤 결과를 허용하는지 적어 보는 것이 도움이 된다. 바이트가 보존됐는가, 해석이 일치하는가, 결과가 허용 위치에 남는가, 그 다음 실행은 따로 승인됐는가. 서로 다른 질문에 서로 다른 증거가 필요하다.

다음에 파일 검사 통과 표시를 볼 때에는 그 표시가 어느 단계에서 나온 것인지 한 번 더 확인하고 싶다. 검사한 자료와 실제 쓰는 자료가 같은 뜻으로 읽히고 있는가. 그 연결을 설명할 수 있어야 앞 단계의 꼼꼼함이 뒤 단계에서도 의미를 가질 것이다.

이어 읽기: SQLite를 여러 곳에 두기 전에 정해야 할 장애의 모양

공개 원문을 참고하고 AI의 도움으로 구성한 글입니다. 가상의 예시는 본문에 구분했으며 제품이나 원문의 실험을 직접 실행한 후기는 아닙니다.