FlawOpen

flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021

● servo/rust-smallvec · CVE-2021-25900 · January 2021 · CVSS 9.8 · 심각
보안 연구 · FlawOpen

CVE-2021-25900: Rust smallvec 힙 버퍼 오버플로

SmallVec::insert_many는 size_hint 하한을 신뢰해 이터레이터가 더 많은 항목을 내놓으면 힙 버퍼 밖에 썼습니다. 0.6.14와 1.6.1에서 수정됐습니다.

💡 알기 쉬운 설명 (ELI5)

로봇이 물건을 올리기 전에 모든 선반을 확인해서 안전하기로 이름난 창고가 있습니다. 몇몇 작업에서는 로봇을 끄고 사람이 조심하겠다고 약속한 뒤 손으로 일합니다. 그중 한 사람이 운전기사에게 "대략 몇 상자 가져왔어요?"라고 묻고, 그 크기로 선반을 만들어 쌓기 시작했습니다. 그 추정은 짐작일 뿐 보장이 아니었고, 약속보다 많은 상자가 오자 더미가 선반 끝을 넘어갔습니다.

핵심 개념 및 용어

SmallVec
적은 수의 요소는 내부에 직접 보관하다가 커지면 힙 버퍼로 옮기는 Rust 컬렉션입니다. 그 속도는 버퍼를 손수 관리하는 unsafe 코드에서 나옵니다.
size_hint
남은 항목 수의 추정 하한과 상한을 알려 주는 Iterator 메서드입니다. 힌트일 뿐이므로 안전한 코드는 틀린 값을 포함해 어떤 값이든 반환할 수 있습니다.
unsafe Rust
unsafe 블록 안의 코드는 원시 포인터를 통한 쓰기처럼 컴파일러가 확인할 수 없는 일을 할 수 있습니다. 작성자는 가능한 모든 안전한 호출자에 대해 메모리 안전성을 지켜야 합니다.
건전성(soundness)
아무리 특이한 안전한 코드라도 어떤 API를 통해 정의되지 않은 동작을 일으킬 수 없다면 그 API는 건전합니다. insert_many는 정당한 size_hint로 메모리를 망가뜨릴 수 있었으므로 건전하지 않았습니다.

프레임워크 매핑

  • 이 가이드에서 설명하는 약점의 CWE 참조: CWE-122 CWE-787

사고 타임라인

2021-01-08

Yechan Bae와 조지아공대 SSLab의 Rust 그룹이 버그를 제보하고, RustSec이 RUSTSEC-2021-0003을 게시하며 smallvec 0.6.14와 1.6.1에 수정이 담깁니다.

2021-01

CVE-2021-25900이 부여되고, cargo audit이 의존성 트리에서 영향받는 버전을 표시하기 시작합니다.

단계별 공격 실행 흐름

단계 1

이터레이터가 길이를 적게 알린다

size_hint 하한이 실제로 내놓는 항목 수보다 작은 이터레이터로 insert_many를 호출합니다. 안전한 코드는 그렇게 해도 됩니다.

단계 2

힌트에 따라 공간을 확보한다

insert_many는 하한만큼만 공간을 확보하고, 기존 요소를 그만큼 옮깁니다.

단계 3

그래도 모든 항목을 쓴다

unsafe 블록 안에서 루프가 원시 포인터로 항목을 하나씩 기록하므로, 남는 항목은 할당된 영역의 끝 너머에 쓰입니다.

단계 4

힙이 손상된다

오버플로가 인접한 힙 메모리를 덮어씁니다. 신뢰할 수 없는 입력의 영향을 받는 이터레이터를 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 영역도 크게 줄였습니다.

엔지니어링 및 시스템 보안 강화 체크리스트

출처