한 줄 요약
732바이트짜리 파이썬 스크립트 하나로, 2017년 이후 빌드된 거의 모든 주요 리눅스 배포판에서 root를 따낼 수 있는 커널 논리 버그.
표준 라이브러리만 쓰고, race condition도 없고, 배포판별 오프셋도, 컴파일된 셸코드 페이로드도 필요 없다. Ubuntu·RHEL·Amazon Linux·SUSE에서 같은 스크립트가 그대로 돈다. 어제(2026-04-29) 공개됐고, 발견자는 한국 보안업체 Theori의 Taeyang Lee, 도구는 자체 AI 코드 감사기 Xint Code다.
Dirty COW·Dirty Pipe와 무엇이 다른가
이 자리는 익숙하다. 2016년의 Dirty COW, 2022년의 Dirty Pipe도 모두 비특권 사용자가 페이지 캐시에 손을 대서 setuid 바이너리를 갈아치우는 LPE였다. 그런데 그 둘과 Copy Fail의 결정적인 차이는 race가 없다는 점이다.
| Dirty COW (2016) | Dirty Pipe (2022) | Copy Fail (2026) | |
|---|---|---|---|
| 클래스 | race condition | flag 초기화 누락 | 직선형 논리 버그 |
| 성공률 | 확률적 (수십~수천 회 시도) | 거의 결정론적 | 첫 시도 결정론적 |
| 배포판별 오프셋 | 필요할 때 있음 | 거의 불필요 | 불필요 |
| 컴파일된 페이로드 | 보통 필요 | 종종 필요 | 불필요 (Python 표준 라이브러리만) |
| 영향 기간 | ~2007–2016 | ~2020–2022 | 2017–패치 적용일 |
“직선형 논리 버그"라는 표현이 핵심이다. 흐름이 분기 없이 일자로 흐르고, 각 단계가 의도대로 떨어지면 페이지 캐시의 정해진 위치에 정해진 바이트가 쓰인다. 운영자 입장에서 가장 무서운 형태의 LPE다 — 패치하기 전까지는 누구나 한 번에 성공할 수 있다.
세 가지 변경의 우연한 교차점
Copy Fail은 단일 버그가 아니다. 시기가 다른 세 가지 커널 변경이 한 자리에서 만나면서 만들어진 함정이다.
| 시기 | 변경 | 원래 의도 |
|---|---|---|
| 2011 | authencesn 템플릿 추가 | IPsec ESP의 64비트 Extended Sequence Number 지원. 처음부터 호출자의 destination scatterlist를 임시 저장 공간으로 재사용 |
| ~2014 | AF_ALG 소켓에 AEAD 지원(algif_aead.c) + splice() 경로 | 사용자 공간이 커널 crypto API를 소켓처럼 쓰도록 하기 위함 |
| 2017 | algif_aead에 in-place AEAD 최적화 (commit 72548b093ee3) | 메모리 절약 목적. 복호화 시 req->src = req->dst로 묶고, tag 페이지는 sg_chain()으로 참조만 연결 |
각각만 보면 그럴듯한 변경이다. 문제는 셋이 만나는 지점이다.
splice()로 읽기 전용 파일의 페이지 캐시 페이지를 AF_ALG의 TX scatterlist에 그대로 참조로 넣을 수 있다.- 2017년 인-플레이스 최적화가 그 TX scatterlist를 그대로 쓰기 가능한 dst scatterlist로 재사용한다.
authencesn은dst[assoclen + cryptlen]위치에 ESN 임시값(4바이트)을 쓴다 — 그 자리가 1번에서 체인된 페이지 캐시 페이지다.
결과적으로 비특권 사용자가 임의 파일의 페이지 캐시에 결정론적인 4바이트 쓰기를 할 수 있다. 거의 10년 동안 조용히 익스플로잇 가능한 상태로 있었다.
익스플로잇 흐름
PoC는 copy.fail에 공개돼 있고 본문에는 옮기지 않는다. 동작 원리는 다음과 같다.
flowchart LR
U["비특권 프로세스"]
SU["/usr/bin/su
(setuid root)"]
PC["page cache
(su 페이지)"]
SOCK["AF_ALG 소켓
authencesn(...)"]
TX["TX SGL
(읽기 전용으로 의도)"]
DST["dst SGL
(쓰기 가능)"]
ESN["authencesn ESN 임시값
(4바이트)"]
EXEC["execve('/usr/bin/su')"]
U -- "1. open + splice" --> SU
SU -- "페이지 참조" --> PC
PC -- "splice로 주입" --> TX
TX -- "2017 in-place 최적화로 재사용" --> DST
SOCK -- "recv() = 복호화 동작" --> DST
DST -- "3. dst[off]에 4B 기록" --> ESN
ESN -- "= page cache 손상" --> PC
U -- "4. 호출" --> EXEC
EXEC -- "5. 손상된 페이지에서 로드" --> SU
SU -- "쉘코드가 root 권한으로 실행" --> U
순서를 풀면 이렇다.
- AF_ALG 소켓을 열어
authencesn(...)알고리즘에 bind한다. - 타깃 setuid 바이너리(
/usr/bin/su등)를 열어 그 페이지 캐시 페이지를splice()로 소켓의 TX scatterlist에 주입한다. - 쉘코드를 4바이트 단위로 잘라, 매번 위치 계산을 맞춰
sendmsg()+splice()페어를 반복한다. recv()를 호출하면 복호화 동작이 이뤄지며authencesn이 ESN 임시값을 dst — 즉 손에 들어온 페이지 캐시 페이지 — 에 4바이트씩 기록한다.- 마지막으로
execve("/usr/bin/su"). 손상된 페이지 캐시에서 로드된 setuid 바이너리가 쉘코드를 root 권한으로 실행한다.
PoC가 이렇게 짧은 이유는 모든 단계가 표준 syscall과 표준 라이브러리만 쓰기 때문이다. 컴파일러도, 외부 페이로드도 필요 없다.
왜 이게 무서운가
세 가지가 겹친다.
디스크는 안 건드린다. 손상은 페이지 캐시에서만 일어나기 때문에 디스크 파일의 해시는 그대로다. AIDE·Tripwire 같은 무결성 모니터링이 잡지 못한다. 재부팅하거나 페이지가 evict되면 캐시는 정상 복원되지만, 그 사이에 익스플로잇은 끝난다.
페이지 캐시는 호스트 전체가 공유한다. 컨테이너는 같은 호스트 커널의 같은 페이지 캐시를 본다. 즉 비특권 컨테이너 안에서 일으킨 4바이트 쓰기가 호스트의 setuid 바이너리, 또는 옆 컨테이너의 read-only mount 파일을 손상시킬 수 있다. K8s 노드, 멀티테넌트 SaaS, 공유 CI 러너가 1순위 위험군이다. namespace 격리만 쓰는 컨테이너 모델은 이 한 줄짜리 버그로 그대로 무너진다.
CVSS 7.8 (High). Critical이 아닌 이유는 로컬 액세스가 필요해서일 뿐, 한번 로컬이 잡히면 결정론적으로 root까지 직행한다. 실무 영향에서는 Critical과 다를 게 없다.
AI가 한 시간 만에 찾았다는 것의 의미
이 발견의 또 다른 화제는 도구다. 발견자는 Theori의 Taeyang Lee, 도구는 자체 개발한 AI 기반 보안 스캐너 Xint Code. 단일 오퍼레이터 프롬프트로 Linux 커널의 crypto/ 서브시스템 전체를 약 한 시간 동안 감사했고, 이 버그가 가장 심각한 항목으로 보고됐다.
핵심은 “AI가 사람보다 똑똑해서"가 아니다. 사람의 통찰 — AF_ALG와 splice가 만나는 자리가 실은 거대한 비특권 공격 표면이라는 가설 — 을 정해주면, AI는 그 가설을 전체 코드 경로에 결정론적으로 적용해 보는 일을 사람과 비교가 안 되게 잘한다. 그게 10년 동안 사람 손에 안 잡힌 코드 경로 하나를 끄집어냈다.
The Register는 Trend Micro Zero Day Initiative의 Dustin Childs를 인용해 “최근 취약점 제보 폭증의 가장 큰 변수는 AI 기반 버그 발견"이라고 보도했다. 이 흐름이 운영자 입장에서 의미하는 건 단순하다. 패치 사이클이 더 짧아져야 한다는 것 — 그리고 오늘 멀쩡해 보이는 코드가 내일도 멀쩡할 거라고 가정하지 않는 것.
공개 타임라인
| 날짜 | 이벤트 |
|---|---|
| 2026-03-23 | Linux 커널 보안팀에 보고 |
| 2026-03-25 | 패치 제안 |
| 2026-04-01 | 메인라인 커널 커밋 (a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 할당 |
| 2026-04-29 | 공개 공시 |
보고에서 메인라인 패치까지 9일. 이건 빠른 편이다. 그러나 메인라인 패치와 각 배포판의 안정 커널이 사용자 손에 들어오는 시점 사이엔 늘 며칠~몇 주의 갭이 있고, 그 사이가 운영자에게 가장 위험한 구간이다.
운영자 대응 가이드
세 단계로 본다 — 즉시 완화, 정식 패치, 검증.
1) 즉시 완화: algif_aead 모듈 차단 (재부팅 불필요)
LUKS·kTLS·IPsec은 AF_ALG 사용자 공간 인터페이스가 아닌 다른 경로를 쓰기 때문에 영향이 없다. 영향을 받는 건 OpenSSL의 afalg 엔진처럼 AF_ALG를 명시적으로 호출하는 경우뿐이다 — 즉 일반 서버에서는 모듈 차단으로 인한 부작용이 거의 없다.
단, 차단이 가능한지부터 확인해야 한다. RHEL 9·10 일부 빌드는 algif_aead가 LKM이 아니라 커널 빌트인이고, 그러면 modprobe로 막을 수 없다.
# 모듈로 로드돼 있는지
lsmod | grep algif_aead
# 빌트인인지 (filename: (builtin) 이면 차단 불가)
modinfo algif_aead 2>/dev/null | head -3
LKM인 경우 — modprobe 차단:
echo 'install algif_aead /bin/true' | sudo tee /etc/modprobe.d/disable-algif_aead.conf
sudo rmmod algif_aead 2>/dev/null || true
/bin/false 대신 /bin/true 를 쓰는 건 다른 모듈이 의존성으로 algif_aead를 끌어올 때 modprobe가 에러를 뱉지 않도록 하기 위해서다 — 어느 쪽이든 모듈은 안 올라온다.
빌트인인 경우 — modprobe 차단은 효과가 없다. 두 가지 우회 중 하나:
- SELinux/AppArmor로 AF_ALG 사용 제한. 일반 사용자 도메인에서
socket(AF_ALG, ...)호출 자체를 막는 정책. 정책 변경이라 운영 검증이 필요하다. - 정식 패치 일정을 앞당긴다. 빌트인 환경에서는 사실상 이 옵션이 가장 현실적이다.
2) 정식 패치 (재부팅 필요, kpatch 가능 시 무중단)
RHEL/Rocky/Alma:
sudo dnf updateinfo list cves | grep CVE-2026-31431 # RHSA 발행 확인
sudo dnf update kernel kernel-core kernel-modules
sudo reboot
Ubuntu/Debian:
sudo apt update
apt list --upgradable 2>/dev/null | grep -E '^linux-(image|generic|headers)'
sudo apt install --only-upgrade linux-image-$(uname -r | sed 's/-generic//')-generic
sudo reboot
# Ubuntu Pro의 경우
sudo pro status
canonical-livepatch status # 라이브 패치 발행 여부
라이브 패치(kpatch / canonical-livepatch)가 발행돼 있다면 재부팅 없이 적용 가능하다. 프로덕션 노드는 우선 라이브 패치로 막은 뒤 다음 정기 점검 창에서 재부팅하는 게 안전하다.
3) 검증
uname -r
# RHEL 계열
rpm -q --changelog kernel-core | grep -i 31431 | head
# Debian/Ubuntu 계열
apt changelog linux-image-$(uname -r) 2>/dev/null | grep -i 31431 | head
CHANGELOG에 CVE 번호가 보이면 패치된 빌드다. 라이브 패치를 적용한 경우엔 kpatch list 또는 canonical-livepatch status 로 적재된 패치 목록을 함께 확인한다.
배포판 대응 현황
| 배포판 | 초기 대응 | 비고 |
|---|---|---|
| Debian | 즉시 패치 | 안정 채널에 빠르게 반영 |
| Ubuntu | 즉시 패치 | Pro 사용자에겐 라이브 패치 동시 발행 |
| SUSE | 즉시 패치 | SLES/openSUSE 모두 |
| Red Hat | 처음 보류 → 입장 변경 후 신속 대응 | 일부 빌드에서 algif_aead builtin이라 즉시 완화 옵션이 제한됨 |
| Amazon Linux | 즉시 패치 | AL2023 우선 |
| Arch / Fedora | 즉시 패치 | 롤링 특성상 가장 빠름 |
여러 배포판이 초기에 “Moderate"로 분류했다가 PoC 공개와 함께 “High"로 재분류한 점도 이번 사례의 특징이다 — race 없는 LPE의 운영 영향이 통상 분류보다 크다는 걸 학습한 셈이다.
정리
- Copy Fail은 race 없는 결정론적 LPE다. 패치 전까지는 누구나 한 번에 root를 딸 수 있다.
- 단일 버그가 아니라 2011·2014·2017년 변경 세 개의 우연한 교차점에서 만들어졌다.
- 페이지 캐시 쓰기 프리미티브이기 때문에 디스크 무결성 검사로는 잡히지 않고, 컨테이너 격리만으로는 막을 수 없다.
- 즉시 완화는
algif_aead모듈 차단이지만, 빌트인 환경에선 무효다. 환경부터 확인할 것. - 정식 패치는 재부팅이 필요하다 — kpatch / canonical-livepatch가 가능한 환경이면 무중단 적용을 우선 검토.
다음 글에서는 이 글에서 다루지 않은 두 가지를 따로 본다 — 컨테이너 탈출(user namespace 경계에서의 동작 검증), 그리고 AI 기반 보안 스캐너를 운영 파이프라인에 도입할 때의 트레이드오프.
참고
- copy.fail — Theori의 공식 공시 페이지, 기술 분석과 PoC 원본
- Bugcrowd: What we know about Copy Fail — 운영자 관점 영향 정리
- OpenCVE: CVE-2026-31431 — CVSS·EPSS·영향 제품 메타데이터
- Red Hat: CVE-2026-31431 — RHSA·완화 가이드
- GeekNews 토픽 — 한국 커뮤니티 토론 (특히 builtin 환경 토론)
- 메인라인 패치 커밋:
a664bf3d603d(2017년 인-플레이스 최적화72548b093ee3되돌림)