구글 프로젝트 제로, 리눅스 커널 레이스 컨디션 탐색 도구 MAccConc 공개

Hacker News2026. 9. 10.조회 2

구글 프로젝트 제로가 리눅스 커널의 레이스 컨디션을 재현하고 탐색하는 도구 모음 MAccConc(Memory Access Concurrency)를 공개했다. 메모리 접근을 추적해 스레드 사이의 상호작용 지점을 찾아내고, 스택 기반으로 지연을 주입해 특정 인터리빙을 강제로 만들어내는 방식이다. 소스는 GitHub에 올라와 있다.

도구는 세 갈래로 나뉜다. 테스트 케이스에서 가능한 모든 A-B-A 인터리빙을 자동으로 시험하는 도구, 인터리빙을 직접 살펴보는 터미널 UI, 같은 목적의 GUI다. 커널 쪽 구현은 퍼징을 통한 레이스 컨디션 자동 발견에도 쓸 수 있도록 설계됐지만, 그걸 받쳐줄 사용자 공간 도구는 아직 구현되지 않았다.

핵심 개념은 SKI 논문이 말하는 '통신 지점(communication point)'이다. 두 스레드가 각각 수행하는 메모리 접근 쌍 가운데 최소 한쪽이 쓰기이고 접근 범위가 겹치면, 그 둘은 서로 영향을 줄 수 있는 후보다. 이런 쌍을 추려내면 살펴볼 가치가 있는 인터리빙을 좁힐 수 있다. SKI는 QEMU의 TCG 모드를 패치해 이 정보를 모았지만, MAccConc는 커널을 ASAN 아웃라인 모드(CONFIG_KASAN_OUTLINE, asan-instrumentation-with-call-threshold=0)로 빌드해 메모리 접근마다 헬퍼 함수 호출이 발생하도록 하는 쪽을 택했다. 커널 안에서 데이터를 모으면 나중에 락 획득·해제 같은 상위 수준 정보까지 얹을 수 있고, 이론적으로는 VM이 아니라 베어메탈에서도 시험할 수 있다는 게 저자의 설명이다.

트레이스 데이터를 사용자 공간으로 넘기는 통로로는 ftrace 대신 KCOV를 골랐다. KCOV는 트레이스를 메모리 안에 단순한 형태로 담아두기 때문에 크래시한 VM에서 데이터를 건져내는 데 유리하고, 상시 켜져 있는 정적 계측이라 비활성 상태의 오버헤드가 거의 없으며, 애초에 ftrace보다 높은 빈도의 이벤트를 염두에 둔 설계라는 점이 이유로 꼽혔다.

ASAN 계측을 그대로 쓰면 연달아 일어나는 메모리 접근에 대한 헬퍼 호출이 하나로 합쳐지므로, 커널 패치가 asan-opt-same-temp 백엔드 플래그로 이 최적화를 꺼서 접근 하나당 콜백 하나를 받도록 했다. 다만 ASAN은 원래 use-after-free 탐지용이라 스택 메모리를 직접 건드리는 접근에는 범위 초과 가능성이 없으면 헬퍼 호출을 내보내지 않는다. wait queue처럼 스택 위에 놓인 객체가 얽힌 레이스는 이 방식으로 잡히지 않을 수 있다는 뜻이다. 전역 변수 접근도 기본적으로 계측 대상이 아니지만 asan-opt-globals 플래그로 해제할 수 있다. 대안으로 TSAN 계측이 있는데, 데이터 레이스를 겨냥한 데다 접근의 원자성 정보까지 주는 대신 컴파일러가 ASAN과 TSAN 훅을 동시에 내보내지 못한다는 제약이 있다. TSAN 훅을 쓰면서 UAF 같은 메모리 안전성 위반 탐지도 유지하려면 커널의 ASAN 구현을 TSAN 훅 위에서 돌리거나 컴파일러를 고쳐야 한다.

실행할 때마다 달라지는 인터리빙을 비교하려면 각 메모리 접근을 실행 간에 안정적으로 식별할 수 있어야 한다. 데이터 주소를 기준으로 삼으면 매 실행 새로 할당되는 객체에서 문제가 생기고, 명령어 주소만 쓰면 같은 코드가 여러 객체에 대해 수행하는 접근을 구분하지 못한다. 그래서 이 도구는 카운트 증강 스택 트레이스(count-augmented stack traces)를 식별자로 쓴다.

레이스 컨디션은 여러 스레드가 정확히 특정 순서로 얽혀야 문제가 드러나기 때문에 다루기 까다롭다. 코드를 읽다가 버그 후보를 찾아도 실제로 존재하는지 증명하거나 반증하기 어렵고, 고친 뒤에는 그 버그를 확실히 재현하는 회귀 테스트를 남기기 어렵다. 퍼저 입장에서도 동시 작업의 흥미로운 인터리빙을 모두 훑거나 레이스가 일어날 때만 지나가는 코드 경로에 도달하기가 힘들다. 저자는 그동안 커널을 다시 빌드하면서 실행 중인 스레드 이름을 조건으로 mdelay()를 끼워 넣는 식으로 버그를 확인해 왔고, DTrace가 있는 macOS·Windows에서는 chill() 프로브를 쓰기도 했지만, 어느 쪽이든 시간이 오래 걸리고 시행착오가 필요하다고 적었다. 리눅스 커널에서는 레이스 수정 패치에 문제가 되는 스레드 인터리빙과 콜 그래프, 관련 메모리 접근을 손으로 그린 ASCII 다이어그램을 붙이는 관행이 있는데(최근 rt_spin_unlock UAF 수정, jbd2 데드락 수정 등), 이런 표현을 도구가 자동으로 만들어주면 편할 것이라는 게 동기다. Ned Williamson의 sockfuzzer 프로젝트와의 논의에서 출발했으며, 동기화 프리미티브 지점에서 스케줄을 바꿔 인터리빙을 탐색하는 커스텀 스케줄러를 썼다는 점이 참고가 됐다.

커널이나 동시성 코드를 다루는 팀에게 이 도구는 회귀 테스트 작성과 버그 후보 검증에 쓸 수 있는 선택지를 하나 더해준다. 수동 코드 리뷰로 찾아낸 레이스 후보가 실제로 재현되는지 확인하는 작업, 그리고 퍼징에 동시성 탐색을 붙이는 작업에 직접 연결된다. 다만 사용자 공간 쪽 퍼징 도구가 아직 없다는 점과 커널 쪽의 백그라운드 작업 지원이 부분적이라는 점은 감안해야 한다.

KCOV의 원격 커버리지는 백그라운드 작업 일부만 지원한다. 루프백 네트워크 패킷 수신 처리처럼 레이스와 관련 깊은 작업은 아직 이 메커니즘에 통합되지 않았고, 현재 원격 커버리지는 주로 블루투스·USB처럼 장치에서 들어오는 데이터를 처리하는 서브시스템 퍼징에 쓰인다. 다른 부분에 적용하는 건 비교적 간단하다며 RCU 콜백용 초안 패치가 있다고 한다. ASAN 계측의 특성상 스택 객체가 얽힌 레이스는 탐지되지 않을 수 있다는 점도 전제다.