Back to feed

tgies/copy-fail-c

tgies/copy-fail-c
284
+18/day
78
C

Cross-platform C port of the Copy Fail Linux LPE (CVE-2026-31431). Disclosed 2026-04-29 by Theori / Xint.

From the README

Copy Fail (CVE-2026-31431) - C port

English (en)日本語 (ja)简体中文 (zh-cn)한국어 (ko)Русский (ru)

A cross-platform C reimplementation of the Copy Fail Linux LPE (CVE-2026-31431), disclosed 2026-04-29 by Theori / Xint. See the canonical writeup at copy.fail for the full vulnerability description, timeline, and Theori's discovery process.

The publicly-released proof-of-concept is a 732-byte Python script. This C port demonstrates that the same exploit can be expressed as portable C compilable to any architecture nolibc supports, with no per-arch hex blobs or inline assembly in the project's own source.

Author of this port: Tony Gies . Discovery and original disclosure: Theori / Xint.

Repository layout

copy-fail-c/
├── exploit.c           the dropper (binary-mutation variant)
├── exploit-passwd.c    the dropper (/etc/passwd UID-flip variant)
├── vulnerable.c        non-destructive vulnerability checker
├── payload.c           the body that gets dropped (setgid+setuid+execve sh)
├── utils.c, utils.h    shared AF_ALG/splice page-cache mutation primitive
├── Makefile            build orchestration
├── nolibc/             vendored from torvalds/linux tools/include/nolibc
└── README.md           this file

After make:

├── payload             tiny static ELF, embedded into the dropper as bytes
├── payload.o           payload wrapped as a relocatable .o by `ld -r -b binary`
├── exploit             dropper, binary-mutation variant
├── exploit-passwd      dropper, /etc/passwd UID-flip variant
└── vulnerable          non-destructive vulnerability checker

exploit.c opens the target binary read-only, then for each 4-byte window of the embedded payload runs one bogus AEAD-decrypt through AF_ALG whose ciphertext input is supplied via splice() from the target's page-cache pages. The authencesn template's in-place optimization treats the splice'd source pages as both the ciphertext input and the plaintext destination, so the (failing) decrypt has already overwritten the page-cache page by the time authentication verification rejects the request. After 4 * N iterations the target's cached image has been replaced byte-for-byte with the payload. execve()'ing the target loads the mutated pages; the on-disk inode is still setuid root, so the kernel grants root credentials and runs the payload.

payload.c is plain portable C: setgid(0); setuid(0); execve("/bin/sh", ...). nolibc supplies the _start, the syscall machinery, and the per-arch register-juggling.

A second variant, exploit-passwd.c, mutates four bytes of /etc/passwd's page cache instead of a setuid binary's image. It needs no embedded payload and works on systems where the binary-mutation route is blocked, but its cashout surface is much narrower.

vulnerable.c is not an exploit. It creates a local testfile containing the string init, then runs the same patch_chunk() primitiv