
CVE-2026-31431(Copy Fail)에 대한 구조화된 설명으로, 취약점을 도입하고 악용을 가능하게 만든 세 가지 커널 변경 사항을 서로 연결합니다.
authencesn의 논리적 결함으로, AF_ALG와 splice()를 통해 연결되어 시스템에서 읽을 수 있는 모든 파일의 page cache에 제어된 4바이트 쓰기로 이어집니다. 경쟁 조건도, 오프셋도, 컴파일된 페이로드도 없습니다. 동일한 732바이트 스크립트가 2017년 이후 모든 리눅스 배포판에서 root 권한을 획득합니다.
CVE-2026-31431 - Copy Fail 은 리눅스 커널의 authencesn 암호화 템플릿의 논리적 결함입니다. 권한이 없는 로컬 사용자가 디스크의 파일을 수정하지 않고도 시스템에서 읽을 수 있는 모든 파일의 page cache에 제어된 4바이트 쓰기를 수행할 수 있게 합니다.
이 버그는 세 구성 요소 중 어느 하나에도 개별적으로 존재하지 않습니다. 이는 이들 간의 상호 작용에서 발생합니다:``` 2011 ────────────────────────────────────────────────────────────────────── - authencesn added to the kernel (a5079d084f8b). - Uses the caller's destination scatterlist as scratch space. - Reorder ESN bytes before HMAC computation. - Only caller: internal xfrm layer. Harmless.
2015 ────────────────────────────────────────────────────────────────────── - algif_aead.c gains AEAD support with splice() path (104880a6b470). - splice() can deliver page cache pages to the TX scatterlist. - AF_ALG uses out-of-place operation: req->src != req->dst. - Page cache pages remain read-only. Not exploitable.
2017 ────────────────────────────────────────────────────────────────────── - In-place optimization in algif_aead.c (72548b093ee3). - Copies AAD+CT to RX buffer but chains authentication tag pages via sg_chain(). - Sets req->src = req->dst. - Page cache pages now reside in WRITABLE dst. - authencesn writes past boundary → page cache corruption.
2026 ────────────────────────────────────────────────────────────────────── - Copy Fail - CVE-2026-31431. Discovered by Theori / Xint Code. - Exploitable across all distros since 2017.
---
---
---
<div id='root-cause'/>
## ***🧬 근본 원인 분석***
<div id='primitive'/>
### ***AF_ALG + splice() 프리미티브***
AF_ALG (*[AF_ALG = 38](https://docs.kernel.org/crypto/userspace-if.html#user-space-api-general-remarks)*)는 커널 암호화 API를 권한이 없는 사용자 공간에 노출하는 소켓 유형입니다. 권한이 없는 프로세스는 다음을 수행할 수 있습니다:
1. AF_ALG / SOCK_SEQPACKET 소켓을 엽니다.
2. 커널 crypto API가 노출하는 사용 가능한 AEAD 템플릿에 bind() 합니다.
3. 구성된 알고리즘에 대해 setsockopt(SOL_ALG, ALG_SET_KEY, ...)를 통해 암호화 키를 설정합니다.
4. 암호화 및 복호화 요청을 처리할 전용 연산 소켓을 얻기 위해 accept()를 호출합니다
5. sendmsg()로 조작된 데이터를 보내고 recvmsg()로 처리된 결과를 수신하여 커널 crypto 서브시스템과 완전히 상호작용합니다.
주요 배포판의 커널 구성에서는 기본적으로 활성화되어 있습니다 (CONFIG_CRYPTO_USER_API_AEAD=y).
**[splice(2)](https://man7.org/linux/man-pages/man2/splice.2.html)** 는 복사 없이 파일 디스크립터 간에 데이터를 전송합니다 - 페이지에 대한 참조를 전달하지 복사본을 전달하지는 않습니다. 관련 흐름은 다음과 같습니다:```
open("/usr/bin/su") -> fd_file
pipe() -> pipe_rd, pipe_wr
# moves N bytes from the file into the pipe
# the pipe buffer now contains a reference to the same physical page in the page cache
splice(fd_file, pipe_wr, N)
# delivers that reference to the AF_ALG socket
# the TX scatterlist of algif_aead now points to the page cache page of /usr/bin/su
splice(pipe_rd, alg_fd, N)
AF_ALG 소켓의 TX scatterlist는 커널이 파일의 모든 read(), mmap(), execve()에서 사용하는 것과 동일한 물리적 페이지에 대한 직접 참조를 포함합니다. 복사가 수반되지 않습니다.
커밋 72548b093ee3, algif_aead.c. 복호화의 경우 구현은 다음과 같습니다.
In-place operation: RX SGL (req->dst): [ user buffer: AAD (copy) || CT (copy) ] --sg_chain--> [ Tag (page cache pages) ] req->src = req->dst = RX SGL
Result: page cache pages from /usr/bin/su are now part of the WRITABLE scatterlist passed to the crypto algorithm.
<div id='authencesn'/>
### ***authencesn의 범위를 벗어난 쓰기(out-of-bounds write)***
authencesn은 확장 시퀀스 번호(Extended Sequence Numbers, RFC 4303)를 사용하는 IPsec에서 쓰이는 커널 AEAD 래퍼입니다. IPsec은 64비트 시퀀스 번호를 사용합니다:
- seqno_hi - 상위 32비트 (AAD의 바이트 0-3)
- seqno_lo - 하위 32비트 (AAD의 바이트 4-7)
seqno_lo만 실제로 전송되며, seqno_hi는 암시적 컨텍스트입니다. HMAC 계산을 위해 authencesn은 이 바이트들을 재배열해야 합니다. 즉, 해시 입력의 시작 부분에 seqno_hi를, 끝 부분에 seqno_lo를 배치해야 합니다.
이 재배열은 호출자의 대상 scatterlist를 스크래치 공간으로 사용하여 수행됩니다:```c
/* crypto/authencesn.c - crypto_authenc_esn_decrypt() */
// [1] Read bytes 0-7 of the AAD from dst
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0);
// [2] Overwrite dst[4..7] with seqno_hi (temporary modification for HMAC)
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
// [3] *** THE BUG ***
// Writes seqno_lo at dst[assoclen + cryptlen]
// This offset is AFTER the authentication tag - outside the legitimate AEAD output region.
// authencesn uses this position as scratch space and NEVER restores the original bytes.
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
호출 [3]은 dst[assoclen + cryptlen]에 4바이트를 쓴다. 복호화에 대한 AEAD API 출력 계약은 AAD || plaintext, 즉 정확히 assoclen + (cryptlen - authsize) 바이트다. assoclen + cryptlen은 인증 태그 너머에 있다. authencesn은 자신이 소유하지 않은 메모리에 쓴다.
crypto_authenc_esn_decrypt_tail()는 올바른 AAD를 재구성하기 위해 seqno_lo를 다시 읽지만, dst[assoclen + cryptlen]의 원래 바이트를 복원하지는 않는다. 덮어쓰기는 HMAC 검사 성공 여부와 관계없이 영구적이다.
커널의 다른 표준 AEAD 알고리즘은 이렇게 동작하지 않는다. GCM, CCM, 표준 authenc는 쓰기를 합법적인 출력 영역으로 엄격히 제한한다.