flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
CVE-2021-25900: Rust smallvec 힙 버퍼 오버플로
SmallVec::insert_many는 size_hint 하한을 신뢰해 이터레이터가 더 많은 항목을 내놓으면 힙 버퍼 밖에 썼습니다. 0.6.14와 1.6.1에서 수정됐습니다.
로봇이 물건을 올리기 전에 모든 선반을 확인해서 안전하기로 이름난 창고가 있습니다. 몇몇 작업에서는 로봇을 끄고 사람이 조심하겠다고 약속한 뒤 손으로 일합니다. 그중 한 사람이 운전기사에게 "대략 몇 상자 가져왔어요?"라고 묻고, 그 크기로 선반을 만들어 쌓기 시작했습니다. 그 추정은 짐작일 뿐 보장이 아니었고, 약속보다 많은 상자가 오자 더미가 선반 끝을 넘어갔습니다.
핵심 개념 및 용어
SmallVec- 적은 수의 요소는 내부에 직접 보관하다가 커지면 힙 버퍼로 옮기는 Rust 컬렉션입니다. 그 속도는 버퍼를 손수 관리하는 unsafe 코드에서 나옵니다.
size_hint- 남은 항목 수의 추정 하한과 상한을 알려 주는 Iterator 메서드입니다. 힌트일 뿐이므로 안전한 코드는 틀린 값을 포함해 어떤 값이든 반환할 수 있습니다.
unsafe Rust- unsafe 블록 안의 코드는 원시 포인터를 통한 쓰기처럼 컴파일러가 확인할 수 없는 일을 할 수 있습니다. 작성자는 가능한 모든 안전한 호출자에 대해 메모리 안전성을 지켜야 합니다.
건전성(soundness)- 아무리 특이한 안전한 코드라도 어떤 API를 통해 정의되지 않은 동작을 일으킬 수 없다면 그 API는 건전합니다. insert_many는 정당한 size_hint로 메모리를 망가뜨릴 수 있었으므로 건전하지 않았습니다.
프레임워크 매핑
사고 타임라인
Yechan Bae와 조지아공대 SSLab의 Rust 그룹이 버그를 제보하고, RustSec이 RUSTSEC-2021-0003을 게시하며 smallvec 0.6.14와 1.6.1에 수정이 담깁니다.
CVE-2021-25900이 부여되고, cargo audit이 의존성 트리에서 영향받는 버전을 표시하기 시작합니다.
단계별 공격 실행 흐름
이터레이터가 길이를 적게 알린다
size_hint 하한이 실제로 내놓는 항목 수보다 작은 이터레이터로 insert_many를 호출합니다. 안전한 코드는 그렇게 해도 됩니다.
힌트에 따라 공간을 확보한다
insert_many는 하한만큼만 공간을 확보하고, 기존 요소를 그만큼 옮깁니다.
그래도 모든 항목을 쓴다
unsafe 블록 안에서 루프가 원시 포인터로 항목을 하나씩 기록하므로, 남는 항목은 할당된 영역의 끝 너머에 쓰입니다.
힙이 손상된다
오버플로가 인접한 힙 메모리를 덮어씁니다. 신뢰할 수 없는 입력의 영향을 받는 이터레이터를 insert_many에 넘기는 프로그램이 위험에 노출됩니다.
소스 코드 비교: 취약한 구현 vs 보안 패치
// *힌트*를 기준으로 할당하지만...
let (lower, _) = iter.size_hint();
self.reserve(lower);
unsafe {
let ptr = self.as_mut_ptr().add(index);
for (i, item) in iter.enumerate() {
// ...실제로 도착하는
// 항목은 모두 기록합니다.
ptr::write(ptr.add(i), item);
}
}// 항목마다 용량을 다시 확인하세요.
// 안전한 코드의 값이
// unsafe 코드에서 쓰는 한계를 정하게 해서는 안 됩니다.
for (i, item) in iter.enumerate() {
self.reserve(1);
unsafe {
let ptr = self.as_mut_ptr()
.add(index + i);
ptr::write(ptr, item);
}
}
// 실제 수정에서는
// unsafe 영역도 크게 줄였습니다.엔지니어링 및 시스템 보안 강화 체크리스트
- ✓unsafe 코드가 쓰는 버퍼의 크기를 size_hint, 트레이트 객체의 len, 비교자 같은 안전한 트레이트 메서드로 정하지 말고, 쓰기 전마다 용량을 다시 확인합니다.
- ✓모든 unsafe 블록에 의존하는 불변 조건을 적은 // SAFETY: 주석을 달고, 그 불변 조건이 신뢰할 수 없는 안전한 코드에 기대는 리뷰는 반려합니다.
- ✓size_hint가 거짓말하는 이터레이터, 패닉하는 Drop, 전순서가 아닌 비교자처럼 적대적인 구현을 쓰는 테스트를 CI의 Miri로 실행합니다.
- ✓unsafe 블록은 가능한 한 작게 유지합니다. smallvec의 수정도 검증할 수 있도록 insert_many의 unsafe 코드를 줄였습니다.
- ✓CI에서 cargo audit(또는 cargo deny)을 실행해, 0.6.14나 1.6.1 이전의 smallvec처럼 RustSec 권고가 있는 의존성이 있으면 빌드를 실패시킵니다.