4바이트로 root 따는 법: Copy Fail이 디스크에 흔적 안 남기는 이유
![]()
쓰기 권한이 없는 파일을 건드릴 수 없다는 건 리눅스 권한 모델의 가장 기본적인 전제잖아요. 일반 사용자가 `/usr/bin/su`를 수정한다? 안 되죠. 읽기랑 실행만 되니까요. 근데 Copy Fail(CVE-2026-31431)은 그 전제를 4바이트로 뒤집어요.
지난 4월 29일, Theori의 Xint 팀이 발견한 리눅스 커널 권한 상승 취약점이에요. 페이지 캐시에 올라온 내용 중 4바이트를 원하는 값으로 덮어쓸 수 있어요. 4바이트. 얼핏 사소하죠. 근데 이걸 반복하면 디스크 원본은 손도 안 댄 채로 메모리에 올라온 setuid 프로그램을 통째로 갈아끼울 수 있고, 그 끝이 root예요.
CISA는 공개 직후 이걸 KEV 카탈로그에 올렸고 미연방 기관엔 빨리 패치하라고 못 박았어요. 2017년 이후 나온 거의 모든 주요 리눅스 배포판이 영향권. 왜 이렇게까지 시끄러운지, 페이지 캐시라는 개념 하나만 잡으면 의외로 깔끔하게 풀려요. 코드 레벨까지 안 파고 원리만 짚어볼게요.
디스크는 외곽 물류창고, 페이지 캐시는 매장 진열대
리눅스가 빠른 척하는 비결 중 하나가 페이지 캐싱(Page Caching)이에요. 이유는 단순해요. SSD나 HDD 같은 디스크는 RAM보다 수십 배에서 수만 배 느리거든요. 그래서 자주 쓰는 파일 데이터를 페이지 단위로 RAM에 미리 복사해두고, 다음부터는 디스크 대신 그 복사본을 읽어요. 이때 RAM에 올라온 파일 데이터를 페이지 캐시라고 부릅니다.
여기서 "페이지"는 메모리를 잘게 쪼갠 최소 단위예요. 책의 한 장 같다고 해서 붙은 이름인데, 리눅스든 윈도우든 맥OS든 보통 한 페이지는 4KB(4096바이트)죠.

물류로 비유하면 와닿아요. 디스크는 외곽에 있는 거대한 메인 물류창고. 매번 거기까지 다녀오긴 번거로우니까, 한 번 가져온 상품은 가까운 진열대에 올려두고 다음 요청부터는 진열대에서 바로 꺼내 써요. 자주 찾는 상품일수록 진열대에 오래 남죠. 그 진열대 위 상품들이 페이지 캐시인 셈이죠.
더티 비트 1비트, 이게 전부예요
페이지엔 더티 비트(Dirty Bit)라는 속성이 있어요. 페이지 데이터가 수정됐는지를 나타내는 1비트짜리 플래그고, 페이지마다 하나씩 달려 있어요. 미리 말해두면 이 1비트가 Copy Fail의 은닉성을 이해하는 열쇠예요.
이게 왜 필요하냐. 디스크의 원본이랑 메모리에 올라온 페이지 사이의 일관성을 맞추려고요. 0(Clean)이면 데이터가 안 바뀌었다는 뜻, 디스크 원본과 캐시 내용이 똑같은 상태예요.

1(Dirty)이면 페이지가 수정돼서 디스크 원본과 내용이 달라진 상태고, 이런 페이지를 더티 페이지(Dirty Page)라고 불러요.

