RustGo: Fairly Directed Greybox Fuzzing for Enforcing Rust Memory Safety
RustGo는 Rust 특화 정적 분석을 활용하여 잠재적인 메모리 버그 타겟을 자동으로 식별하고 무관한 실행 경로를 제거함으로써 기존 방식에 비해 퍼징 효율성을 크게 향상시키고 unsafe Rust 코드에서 알려지지 않은 취약점을 발견하는 새로운 지향성 그레이박스 퍼저입니다.
원본 논문은 CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/) 라이선스로 제공됩니다. 이것은 아래 논문에 대한 AI 생성 설명입니다. 저자가 작성하거나 승인한 것이 아닙니다. 기술적 정확성을 위해서는 원본 논문을 참조하세요. 전체 면책 조항 읽기
당신이 거대하고 북적이는 도시 속에서 단 하나의 아주 작은 결함을 찾으려는 형사라고 상상해 보십시오. 컴퓨터 소프트웨어의 세계에서 이 도시는 하나의 프로그램이며, 그 결함은 '메모리 버그(memory bug)'입니다. 이는 프로그램이 허용되지 않은 데이터를 잡거나, 사용하거나, 버리려고 시도하여 충돌을 일으키거나 해킹을 당하게 만드는 실수입니다. 수십 년 동안 이러한 버그를 찾는 가장 대중적인 방법은 '퍼징(fuzzing)'이었습니다. 퍼징을 도시 벽에 수백만 개의 무작위적이고 혼란스러운 눈덩이를 던져서 어떤 눈덩이가 창문을 깨뜨리는지 확인하는 것이라고 생각해 보십시오. 보통 이 방식은 오래된 언어들에는 효과적이지만, 한 가지 문제가 있습니다. 매우 비효식적이라는 점입니다. 당신은 뒷골목의 약한 창문 하나를 찾기 위해 튼튼하고 부서지지 않는 정문에 99개의 눈덩이를 던져야 할 수도 있습니다.
여기에 Rust가 등장합니다. Rust는 초정밀 보안 요원처럼 행동하는 현대적인 프로그래밍 언어입니다. Rust의 규칙은 매우 뛰어나서, 이 언어가 구축한 도시의 90%는 사실상 방탄 수준입니다. 보안 요원이 대부분의 '창문'(메모리 영역)을 기본적으로 꽉 잠가두기 때문입니다. 하지만 특정 복잡한 것들을 만들기 위해, Rust는 개발자에게 'unsafe code'라고 불리는 작고 특별한 문을 열 수 있도록 허용합니다. 이곳은 엄격한 규칙이 완화되는 유일한 곳이며, 메모리 버그가 실제로 숨어 있을 수 있는 유일한 장소이기도 합니다. 문제는 기존의 '눈덩이 던지기' 도구들이 이를 모른다는 점입니다. 그들은 이미 안전한 코드인 90%의 영역에 계속해서 눈덩이를 던지며 시간과 에너지를 낭비하고, 정작 위험한 지점인 특별한 문 뒤쪽은 놓치고 맙니다. 이 논문은 정확히 어디로 눈덩이를 던져야 할지 아는 새로운 탐정 도구인 RustGo를 소개합니다.
스마트한 탐정: RustGo
이 논문의 연구진인 고려대학교와 기타 기관의 팀은 Rust 프로그램에 무작위로 눈덩이를 던지는 것이 마치 건불을 통째로 태워버려 바늘을 찾으려는 것과 같다는 점을 깨달았습니다. Rust는 대부분의 코드에 대해 안전성을 보장하므로, 그들은 다음과 같이 질문했습니다. 왜 안전한 부분을 테스트해야 하는가? 그들은 눈을 가린 채 돌을 던지는 사람이 아니라, 지도와 돋보기를 가진 탐정처럼 행동하는 '지향성 그레이박스 퍼저(directed greybox fuzzer)'인 RustGo를 구축했습니다.
RustGo는 버그를 효율적으로 추적하기 위해 세 가지 영리한 단계를 거칩로 작동합니다:
1. 적절한 타겟 찾기 (지도)
먼저, RustGo는 버그가 숨어 있을 수 있는 'unsafe' 구역을 스캔합니다. 하지만 단순히 추측하지 않습니다. 데이터가 프로그램을 통해 어떻게 흐르는지 분석하는 특수한 종류의 분석을 사용합니다. 도시에는 많은 골목이 있지만, 어떤 골목은 막다른 길로 이어지거나 실제로는 절대 일어나지 않는 루프를 포함하고 있습니다. RustGo는 이러한 것들을 걸러냅니다. 이 도구는 메모리 버그가 발생할 수 있는 특정 지점을 식별한 다음, 유사한 지점들을 하나로 합칩니다. 만약 코드의 두 서로 다른 경로가 정확히 동일한 위험한 결과로 이어진다면, RustGo는 이를 하나의 타겟으로 취급하여 같은 것을 두 번 확인하며 시간을 낭비하지 않습니다. 테스트에서 이 단계는 확인해야 할 타겟의 수를 약 84% 줄여, 정말 의심스러운 영역에만 집중할 수 있게 도왔습니다.
2. 막다른 길 잘라내기 (돋보기)
RustGo가 어디를 봐야 할지 알게 되면, 나머지 도시를 무시해야 합니다. 기존의 퍼징 도구들의 경우, 도시의 '지도'에는 표준 라이브로 구성된 거대한 도구들이 포함되어 있어, 도달 가능한 것처럼 보이지만 실제로는 도달할 수 없는 수많은 경로가 존재했습니다. 이는 도구를 혼란스럽게 만들어 쓸모없는 영역을 탐색하게 만들었습니다. RustGo는 '타겟 특정적 경로 프루닝(target-specific path pruning)'이라는 새로운 기술을 도입했습니다. 이것은 현재 조사 중인 특정 타겟으로 이어지지 않는 경로는 즉시 사라지게 만드는 마법의 울타리와 같습니다. 만약 탐정이 빵집의 버그를 찾고 있다면, 그 경로는 도서관이나 공원으로 가는 길을 차단합니다. 비록 그 경로들이 기술적으로는 도시에 존재할지라도 말입니다. 이를 통해 RustGo는 다른 도구들이 시간을 낭비했을 법한 무관한 경로의 **78.49%**를 잘라낼 수 있었습니다.
3. 공정하게 플레이하기 (라운드 로빈 전략)
여기 마지막으로 결정적인 반전이 있습니다. 탐정이 확인해야 할 여러 명의 용의자(타겟)가 있을 때, 기존의 도구들은 종종 가장 접근하기 쉬운 용의자에게 집착하여 더 어려운 용의자들을 무시하곤 합니다. RustGo는 '동적 공정 토글링(dynamic fair toggling)' 시스템을 사용합니다. 이는 탐정의 주의력을 완벽한 순환 방식으로 용의자들 사이에서 전환해 주는 심판과 같습니다. 만약 탐정이 용의자 A에 너무 많은 시간을 쓴다면, 심판은 즉시 탐정을 용의자 B로 전환하여 모든 용의자가 공정한 관심을 받도록 보장합니다. RustGo는 각 타겟에 자신만의 '눈덩이 큐(queue)'와 진행 상황을 추적하는 '점수판'을 부여함으로써 이를 수행합니다. 이를 통해 어떤 위험한 지점도 도달하기 어렵다는 이유로 테스트되지 않은 채 남겨지지 않도록 합니다.
결과: 더 빠르고 더 똑똑하게
연구팀은 gimli 애플리케이션을 포함하여 웹 서버부터 데이터 처리 도구에 이르는 13개의 실제 Rust 애플리케이션에 대해 RustGo를 테스트했습니다. 그들은 유명한 AFL++와 여러 특화된 '지향성' 퍼저들을 포함한 기존의 최고 도구들과 비교했습니다. 결과는 놀라웠습니다.
RustGo는 안전한 90%의 코드에 시간을 낭비하는 대신 순수하게 위험한 10%에만 집중했기 때문에, 다른 도구들보다 2.09배에서 5.08배 더 빠르게 취약점을 찾아냈습니다. 어떤 경우에는 gimli 애플리케ชัน처럼 10배 이상 빠른 속도를 보이기도 했습니다. 이를 체감해 보자면, 기존 도구가 버그를 찾는 데 10시간이 걸렸다면, RustGo는 한 시간도 채 걸리지 않아 찾아낸 것입니다.
하지만 속도만이 승리가 아닙니다. 연구팀은 실험실 안에서뿐만 아니라 실제 환경에서도 버그를 잡기 위해 RustGo를 사용했습니다. 그들은 실제 소프트웨어에서 13개의 알려지지 않은 메모리 버그를 발견했습니다. 이 중 10개는 소프트웨어 개발자들에 의해 확인되었으며, 8개는 이미 수정되었습니다. 더욱 인상적인 것은, 6개의 버그가 공식 보안 ID(RUSTSEC)를 부여받았고, 그중 하나는 글로벌 표준인 CVE ID를 받았다는 점입니다. 이는 RustGo가 단순한 이론적 아이디어가 아니라, 소프트웨어를 더 안전하게 만드는 데 이미 기여하고 있는 실질적인 도구임을 증명합니다.
이것이 중요한 이유
이 논문은 단순히 RustGo가 좋은 아이디어라는 것을 제안하는 데 그치지 않고, 그것을 측정하고, 증명하며, 다른 도구들이 놓친 실제 버그를 찾아내는 모습을 보여줍니다. 이 논문은 '안전하기 위해서는 모든 코드 라인을 테스트해야 한다'는 기존의 사고방식에 명시적으로 반박합니다. 대신, Rust의 고유한 규칙을 이해함으로써 우리가 더 똑똑하고, 빠르고, 효과적일 수 있음을 보여줍니다.
연구진은 또한 흔한 우려 사항인 "안전한 부분을 무시함으로써 버그를 놓치면 어떡하는가?"라는 질문에 답했습니다. 그들은 Rust의 안전 규칙이 매우 엄격하여, 안전한 코드 부분에서는 공간적 메모리 버그(잘못된 메모리 주소에 접근하는 것 등)가 절대로 발생할 수 없다고 설명합니다. 따라서 그 부분을 무시하는 것은 사각지대를 만드는 것이 아니라, 노이즈를 제거하는 것뿐입니다. 시간적 버그(메모리가 해제된 후 사용하는 것)의 경우, RustGo는 모든 것을 잡아낼 수 있도록 충분히 넓은 안전망을 유지하는 '보수적인(conservative)' 접근 방식을 사용하여, 버그로 이어질 수 있는 경로를 실수로 제거하지 않도록 주의를 기울였습니다.
결국, RustGo는 전문화의 힘을 보여주는 증거입니다. 숙련된 요리사가 견과류를 깨기 위해 망치를 사용하지 않듯, 현대적인 퍼저 역시 Rust와 같이 정교한 언어에 투박한 도구를 사용해서는 안 됩니다. 언어의 고유한 강점과 약점에 맞춰 접근 방식을 조정함으로써, 저자들은 더 빠를 뿐만 아니라 더 공정하며, 코드 내의 모든 잠재적 위험에 마땅한 주의를 기울일 수 있는 도구를 만들어냈습니다.
연구 분야의 논문에 파묻히고 계신가요?
연구 키워드에 맞는 최신 논문의 일일 다이제스트를 받아보세요 — 기술 요약 포함, 당신의 언어로.