
CVE-2026-31431에 대한 재현 분석, C 언어로 개작된 exp.
.
├── container
│ ├── container.md # 容器逃逸的思路
│ ├── expenv # 容器环境
│ │ ├── bin
│ │ │ ├── gscontainer
│ │ │ └── share
│ │ ├── Dockerfile
│ │ ├── run.sh
│ │ └── src
│ │ ├── gscontainer.c
│ │ └── share.c
│ ├── images
│ │ ├── container.png
│ │ ├── getshell.png
│ │ ├── host.png
│ │ ├── payload.png
│ │ └── share.png
│ ├── recode.bin # 单阶段的 shellcode
│ └── stager
│ ├── shell.elf # 内嵌在exp当中的分阶段 shellcode
│ ├── stager1.bin
│ ├── stager2.bin
│ ├── stager3.bin
│ └── stager4.bin
├── doc
│ ├── cve-2026-31431.md # xint.io 分析总结
│ └── shellcode.md # 介绍什么是 shellcode
├── exp.py # curl http://copy.fail/exp -o exp.py
├── README.md
├── success
│ ├── code.py # exp.py 提取的 shellcode
│ └── exploit.c # C 语言精简的 exp,当中有原生的 msf 提权的 shellcode,需要自己免杀
└── test
├── exptest.c # 清除注释
└── test.c # 正确且带注释,便于理解
각각 단독으로는 무해한 세 개의 커밋이 결합되면서 취약점이 발생했습니다:
splice(文件 → pipe → AF_ALG)
↓
af_alg_sendmsg: 文件页缓存页 → TX SGL (零拷贝)
↓
_aead_recvmsg: in-place 优化
├─ memcpy_sglist: AAD+密文拷贝到 RX SGL
├─ sg_chain: tag 页(仍指向文件页缓存)链入 RX SGL
└─ req->src = req->dst (= 含文件页缓存页的 scatterlist)
↓
crypto_authenc_esn_decrypt: ESN 重排列
└─ scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1)
↓ 在 dst scatterlist 中遍历到 tag 位置
↓ kmap_local_page() 映射文件页缓存页
↓ memcpy: 将 seqno_lo 写入文件页缓存页
↓
HMAC 验证失败 → 返回 -EBADMSG
↓
文件页缓存页已被修改,内核未标记脏页,磁盘文件不变
논문은 2017년 이후 "거의 모든 Linux 배포판"이 영향을 받는다고 주장합니다. 페이지 캐시는 컨테이너 간에 공유되므로 이 취약점은 로컬 권한 상승(LPE)이자 컨테이너 탈출 벡터입니다.
커널 메인라인의 수정 커밋 a664bf3d603d는 AF_ALG AEAD 작업을 out-of-place(src와 dst 분리)로 되돌립니다:
Before: aead_request_set_crypt(..., rsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src == dst ^
After: aead_request_set_crypt(..., tsgl_src,
areq->first_rsgl.sgl.sgt.sgl, ...)
^ src ≠ dst ^ (TX SGL for src, RX SGL for dst)
커밋 메시지: "There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings."
긴급 완화 조치:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead
또는 seccomp 정책을 통해 AF_ALG 소켓 생성(socket(AF_ALG, ...))을 금지합니다.
| 연도 | 커밋 | 영향 |
|---|
| 2011 | a5079d084f8b | authencesn이 추가되어 dst scatterlist를 ESN 재배열의 임시 공간으로 사용했습니다. 이때는 무해했습니다. 기존 AEAD 인터페이스가 AAD를 분리해 유지했고 유일한 호출처가 커널 xfrm이었기 때문입니다. |
| 2015 | 104880a6b470 | authencesn이 새 AEAD 인터페이스로 전환되면서 "출력 경계를 벗어나" assoclen+cryptlen 위치에 seqno_lo를 쓰는 동작이 도입되었습니다. 그래도 악용할 수 없었습니다. AF_ALG가 out-of-place 모드를 사용하여 req->src와 req->dst가 분리되어 있었고, page cache 페이지가 읽기 전용 src에만 있었기 때문입니다. |
| 2017 | 72548b093ee3 | AF_ALG in-place 최적화. AAD+암호문을 TX SGL에서 RX buffer로 복사하지만 sg_chain을 통해 tag 페이지를 연결합니다. req->src = req->dst로 설정합니다. 이제 splice로 도입된 page cache 페이지가 쓰기 가능한 dst scatterlist에도 동시에 위치하게 됩니다. 취약점이 성립합니다. |
| 배포판 | 커널 버전 | 영향 받음 |
|---|
| 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 | 예 |
| 특성 | 소스 코드 검증 | 설명 |
|---|
| authencesn이 seqno_lo를 끝에 기록 | authencesn.c:134 | assoclen+cryptlen 위치에 기록되는 것은 seqno_lo(하위 32비트) 입니다. 사용자의 설명이 정확합니다. |
| ESN 재배열 규약 | 블로그 확인 (xint.io) | AAD bytes 0-3 = seqno_hi, bytes 4-7 = seqno_lo. tmp[1](=bytes 4-7=seqno_lo)이 끝에 기록됩니다. |
| 복호화 경로는 덮어쓴 바이트를 복구하지 않음 | authencesn.c:270-273 vs 215-217 | decrypt_tail은 ESN을 offset 0-7로 복구하지만 assoclen+cryptlen 위치의 원래 바이트는 절대 복구하지 않습니다. 암호화 경로(genicv_tail)는 ICV를 쓰기 전에 복구합니다. |
| AF_ALG in-place 암·복호화 | algif_aead.c:189-252 | rsgl_src는 areq->first_rsgl.sgl.sgt.sgl과 동일한 RX SGL을 가리킵니다. |
| AF_ALG tag 체인 연결 | algif_aead.c:238-244 | 복호화 시 tag는 복사가 아닌 sg_chain 참조로 연결됩니다. splice 시나리오에서 tag 페이지는 여전히 파일 페이지 캐시를 가리킵니다. 보안 취약점의 근본 원인. |
| splice 제로 카피 전송 | fs/splice.c:876, af_alg.c:1049, lib/scatterlist.c:1167 | pipe page를 직접 참조하여 제로 카피로 전송합니다. |
| CVE-2026-31431 | 세 가지 커밋 체인 | authencesn(2011/2015) + in-place(2017) + splice 제로 카피 = 제어 가능한 페이지 캐시 쓰기 프리미티브. |