
Кроссплатформенный порт на C уязвимости Copy Fail Linux LPE (CVE-2026-31431). Раскрыта 2026-04-29 компаниями Theori / Xint.
English (en) ∙ 日本語 (ja) ∙ 简体中文 (zh-cn) ∙ 한국어 (ko) ∙ Русский (ru)
Кроссплатформенная реализация на C эксплойта Copy Fail для Linux (CVE-2026-31431). Уязвимость была раскрыта 29 апреля 2026 года исследователями Theori / Xint. Полное описание уязвимости, хронологию и процесс обнаружения Theori можно найти в официальном отчете на copy.fail.
Публично выпущенный Proof-of-Concept (PoC) представляет собой Python-скрипт размером 732 байта. Данный порт на C демонстрирует, что тот же самый эксплойт может быть написан на переносимом C, который компилируется под любую архитектуру, поддерживаемую nolibc, без использования специфичных для архитектуры шестнадцатеричных блоков (hex blobs) или встроенного ассемблера в исходном коде самого проекта.
Автор этого порта: Tony Gies [email protected]. Обнаружение и исходное раскрытие: Theori / Xint.
copy-fail-c/
├── exploit.c дроппер (вариант с мутацией бинарного файла)
├── exploit-passwd.c дроппер (вариант с подменой UID в /etc/passwd)
├── vulnerable.c неразрушающая проверка уязвимости
├── payload.c сбрасываемая полезная нагрузка (setgid+setuid+execve sh)
├── utils.c, utils.h общий примитив мутации page cache через AF_ALG/splice
├── Makefile оркестрация сборки
├── nolibc/ код, заимствованный из torvalds/linux tools/include/nolibc
└── README.md этот файл
После выполнения make:
├── payload крошечный статический ELF, встроенный в дроппер в виде байтов
├── payload.o полезная нагрузка, обернутая в перемещаемый .o с помощью `ld -r -b binary`
├── exploit дроппер, вариант с мутацией бинарного файла
├── exploit-passwd дроппер, вариант с подменой UID в /etc/passwd
└── vulnerable неразрушающая проверка уязвимости
exploit.c открывает целевой бинарный файл только для чтения, затем для каждого 4-байтового окна встроенной полезной нагрузки запускает одну фиктивную операцию AEAD-расшифровки через AF_ALG, где зашифрованный ввод поставляется через splice() из страниц кэша (page-cache) целевого файла. Оптимизация "на месте" (in-place) шаблона authencesn рассматривает исходные страницы, переданные через splice, одновременно как ввод шифротекста и место назначения для открытого текста, поэтому обреченная на неудачу расшифровка уже перезаписывает страницу кэша к тому моменту, когда проверка подлинности отклоняет запрос. После 4 * N итераций закэшированный образ целевого файла побайтово заменяется полезной нагрузкой. Выполнение execve() для целевого файла загружает мутировавшие страницы; inode на диске по-прежнему имеет бит setuid root, поэтому ядро предоставляет root-привилегии и запускает полезную нагрузку.
payload.c — это обычный переносимый C: setgid(0); setuid(0); execve("/bin/sh", ...). nolibc предоставляет _start, механизмы системных вызовов и работу с регистрами, специфичную для каждой архитектуры.
Второй вариант, exploit-passwd.c, мутирует четыре байта в кэше страниц файла /etc/passwd вместо образа setuid-бинарника. Ему не нужна встроенная полезная нагрузка, и он работает на системах, где путь с мутацией бинарного файла заблокирован, но его поверхность для успешной эксплуатации (cashout) гораздо уже.
vulnerable.c не является эксплойтом. Он создаёт в рабочей директории локальный файл testfile со строкой init, после чего запускает тот же примитив patch_chunk() против собственного кэша страниц этого файла, пытаясь перезаписать байты строкой vulnerable. Если прочитанное содержимое совпадает, работающее ядро находится в зоне действия CVE-2026-31431. Inode на диске не модифицируется; testfile удаляется при выходе, и мутация кэша страниц исчезает вместе с ним. Запускается без привилегий. Возвращает код выхода 100 при наличии уязвимости, 0 если примитив выполнился, но мутация не сработала, 2 если семейство сокетов AF_ALG или шаблон authencesn недоступны и состояние патча нельзя определить, и 1 для других ошибок выполнения.
По умолчанию (нативно для архитектуры хоста):
make
Кросс-компиляция под aarch64 (или любую другую архитектуру Linux, для которой установлен кросс-тулчейн):
make CC=aarch64-linux-gnu-gcc LD=aarch64-linux-gnu-ld
Архитектуры, поддерживаемые включенной (vendored) версией nolibc (в соответствии с апстримом): x86_64, i386, arm, aarch64, riscv32/64, mips, ppc, s390x, loongarch, m68k, sh, sparc. nolibc осуществляет диспетчеризацию на основе макросов архитектуры компилятора, поэтому достаточно выбрать правильные CC/LD.
Требования для сборки:
cc, gcc или любой кросс-компилятор)ld -r -b binary (подходят как binutils ld, так и lld)linux/if_alg.h и <asm/unistd.h> (Debian/Ubuntu: linux-libc-dev; для кросс-компиляции: обычно подтягиваются пакетом кросс-тулчейна)Наборы заголовков старше Linux 5.6 не содержат __kernel_old_time_t и struct __kernel_old_timespec, которые использует включённый (vendored) nolibc. compat.h (принудительно включаемый в сборку полезной нагрузки) предоставляет их при отсутствии, поэтому сборка проходит и со старым linux-libc-dev. На заголовках 5.6 и новее он не даёт никакого эффекта.
Зависимости от внешних библиотек отсутствуют. Полезная нагрузка (payload) собирается автономно (freestanding) с использованием nolibc; дроппер линкуется с системной libc только для использования fprintf и perror.
Несколько небольших возможностей тулчейна берут на себя основную задачу по сохранению переносимости исходного кода и небольшого размера полезной нагрузки.
nolibc/ — это крошечная замена libc от ядра, состоящая только из заголовочных файлов, скопированная из tools/include/nolibc/ репозитория torvalds/linux. Она предоставляет _start, переносимый макрос syscall() и встроенные обертки системных вызовов, причем соглашения о регистрах для конкретных архитектур закодированы в nolibc/arch-*.h. Сборка полезной нагрузки с флагами -nostdlib -static -ffreestanding -Inolibc создает крошечный статический ELF, который обращается к ядру напрямую, не подтягивая код запуска glibc, инициализацию TLS или механизмы stack-canary. Результат, после упаковки и удаления заголовков секций (то и другое описано ниже): ~720 байт на x86_64, ~1.2 КБ на aarch64 по сравнению с ~17 КБ для того же payload.c, слинкованного с musl-static, или ~700 КБ с glibc-static.
ld -r -b binary для встраиванияMakefile превращает собранный ELF-файл payload в payload.o с помощью команды ld -r -b binary -o payload.o payload. Линкер выводит входные байты "как есть" в качестве секции данных перемещаемого объектного файла и синтезирует три символа из имени входного файла:
_binary_payload_start адрес первого байта полезной нагрузки
_binary_payload_end адрес байта, следующего за последним байтом полезной нагрузки
_binary_payload_size абсолютный символ, значением которого является размер в байтах
exploit.c объявляет первые два символа как extern const unsigned char[] и вычисляет размер как _binary_payload_end - _binary_payload_start.
-Wl,-N и жесткий max-page-size