한 줄 요약

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 conditionflag 초기화 누락직선형 논리 버그
성공률확률적 (수십~수천 회 시도)거의 결정론적첫 시도 결정론적
배포판별 오프셋필요할 때 있음거의 불필요불필요
컴파일된 페이로드보통 필요종종 필요불필요 (Python 표준 라이브러리만)
영향 기간~2007–2016~2020–20222017–패치 적용일

“직선형 논리 버그"라는 표현이 핵심이다. 흐름이 분기 없이 일자로 흐르고, 각 단계가 의도대로 떨어지면 페이지 캐시의 정해진 위치에 정해진 바이트가 쓰인다. 운영자 입장에서 가장 무서운 형태의 LPE다 — 패치하기 전까지는 누구나 한 번에 성공할 수 있다.

세 가지 변경의 우연한 교차점

Copy Fail은 단일 버그가 아니다. 시기가 다른 세 가지 커널 변경이 한 자리에서 만나면서 만들어진 함정이다.

시기변경원래 의도
2011authencesn 템플릿 추가IPsec ESP의 64비트 Extended Sequence Number 지원. 처음부터 호출자의 destination scatterlist를 임시 저장 공간으로 재사용
~2014AF_ALG 소켓에 AEAD 지원(algif_aead.c) + splice() 경로사용자 공간이 커널 crypto API를 소켓처럼 쓰도록 하기 위함
2017algif_aead에 in-place AEAD 최적화 (commit 72548b093ee3)메모리 절약 목적. 복호화 시 req->src = req->dst로 묶고, tag 페이지는 sg_chain()으로 참조만 연결

각각만 보면 그럴듯한 변경이다. 문제는 셋이 만나는 지점이다.

  1. splice()읽기 전용 파일의 페이지 캐시 페이지를 AF_ALG의 TX scatterlist에 그대로 참조로 넣을 수 있다.
  2. 2017년 인-플레이스 최적화가 그 TX scatterlist를 그대로 쓰기 가능한 dst scatterlist로 재사용한다.
  3. authencesndst[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

순서를 풀면 이렇다.

  1. AF_ALG 소켓을 열어 authencesn(...) 알고리즘에 bind한다.
  2. 타깃 setuid 바이너리(/usr/bin/su 등)를 열어 그 페이지 캐시 페이지를 splice()로 소켓의 TX scatterlist에 주입한다.
  3. 쉘코드를 4바이트 단위로 잘라, 매번 위치 계산을 맞춰 sendmsg() + splice() 페어를 반복한다.
  4. recv()를 호출하면 복호화 동작이 이뤄지며 authencesn이 ESN 임시값을 dst — 즉 손에 들어온 페이지 캐시 페이지 — 에 4바이트씩 기록한다.
  5. 마지막으로 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-23Linux 커널 보안팀에 보고
2026-03-25패치 제안
2026-04-01메인라인 커널 커밋 (a664bf3d603d)
2026-04-22CVE-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 기반 보안 스캐너를 운영 파이프라인에 도입할 때의 트레이드오프.

참고