
복사 실패: 모든 주요 Linux 배포판에서 루트까지 732바이트.
732바이트. 모든 배포판. 루트.
Linux 커널의
authencesn암호화 템플릿에 존재하는 단순한 논리 결함으로, 권한이 없는 로컬 사용자가 setuid 바이너리를 포함한 모든 읽기 가능한 파일의 페이지 캐시에 정밀하고 통제된 4바이트 쓰기를 수행할 수 있습니다. 레이스 없음. 재시도 없음. 재컴파일 없음. 2017년 이후 출시된 모든 주요 Linux 배포판에서 루트 권한 획득.
📄 기술 문서 · 🔗 커널 패치 · 🛡️ CVSS: Critical
| 배포판 | 커널 버전 |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
네 가지 모두 수정 없이 동일한 732바이트 Python 스크립트로 루트 권한을 획득했습니다.
AF_ALG는 커널의 암호화 하위 시스템을 권한 없는 사용자 공간에 노출합니다. splice()는 파일 데이터를 복사하지 않고 참조로 파이프로 전송합니다 — 페이지 캐시 페이지를 직접 전달합니다. 사용자가 파일을 AF_ALG AEAD 소켓으로 splice하면, 소켓의 입력 scatterlist는 해당 파일의 커널 캐시 페이지에 대한 활성 참조를 보유합니다.
algif_aead.c에서 2017년 제자리(in-place) 최적화는 TX scatterlist에서 RX 버퍼로 AAD와 암호문을 복사했지만, 인증 태그 페이지는 sg_chain()을 사용하여 참조로 연결한 후 req->src = req->dst로 설정했습니다:
입력 SGL: [ AAD | CT | Tag ]
^
└─ sg_chain() → 여전히 페이지 캐시 페이지를 가리킴
출력 SGL: [ AAD | CT ] ──→ [ Tag (페이지 캐시 페이지) ]
(RX 버퍼) (TX SGL에서 연결됨)
req->src ──┐
├──→ 동일한 결합 scatterlist
req->dst ──┘
splice()의 페이지 캐시 페이지는 이제 쓰기 가능한 대상 scatterlist 내부에 위치하게 되었고, 합법적인 쓰기 영역과는 오프셋 경계로만 분리되었습니다. API에는 알고리즘이 경계 내에 머물러야 한다는 강제 조항이 없었습니다.
authencesn의 범위 밖 스크래치 쓰기authencesn은 IPsec의 64비트 확장 시퀀스 번호(ESN) 지원을 위해 사용되는 AEAD 래퍼입니다. HMAC 계산을 위해 ESN 바이트를 재배열할 때 호출자의 대상 버퍼를 스크래치 공간으로 사용합니다 — 여기에는 인증 태그 경계를 넘어서는 assoclen + cryptlen 오프셋에서의 쓰기도 포함됩니다:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // AAD[0..7] 읽기
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // dst[4..7] 덮어쓰기
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← 태그를 넘어 쓰기
세 번째 호출은 dst[assoclen + cryptlen]에 4바이트(seqno_lo)를 씁니다. 제자리 AF_ALG 경로에서 scatterwalk는 RX 버퍼에서 연결된 페이지 캐시 태그 페이지로 교차합니다. 커널은 kmap_local_page를 통해 페이지 캐시 페이지를 매핑하고 대상 파일의 캐시된 복사본에 직접 씁니다.
그런 다음 HMAC는 실패하고(암호문이 조작됨), recvmsg()는 오류를 반환합니다 — 그러나 4바이트 쓰기는 영구적으로 유지됩니다.
개별 변경 사항 중 어느 것도 단독으로는 잘못되지 않았습니다. 취약점은 세 가지 모두의 교차점에 존재합니다.
기본 대상은 모든 테스트된 배포판에 존재하는 setuid-root 바이너리인 /usr/bin/su입니다.
1단계 — 소켓 설정
AF_ALG 소켓 열기, authencesn(hmac(sha256),cbc(aes))에 바인딩
키 설정. 요청 소켓 수락. (권한 불필요.)
2단계 — 쓰기 루프 (4바이트 셸코드 청크당 1회)
sendmsg() → AAD 바이트 [4:8]이 쓸 4바이트를 전달 (seqno_lo)
splice() → 대상 파일의 페이지 캐시 페이지를 AF_ALG 소켓으로 전송
recv() → 복호화 트리거 → authencesn이 seqno_lo를 페이지 캐시에 기록
(recvmsg는 오류 반환; 쓰기는 유지됨)
3단계 — 실행
execve("/usr/bin/su")
커널이 (이제 손상된) 페이지 캐시에서 바이너리를 로드
Setuid-root 바이너리가 주입된 셸코드 실행 → UID 0
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... 키 설정, 요청 소켓 u 수락 ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # 페이지 캐시 쓰기 트리거
패치 a664bf3d603d가 포함된 커널로 업데이트하세요. 이 수정은 algif_aead.c를 제자리 방식에서 벗어난 방식으로 되돌립니다: req->src는 TX SGL을 가리키고, req->dst는 RX 버퍼를 가리킵니다. splice()의 페이지 캐시 페이지는 읽기 전용으로 유지됩니다. 이를 쓰기 가능한 대상에 연결했던 sg_chain() 메커니즘은 제거되었습니다.
// 이전 (취약): src와 dst가 동일한 scatterlist 공유
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// 이후 (수정): src는 TX SGL, dst는 RX 버퍼 — 완전히 분리됨
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
algif_aead 커널 모듈 비활성화:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
또는 워크로드 프로필에서 seccomp 정책을 통해 AF_ALG 소켓 생성을 차단하세요.
컨테이너 환경 참고: 페이지 캐시는 호스트 전체에서 공유되므로 이 취약점은 컨테이너 경계를 넘습니다. 팟별이 아닌 노드 수준에서 완화 조치를 적용하세요. 전체 Kubernetes 탈출 세부 사항은 2부를 참조하세요.
Theori 연구원 Taeyang Lee는 이전 kernelCTF 작업을 통해 AF_ALG + splice()가 권한 없는 사용자 공간이 페이지 캐시 페이지를 암호화 하위 시스템에 직접 공급할 수 있는 경로를 생성한다는 것과, scatterlist 페이지 출처가 충분히 탐구되지 않은 취약점 클래스라는 것을 확인했습니다.
연구팀은 Xint Code를 사용하여 다음 운영자 프롬프트로 이 통찰력을 전체 crypto/ 하위 시스템에 확장했습니다:
"이것은 Linux crypto/ 하위 시스템입니다. 사용자 공간 syscall에서 도달 가능한 모든 코드 경로를 검토하세요. 한 가지 핵심 관찰을 기록하세요: splice()는 읽기 전용 파일( setuid 바이너리 포함)의 페이지 캐시 참조를 crypto TX scatterlist에 전달할 수 있습니다."
약 1시간의 자동 분석 후, Copy Fail이 가장 높은 심각도의 출력이었습니다. 동일한 스캔 중 발견된 추가 취약점은 조율된 공개 절차를 진행 중입니다.
2부: Pod에서 호스트로 — Copy Fail이 모든 주요 클라우드 Kubernetes 플랫폼을 탈출하는 방법. 곧 공개 예정.
| 속성 | 세부 사항 |
|---|
| 결정적(Deterministic) | 단순한 논리 결함 — 레이스 조건, 타이밍 윈도우, 재시도 없음 |
| 이식성(Portable) | 동일한 스크립트, 동일한 바이트, 모든 테스트된 배포판 및 아키텍처에서 동작 |
| 초소형(Tiny) | 표준 라이브러리(os, socket, zlib)만 사용하는 732바이트 Python 스크립트. os.splice 사용을 위해 Python 3.10+ 필요 |
| 은밀성(Stealthy) | 손상된 페이지는 절대 dirty로 표시되지 않음. 디스크의 체크섬은 변경되지 않으며, 메모리 내 페이지 캐시만 수정됨 |
| 컨테이너 간(Cross-container) | 페이지 캐시는 컨테이너 경계를 넘어 시스템 전체에서 공유됨 — 이는 Kubernetes 노드 탈출 프리미티브이기도 함 (2부 참조) |
| 변수 | 제어 방법 |
|---|
| 대상 파일 | 현재 사용자가 읽을 수 있는 모든 파일 |
| 쓰기 오프셋 | assoclen, splice 오프셋, splice 길이 |
| 쓰기 값 | sendmsg()에서 제공된 AAD의 4–7번째 바이트 (seqno_lo) |
| 연도 | 사건 |
|---|
| 2011 | authencesn이 IPsec ESN 지원을 위해 커널에 추가됨 (a5079d084f8b). 스크래치 쓰기는 존재했지만 무해했습니다 — 내부 xfrm 레이어만 호출했고, AAD는 별도의 scatterlist에 있었습니다. |
| 2015 | AF_ALG에 AEAD 지원 추가. authencesn이 새로운 AEAD 인터페이스로 변환됨 (104880a6b470), assoclen + cryptlen 쓰기 오프셋 도입. 여전히 제자리가 아님: 페이지 캐시 페이지는 src(읽기 전용)에 있었습니다. 아직 악용 불가. |
| 2017 | algif_aead.c에 제자리 최적화 추가 (72548b093ee3). req->src = req->dst. 페이지 캐시 태그 페이지가 쓰기 가능한 대상에 연결됨. 취약점 형성. |
| 2026-03-23 | Linux 커널 보안 팀에 보고됨. |
| 2026-04-01 | 메인라인에 패치 병합. |
| 2026-04-22 | CVE-2026-31431 지정. |
| 2026-04-29 | 공개 공개. |
| 날짜 | 사건 |
|---|
| 2026-03-23 | Linux 커널 보안 팀에 취약점 보고 |
| 2026-03-24 | 초기 확인 수신 |
| 2026-03-25 | 패치 제안 및 검토 |
| 2026-04-01 | 메인라인 커널에 패치 커밋 |
| 2026-04-22 | CVE-2026-31431 지정 |
| 2026-04-29 | 공개 공개 |