732바이트짜리 파이썬 스크립트 하나로 2017년 이후 빌드된 거의 모든 주요 리눅스 배포판에서 root를 따낼 수 있습니다. 이번에 공개된 커널 논리 버그 Copy Fail 이야기예요.
표준 라이브러리만 쓰고, 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 TD
U["비특권 프로세스"]
SU["/usr/bin/su
(setuid root)"]
PC["page cache
(su 페이지)"]
SOCK["AF_ALG 소켓
authencesn(...)"]
TX["TX SGL
(읽기 전용 의도)"]
DST["dst SGL
(쓰기 가능)"]
ESN["authencesn ESN
임시값 (4B)"]
EXEC["execve('/usr/bin/su')"]
U -- "1. open + splice" --> SU
SU -- "페이지 참조" --> PC
PC -- "splice 주입" --> TX
TX -- "in-place 재사용" --> DST
SOCK -- "recv() 복호화" --> DST
DST -- "3. 4B 기록" --> ESN
ESN -- "page cache 손상" --> PC
U -- "4. execve 호출" --> 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되돌림)