Rust, 메모리 안전성은 보장하지만, 시스템 프로그래밍의 모든 위험을 막을 순 없다.
Rust는 메모리 안전성을 제공하지만, uutils 프로젝트에서 44개의 CVE가 발생하며 한계를 드러냄
주요 원인으로 TOCTOU(Time-of-Check to Time-of-Use) 취약점, 바이트 처리 오류, 오류 처리 부주의 등이 지적됨
GNU coreutils의 테스트 스위트(Test Suite)를 활용한 테스트 부재와 기존 Unix API에 대한 이해 부족이 문제로 제기됨
Rust의 안전성은 메모리 안전성(Memory Safety)에 국한되며, 시스템 프로그래밍의 모든 취약점을 해결하지 못함
TOCTOU(Time-of-Check to Time-of-Use) 취약점과 파일 시스템 API
uutils 프로젝트에서 발생한 다수의 버그는 TOCTOU(Time-of-Check to Time-of-Use) 취약점과 관련이 있다. 이는 파일 경로를 확인하는 시점과 해당 경로에 접근하는 시점 사이에 공격자가 파일 시스템을 조작하여 발생하는 문제이다. 특히, Rust의 `std::fs` API가 파일 디스크립터(File Descriptor) 대신 경로 기반의 API를 사용함으로써 이러한 취약점에 더 쉽게 노출될 수 있다는 점이 지적된다. 파일 디스크립터 기반의 API를 활용하여 이러한 위험을 줄여야 한다.
바이트(Byte) 처리와 유닉스(Unix) 시스템의 경계
유닉스 시스템에서 파일 경로, 환경 변수, 명령어 인자 등은 바이트(Byte) 단위로 처리된다. Rust의 `String`과 `&str`은 UTF-8 인코딩을 기본으로 사용하므로, 바이트 데이터를 처리할 때 변환 과정에서 데이터 손실이나 오류가 발생할 수 있다. uutils 프로젝트에서는 이러한 바이트 처리의 중요성을 간과하여, `from_utf8_lossy`를 사용하여 데이터 손실을 유발하거나, `unwrap`을 사용하여 프로그램의 실행을 중단시키는 오류를 발생시켰다. OsStr, &[u8]과 같은 바이트 기반의 타입을 사용하여 이러한 문제를 해결해야 한다.
오류 처리 부주의와 치명적인 결과
Rust 코드에서 `unwrap`, `expect`와 같은 함수를 사용하여 오류를 처리하지 않고 무시하는 경우, 예상치 못한 입력에 의해 프로그램이 종료될 수 있다. 특히, CLI(Command Line Interface) 환경에서는 이러한 오류가 서비스 거부(Denial of Service) 공격으로 이어질 수 있다. uutils 프로젝트에서는 `sort --files0-from` 명령어에서 UTF-8 인코딩 오류로 인해 프로그램이 종료되는 사례가 발생했다. 오류 정보를 적절히 처리하고, 예외 상황에 대한 대비가 필요하다.
GNU Coreutils 테스트 스위트(Test Suite)의 중요성
uutils 프로젝트는 GNU coreutils의 Rust 재구현을 목표로 했지만, 기존 GNU coreutils의 테스트 스위트를 활용하지 않아, 기존 도구의 동작 방식을 정확히 모방하지 못하는 버그가 다수 발생했다. 특히, `kill -1` 명령어의 동작 방식이 GNU coreutils와 달라 시스템 전체에 영향을 미치는 치명적인 오류로 이어졌다. 기존 도구의 테스트 스위트를 활용하여, bug-for-bug 호환성을 확보하는 것이 중요하다.
Rust의 안전성과 시스템 프로그래밍의 한계
Rust는 메모리 안전성을 보장하여 버퍼 오버플로우, use-after-free, 데이터 레이스(Data Race)와 같은 메모리 관련 취약점을 방지하지만, 시스템 프로그래밍의 모든 문제를 해결하지는 못한다. uutils 프로젝트에서 발생한 버그들은 Rust의 안전성 범위를 벗어나는, 파일 시스템 API, 바이트 처리, 오류 처리 등과 관련된 문제였다. Rust의 강점과 한계를 명확히 인식하고, 시스템 프로그래밍의 특성을 고려하여 코드를 작성해야 한다.