리눅스는 프로세스가 파일을 고쳐도 곧바로 디스크에 안 써요. 일단 페이지 캐시 내용을 먼저 바꾸고 더티 비트를 1로 세워둬요. 그리고 커널이 주기적으로 더티 페이지들을 모아서 디스크에 한 번에 반영한 다음, 끝난 페이지의 더티 비트를 다시 0으로 내립니다. 정상 경로라면요. 이 "정상 경로"라는 단서, 기억해두세요.
일반 사용자가 암호화 모듈을 두드릴 수 있다는 게 시작이에요
리눅스 커널 안에는 인증 암호화(AEAD, Authenticated Encryption with Associated Data) 알고리즘을 처리하는 모듈이 여럿 있어요. AEAD는 데이터를 암호화하면서 무결성까지 검증하는 알고리즘이라 디스크 암호화, IPsec, TLS 같은 데 두루 쓰여요. 만드는 방식은 둘. AES-GCM이나 ChaCha20-Poly1305처럼 알고리즘 하나가 암호화와 인증을 같이 해버리는 방식, 그리고 일반 암호 알고리즘과 인증 알고리즘을 조합해주는 헬퍼 템플릿 방식.
Copy Fail은 후자, 헬퍼 템플릿 중 `authencesn`에 있어요. ESN 옵션이 적용된 IPsec 패킷을 처리하는 모듈인데, 솔직히 일반 데스크톱에서 직접 불릴 일이 거의 없는 특수 목적 코드예요. (근데 안 불린다고 못 부르는 건 아니죠.)
문제는 여기예요. 리눅스 커널은 `AF_ALG`라는 특수 소켓 인터페이스로 커널 내부 암호화 모듈을 일반 사용자 프로세스에도 열어둬요. 그래서 root가 아닌 일반 사용자도 `socket()`만 있으면 `authencesn`을 비롯한 커널 암호화 헬퍼에 입력을 밀어넣을 수 있어요. 그리고 이 `authencesn`에, 페이지 캐시의 4바이트를 원하는 값으로 덮어쓰는 버그가 있는 거고요.
4바이트만 덮어쓴다니까 우습죠? 근데 여러 번 반복해서 넓은 영역을 갈거나, 프로그램 내부의 권한 검사 로직 한 조각만 비틀면 얘기가 달라져요. 진짜 핵심은 이 덮어쓰기가 디스크 원본이 아니라 페이지 캐시에서 일어난다는 점.
생각해보세요. `su`나 `passwd`처럼 root가 소유하고 setuid가 걸린 프로그램은 일반 사용자가 읽기(r)와 실행(x)만 됩니다. 수정 불가. 근데 Copy Fail로 페이지 캐시를 변조해두면, 그 프로그램이 실행될 때 커널은 디스크 원본 대신 변조된 캐시를 써요. 쓰기 권한 없는 실행 파일의 동작을 바꿔서 돌려버리는 거죠.
잠깐, setuid 비트가 뭐냐면. 보통 프로그램은 실행한 사용자의 권한으로 돌아요. 근데 setuid가 걸리면 EUID(Effective UID)가 실행한 사람이 아니라 파일 소유자 권한으로 설정돼요. `/usr/bin/passwd`가 딱 그래요. 소유자가 root에 setuid가 걸려 있어서, 일반 사용자가 실행해도 EUID는 root. 덕분에 `/etc/shadow` 같은 파일에 접근해 자기 비밀번호를 바꿀 수 있죠. 그렇다고 남의 비밀번호까지 마음대로? 아니에요. EUID가 root여도 RUID(Real UID)로 "실제 실행한 사람이 누군지"는 여전히 확인되거든요. passwd는 RUID를 검사해서, 일반 사용자면 자기 것만, root가 실행했을 때만 남의 것까지 바꾸게 짜여 있어요. 이 RUID 검사, 다음 문단에서 깨집니다.
su를 실행했는데 160바이트 ELF가 뜨는 마술
단계로 보면 이래요. 디스크에 root 소유 setuid 바이너리 `/usr/bin/su`가 있어요. 누가 이걸 실행하면 페이지 캐싱돼서 메모리에 페이지 캐시로 올라오죠. 이때는 디스크 원본과 캐시 내용이 똑같으니 더티 비트는 0.
이제 Copy Fail로 메모리에 올라온 `/usr/bin/su`의 페이지 캐시를 변조해요. 최초 공개된 PoC에서는 `setreuid()`와 `execve()`를 호출하는 '160바이트짜리 자그마한 ELF 실행 파일'로 캐시를 덮었어요. 이 ELF는 RUID까지 root로 박아버린 다음 셸을 띄우도록 만들어졌고요. 아까 그 RUID 검사? `setreuid()`로 정면 돌파한 거예요.
여기서 진짜 봐야 할 건 따로 있어요. 캐시 내용은 분명 변조됐는데, 더티 비트는 여전히 0.

