
对 CVE-2026-31431 的复现分析、C 改编的 exp。
.
├── container
│ ├── container.md # Container escape ideas
│ ├── expenv # Container environment
│ │ ├── bin
│ │ │ ├── gscontainer
│ │ │ └── share
│ │ ├── Dockerfile
│ │ ├── run.sh
│ │ └── src
│ │ ├── gscontainer.c
│ │ └── share.c
│ ├── images
│ │ ├── container.png
│ │ ├── getshell.png
│ │ ├── host.png
│ │ ├── payload.png
│ │ └── share.png
│ ├── recode.bin # Single-stage shellcode
│ └── stager
│ ├── shell.elf # Staged shellcode embedded in the exploit
│ ├── stager1.bin
│ ├── stager2.bin
│ ├── stager3.bin
│ └── stager4.bin
├── doc
│ ├── cve-2026-31431.md # xint.io analysis summary
│ └── shellcode.md # Introduction to what shellcode is
├── exp.py # curl http://copy.fail/exp -o exp.py
├── README.md
├── success
│ ├── code.py # Shellcode extracted from exp.py
│ └── exploit.c # C-based minimal exploit with native msf privilege escalation shellcode, needs its own bypass
└── test
├── exptest.c # Without comments
└── test.c # Correct and with comments for easier understanding
Three individually harmless commits combined to create the vulnerability:
splice(file → pipe → AF_ALG)
↓
af_alg_sendmsg: file page cache pages → TX SGL (zero-copy)
↓
_aead_recvmsg: in-place optimization
├─ memcpy_sglist: copy AAD+ciphertext to RX SGL
├─ sg_chain: chain tag page (still pointing to file page cache) into RX SGL
└─ req->src = req->dst (= scatterlist containing file page cache pages)
↓
crypto_authenc_esn_decrypt: ESN reordering
└─ scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1)
↓ traverse dst scatterlist to tag position
↓ kmap_local_page() maps file page cache page
↓ memcpy: writes seqno_lo into file page cache page
↓
HMAC verification fails → returns -EBADMSG
↓
File page cache page modified, kernel does not mark page dirty, disk file unchanged
The paper claims "almost all Linux distributions" since 2017 are affected. Page cache is shared across containers, making this vulnerability both a local privilege escalation (LPE) and a container escape vector.
The upstream fix commit a664bf3d603d reverts AF_ALG AEAD operations to out-of-place (separate src and 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)
Commit message: "There is no benefit in operating in-place in algif_aead since the source and destination come from different mappings."
Emergency mitigation:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead
Or block AF_ALG socket creation (socket(AF_ALG, ...)) via seccomp policy.
| Time | Commit | Impact |
|---|
| 2011 | a5079d084f8b | authencesn added, uses dst scatterlist as temporary space for ESN reordering. Harmless at this point — the old AEAD interface kept AAD separate, and the only caller was the kernel xfrm. |
| 2015 | 104880a6b470 | authencesn converted to the new AEAD interface, introducing the behavior of writing seqno_lo "past the output boundary" at assoclen+cryptlen. Still not exploitable — because AF_ALG uses out-of-place mode, req->src and req->dst are separate, and page cache pages are only in read-only src. |
| 2017 | 72548b093ee3 | AF_ALG in-place optimization. Copies AAD+ciphertext from TX SGL to RX buffer, but chains the tag page via sg_chain. Sets req->src = req->dst. Now page cache pages introduced by splice are also in the writable dst scatterlist. Vulnerability formed. |
| Distribution | Kernel Version | Affected |
|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws | Yes |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 | Yes |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 | Yes |
| SUSE 16 | 6.12.0-160000.9-default | Yes |
| Feature | Source Verification | Description |
|---|
| authencesn writes seqno_lo to the end | authencesn.c:134 | Writes seqno_lo (low 32 bits) at assoclen+cryptlen. User description is correct. |
| ESN reordering convention | Blog confirms (xint.io) | AAD bytes 0-3 = seqno_hi, bytes 4-7 = seqno_lo. tmp[1] (=bytes 4-7=seqno_lo) is written to the end. |
| Decryption path does not restore overwritten bytes | authencesn.c:270-273 vs 215-217 | decrypt_tail restores ESN at offset 0-7 but never restores original bytes at assoclen+cryptlen. Encryption path (genicv_tail) restores before writing ICV. |
| AF_ALG in-place encryption/decryption | algif_aead.c:189-252 | rsgl_src points to the same RX SGL as areq->first_rsgl.sgl.sgt.sgl. |
| AF_ALG tag chaining | algif_aead.c:238-244 | During decryption, tag is referenced via sg_chain instead of copied — in splice scenarios, the tag page still points to file page cache. Root cause of security vulnerability. |
| Splice zero-copy transfer | fs/splice.c:876, af_alg.c:1049, lib/scatterlist.c:1167 | Directly references pipe pages, zero-copy. |
| CVE-2026-31431 | Three commit chains | authencesn(2011/2015) + in-place(2017) + splice zero-copy = controllable page cache write primitive. |