리눅스 커널 CVE 목록을 오래 들여다본 사람이라면 use-after-free, 버퍼 오버플로우, 널 포인터 역참조 같은 메모리 안전성 버그가 얼마나 자주 반복되는지 알 것이다. 리눅스 안정 커널 메인테이너 그렉 크로아-하트만은 지난 25년간의 커널 CVE 중 약 80%가 메모리 안전 언어였다면 애초에 존재하지 않았을 버그라고 평가한 바 있다. Rust for Linux는 커널 전체를 다시 쓰자는 게 아니라, 새로 작성하는 드라이버 코드부터 이런 종류의 버그를 언어 차원에서 막아보자는 프로젝트다. 2022년 12월 커널 6.1에 실험적 지원으로 처음 들어온 이후 4년 가까이 지난 지금, 2026년 7월 15일 크로아-하트만은 뭄바이 Open Source Summit India에서 “커널 프로젝트 안에서 Rust는 더 이상 실험이 아니다. 영구적으로 자리잡았다”고 공식 선언했다. 이 글에서는 Rust for Linux가 어떤 방식으로 동작하는지, 지금까지 어디까지 왔는지, 그리고 실제 최소 Rust 커널 모듈을 어떻게 빌드하는지 정리한다.
Rust for Linux란
핵심 아이디어는 커널의 C API와 직접 맞닿는 부분을 전부 unsafe로 감싸 신중하게 검증된 추상화 계층(kernel crate) 안에 가둬버리는 것이다. 이 추상화가 제대로 되어 있다면, 그 위에서 실제 드라이버를 작성하는 개발자는 unsafe 키워드를 한 번도 쓰지 않고도 코드를 완성할 수 있다. C 헤더에서 Rust가 호출할 수 있는 raw 바인딩은 bindgen이 자동으로 생성해주고, 인라인 함수나 복잡한 매크로처럼 bindgen이 다루지 못하는 부분은 rust/helpers/에 작은 wrapper 함수를 추가해 노출한다. 기존 C 코드를 Rust로 재작성할 계획은 없고, 새 코드 — 주로 드라이버 — 를 Rust로 작성할 수 있는 선택지를 여는 데 초점이 맞춰져 있다.
지금까지의 진행 상황
- 2022년 12월, 커널 6.1: Rust 실험적 지원 최초 병합.
- 2024년 초, 커널 6.8: 첫 Rust 드라이버인 ASIX AX88772A 네트워크 PHY 드라이버와 PHY 추상화 계층이 메인라인에 들어감.
- 커널 6.12 기반 Android 16: 구글의 ashmem(익명 공유 메모리) 서브시스템이 Rust로 포함됨.
- 2025년 10월: 안드로이드 프로세스 간 통신(IPC)의 핵심인 Binder 드라이버의 Rust 구현이 메인라인에 병합됨. 기존 C 구현을 완전히 대체하는 게 목표다.
- GPIO 서브시스템: 핀 컨트롤 조작을 위한 Rust 추상화가 마련됨.
- 그래픽스 서브시스템: 신규 드라이버는 이제 Rust로만 받는다는 방침이 정해짐.
- 2026년 7월 15일: 커널 7.0을 앞두고 “실험적” 딱지가 공식적으로 떨어짐.
개발 환경 준비
커널 소스 트리에서 빌드 요구사항이 충족됐는지부터 확인한다. Kconfig가 Rust 지원 여부를 판단할 때 쓰는 것과 같은 로직으로 검사해준다.
make LLVM=1 rustavailable
rustup은 apt의 rustc/cargo와 달리 여러 toolchain(stable/beta/nightly)을 프로젝트별로 전환할 수 있는 버전 관리자다. 커널 트리가 특정 rustc 버전을 요구하는 경우가 많아 apt 대신 rustup을 권장한다.
| 설치 방법 | 명령 | 비고 |
|---|---|---|
| Ubuntu snap | sudo snap install rustup --classic | rustup self update가 막혀 있음 — rustup 자체 업데이트는 sudo snap refresh rustup으로 |
| rust-lang.org 스크립트 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh | rustup self update 정상 동작, 배포판 무관하게 동일 |
실제 Ubuntu 24.04 LTS에서 curl 스크립트로 설치하면 rustc 1.97.1이 설치된다. 어느 쪽이든 설치 후 새 셸을 열거나 source "$HOME/.cargo/env"로 PATH를 반영하고 필요한 컴포넌트를 준비한다.
rustup override set stable
rustup component add rust-src
bindgen을 설치하기 전에 libclang부터 챙겨야 한다. libclang 없이 실제로 bindgen을 실행하면 이렇게 실패한다.
$ bindgen test.h -o test_bindings.rs
thread 'main' panicked at ...:
Unable to find libclang: "couldn't find any valid shared libraries matching:
['libclang.so', 'libclang-*.so', 'libclang.so.*', 'libclang-*.so.*'],
set the `LIBCLANG_PATH` environment variable to a path where
one of these files can be found (invalid: [])"
실제로 두 테스트 머신에서 재현 여부가 갈렸다 — 최소 설치 환경은 에러가 났지만, 다른 도구가 이미 깔린 머신은 libclang-dev 없이 런타임 패키지 libclang1-18만으로도 동작했다(clang-sys가 libclang-18.so.1처럼 버전 붙은 파일도 인식). 핵심은 “libclang 공유 라이브러리가 어떤 경로로든 있어야 한다”는 것이므로, 확실히 하려면 libclang-dev(내부적으로 libclang-18-dev 의존)를 설치해두는 게 안전하다.
sudo apt install libclang-dev
libclang이 준비된 뒤에 bindgen-cli를 설치한다.
cargo install --locked bindgen-cli
커널을 LLVM=1로 빌드할 때 쓰는 clang/llvm 툴체인도 결국 같은 libclang을 공유하므로, 커널 소스 쪽 요구사항까지 맞추려면 clang 패키지도 함께 설치해두는 게 안전하다. 조건이 갖춰졌다면 커널 설정에서 CONFIG_RUST를 켜고 다음으로 빌드한다.
make LLVM=1
최소 Rust 커널 모듈 — rust_minimal.rs
커널 소스 트리의 samples/rust/rust_minimal.rs는 가장 짧은 형태의 Rust 커널 모듈 예제다.
// SPDX-License-Identifier: GPL-2.0
//! Rust minimal sample.
use kernel::prelude::*;
module! {
type: RustMinimal,
name: "rust_minimal",
authors: ["Rust for Linux Contributors"],
description: "Rust minimal sample",
license: "GPL",
params: {
test_parameter: i64 {
default: 1,
description: "This parameter has a default of 1",
},
},
}
struct RustMinimal {
numbers: KVec<i32>,
}
impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
pr_info!(
"test_parameter: {}\n",
*module_parameters::test_parameter.value()
);
let mut numbers = KVec::new();
numbers.push(72, GFP_KERNEL)?;
numbers.push(108, GFP_KERNEL)?;
numbers.push(200, GFP_KERNEL)?;
Ok(RustMinimal { numbers })
}
}
impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}module! 매크로가 C의 module_init()/module_exit(), MODULE_LICENSE() 같은 매크로들을 한 번에 대체한다. 모듈이 로드될 때는 kernel::Module 트레이트의 init()이 호출되는데, 여기서 KVec(커널 전용 할당자를 쓰는 벡터 타입)에 값을 채워 넣고 RustMinimal 인스턴스를 반환한다. numbers.push(72, GFP_KERNEL)?처럼 커널 메모리 할당 함수마다 실패 가능성을 Result로 명시적으로 다루게 강제되는 점이 C와 가장 다른 부분이다. 모듈이 언로드될 때는 Drop 트레이트의 drop()이 자동으로 호출되어 정리 작업을 하는데, C처럼 별도의 exit 함수에서 해제를 깜빡하는 실수를 구조적으로 줄여준다.
Rust도 완벽한 방패는 아니다 — CVE-2025-68260
2025년 12월 16일, Rust로 작성된 커널 코드에 처음으로 CVE가 할당됐다. 대상은 앞서 언급한 Rust Binder 드라이버였다. drivers/android/binder/node.rs의 death_list(프로세스가 죽었을 때 알림을 등록해두는 연결 리스트) 처리 로직에서, 동기화 없이 리스트 포인터를 조작하는 unsafe 코드가 매우 드문 동시성 상황에서 리스트를 손상시켜 커널 패닉을 유발할 수 있는 레이스 컨디션이었다. 이 unsafe 블록은 “이 포인터는 이 락을 쥐고 있는 동안에만 유효하다”, “이 객체는 리스트에 있는 동안 해제될 수 없다” 같은 불변조건(invariant)을 사람이 직접 보증해야 하는 코드였는데, 그 보증이 특정 동시성 인터리빙에서 깨졌다. 커널 6.18.1과 6.19-rc1에서 패치됐다.
이 사례는 Rust가 메모리 안전성 버그를 “구조적으로 줄여준다”는 것과 “완전히 없애준다”는 것이 다른 얘기라는 걸 보여준다. 추상화 계층 바깥으로 나가 unsafe를 직접 쓰는 순간부터는, 그 블록 안의 불변조건을 지키는 책임은 결국 사람에게 돌아온다.
주의사항
- 커널 전체가 Rust로 옮겨가는 게 아니다. 기존 C 코드를 재작성할 계획은 없고, 새로 작성하는 드라이버 위주로 선택지가 열려 있는 상태다. 여전히 커널 대부분은 C다.
- 그래픽스 서브시스템처럼 신규 드라이버에 Rust를 사실상 강제하는 곳이 있는 반면, 대부분의 서브시스템은 아직 C가 기본이라 프로젝트마다 정책이 다르다는 점을 확인하고 시작해야 한다.
- 안전한 추상화 계층으로 감싸지 못해
unsafe를 직접 써야 하는 지점은 여전히 존재하며, CVE-2025-68260처럼 그 안의 불변조건이 깨지면 C와 똑같이 메모리 손상이나 패닉으로 이어질 수 있다. - 커널 트리가 요구하는 rustc/rust-src 버전과 로컬에 설치된 rustup 툴체인 버전이 어긋나면
make LLVM=1 rustavailable부터 실패하므로, 커널 버전에 맞는 툴체인 버전을 문서에서 먼저 확인하는 게 좋다.
마무리
Rust for Linux는 “커널을 Rust로 다시 쓰자”가 아니라 “새로 짜는 코드부터는 언어가 흔한 버그 클래스를 미리 막아주게 하자”는 실용적인 접근에 가깝다. 4년 가까운 실험 기간을 거쳐 그래픽스·Binder 같은 실제 서브시스템에 자리잡았고, 이제는 실험 딱지까지 뗀 만큼 앞으로 더 많은 서브시스템에서 선택지로 등장할 가능성이 크다. 다만 CVE-2025-68260이 보여주듯 unsafe 경계 안쪽은 여전히 사람의 몫이라, “Rust라서 안전하다”보다는 “안전한 추상화가 어디까지 감싸주는지”를 따져보는 태도가 더 정확하다.