그 다음은 허무할 정도로 간단해요. 일반 사용자가 `/usr/bin/su`를 실행하면, 디스크 프로그램이 아니라 페이지 캐시에 올라온 '160바이트 ELF'가 실행돼요. 결과는 root 권한 셸.

예시로 `su`를 들었을 뿐, su만 되는 게 아니에요. root가 소유하고 setuid가 걸린 실행 파일이면 뭐든 표적이 될 수 있어요. 캐시는 바뀌었는데 더티 비트가 0인 이유는, Copy Fail의 비정상적인 쓰기가 일반적인 페이지 수정 경로를 안 거치기 때문이고요. 앞서 "정상 경로면 더티 비트를 1로"라고 했죠. 정상 경로가 아니니까 커널은 더티 비트를 갱신하지 않아요.
🚩 여기서는 setuid 실행 파일을 예로 들었지만, 문제는 실행 파일에만 머물지 않아요. root 권한으로 보호돼 수정되면 안 되는 파일이라면 같은 방식으로 당할 수 있고, `/etc/passwd` 같은 핵심 시스템 파일이 변조되면 그 자체로 심각한 사고예요.
실제론 더 복잡한 원리가 숨어 있지만, 큰 그림은 이게 다예요.
더티 비트가 안 켜진다는 건, 흔적이 없다는 뜻
이 취약점이 무서운 진짜 이유는 은닉성이에요. Copy Fail은 캐시 내용은 바꾸면서 더티 비트는 건드리지 않아요. 그래서 커널은 이 페이지를 "디스크에 반영해야 할 더티 페이지"로 아예 인식을 못 해요.
결과가 묘해요. 변조된 내용은 페이지 캐시에만 남고, 디스크 원본 파일은 멀쩡합니다. 프로그램을 돌릴 땐 변조된 캐시가 쓰이는데, 디스크 기반 해시 검사나 파일 무결성 검사를 돌리면 원본이 깨끗하게 보여요. 솔직히 이 부분이 제일 섬뜩해요. 침해를 당했는데 포렌식 도구는 "이상 없음"이라고 말하는 거니까요.
물론 변조가 메모리에서만 일어나니까 재부팅하거나 페이지 캐시가 비워지면 변조 내용이 날아갈 수 있어요. 휘발성. 근데 공격자 입장에선 이게 약점이 아니라 강점이에요. 디스크에 흔적 한 점 안 남기고 권한 상승을 끝낼 수 있다는 뜻이잖아요.
Copy Fail은 혼자가 아니에요 — Dirty 가문 계보
페이지 캐시를 이해하고 나면 Copy Fail이 완전히 낯선 종족이 아니라는 게 보여요. 리눅스엔 예전부터 "디스크 파일을 직접 수정할 권한은 없지만 페이지 캐시를 덮어써서 권한을 올리는" 취약점들이 있었거든요. 대표 선수가 Dirty Pipe와 Dirty COW.
Copy Fail이랑 Dirty Pipe는 특히 닮았어요. Copy Fail은 커널 암호화 모듈에서, Dirty Pipe는 파이프 버퍼 기능에서 터졌지만, 둘 다 결국 파일의 페이지 캐시를 변조한다는 점에서요. Dirty COW는 결이 좀 달라요. 디스크 원본은 그대로 두고 캐시만 조용히 바꾸는 부류는 아니고, Copy-On-Write 처리의 레이스 컨디션을 노려 읽기 전용 파일을 변조한다는 점에서 비슷한 거예요. 그리고 Copy Fail이 공개된 뒤엔 다른 연구자들이 각자 다른 길로 페이지 캐시를 덮는 취약점들을 또 내놨어요. Dirty Frag랑 DirtyDecrypt가 그거고요.
정리하면 이래요.
- Copy Fail (CVE-2026-31431, 공개 26.04): 커널 암호화 모듈. AF_ALG, `splice()`, `authencesn` 처리 과정에서 페이지 캐시 페이지가 쓰기 대상으로 잘못 쓰임. 디스크 원본은 그대로 두고 캐시만 변조해서 디스크 기반 무결성 검사를 우회하기 쉬움. 익스플로잇은 확정적으로 변조 가능.
- Dirty Pipe (CVE-2022-0847, 공개 22.03): 파이프 버퍼 기능. 초기화 안 된 `pipe_buffer.flags` 때문에 파일 페이지 캐시에 데이터가 잘못 병합됨. 캐시는 변조되지만 디스크 원본엔 바로 안 남을 수 있음. 조건이 맞으면 비교적 안정적으로 재현 가능.
- Dirty COW (CVE-2016-5195, 공개 16.10): Copy-On-Write 처리. COW 레이스 컨디션으로 읽기 전용 파일 매핑에 쓰기가 뚫림. 페이지 캐시와 관련은 있지만 공격 방식에 따라 실제 파일 변경으로 이어져 흔적이 남을 수 있음. 레이스 컨디션 기반이라 타이밍·커널 상태·시스템 부하를 탐.
- Dirty Frag (CVE-2026-43284, CVE-2026-43500, 공개 26.05): 네트워크 암호화 기능(IPsec, RxRPC) 처리 과정에서 페이지 캐시 페이지가 쓰기 대상으로 잘못 쓰임. 조건이 맞으면 안정적으로 재현 가능.
- DirtyDecrypt (CVE-2026-31635, 공개 26.05): 네트워크 보안 계층(RxGK)이 받은 데이터를 복호화할 때 실수로 페이지 캐시에 쓰기가 발생. Fedora, Arch, openSUSE 같은 일부 배포판에서만 작동. 안정적이지만 해당 기능이 켜진 배포판 한정.
같은 가문인데 익스플로잇 안정성이 제각각인 게 보이죠. 레이스 컨디션에 기대는 Dirty COW는 타이밍 운을 타지만, Copy Fail은 "확정적으로 페이지 캐시를 변조"해요. 이 확정성이 Copy Fail을 한 단계 더 위험하게 만드는 지점이에요.
원리를 머리로 아는 거랑 손으로 직접 root를 따보는 건 또 다른 경험이잖아요. 드림핵에 Copy Fail을 다루는 워게임 문제가 있어요. 자세한 내부 원리를 몰라도 괜찮아요. 인터넷에 공개된 PoC를 찾아서 문제 환경에 올리고 root 권한으로 플래그를 얻어보는 것만으로도 이 취약점의 위험을 훨씬 직관적으로 체감하게 되거든요. 어떤 PoC가 그 환경에 맞는지, PoC를 어떻게 옮겨 실행할지 고민하는 과정도 꽤 남는 게 있고요.
코드 레벨에서 패치까지 더 깊이 파고 싶다면 Theori Xint 팀이 직접 쓴 "Copy Fail: 732 Bytes to Root on Every Major Linux Distribution", 그리고 컨테이너 이스케이프로 응용한 "Copy Fail: From Pod to Host"를 보면 돼요.
다시 처음 질문으로. 4바이트로 root가 가능하냐. 가능해요. 단, 디스크가 아니라 메모리를 노렸을 때, 그리고 더티 비트라는 1비트를 안 켜는 방법을 알았을 때.