732 байта Python-кода. Один запуск. Из непривилегированного пользователя - в root. Без компиляции, без race condition, без подбора офсетов под конкретное ядро. 29 апреля 2026 года этот скрипт попал в открытый доступ, и с тех пор CVE-2026-31431 - она же Copy Fail - не даёт спать ни одному сисадмину с непропатченным Linux.
Детерминированная логическая ошибка в криптографической подсистеме ядра. CVSS 7.8 (HIGH), вектор
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, девять лет экспозиции: каждый мейнстримный дистрибутив, собранный с 2017 года, уязвим из коробки. Через два дня CISA внесла уязвимость в KEV-каталог с решением SSVC «Act» - патчить немедленно, эксплуатация активна, технический импакт total. Один и тот же скрипт из стандартной библиотеки Python рутит Ubuntu, RHEL, Amazon Linux, SUSE и Debian без единой правки. По данным Unit 42 (Palo Alto Networks), баг нашли через AI-assisted-процесс примерно за час - а прожил он в коде 9 лет. Девять. Лет.Место Copy Fail в цепочке атаки
Copy Fail - классический Exploitation for Privilege Escalation (T1068, privilege-escalation по MITRE ATT&CK). На пентесте этот баг закрывает переход от low-priv foothold к полному контролю над хостом за секунды.Типовая цепочка на внутреннем пентесте:
- Initial Access - SSH с утёкшими кредами, RCE через уязвимый сервис или скомпрометированный контейнер с web-приложением.
- Privilege Escalation - Copy Fail: непривилегированный шелл → root. Без подготовки и зависимостей.
- Post-exploitation - lateral movement, dump credentials, persistence через Kernel Modules and Extensions (T1547.006), exfiltration.
Percivalll/Copy-Fail-CVE-2026-31431-Kubernetes-PoC, 147 stars) валидирован на Alibaba Cloud ACK, Amazon EKS и Google GKE.Эксплойт целенаправленно злоупотребляет механикой Setuid and Setgid (T1548.001): модифицирует page-cache-копию setuid-бинаря (
/usr/bin/su, sudo, passwd), чтобы при его выполнении получить uid=0.Контекст применимости:
- Внутренний пентест - основной сценарий. После получения непривилегированного шелла на Linux-хосте с ядром 4.14–6.19.12.
- Контейнерная инфраструктура (Kubernetes, Docker) - побег из пода через shared page cache, даже из unprivileged-контейнера.
- CI/CD-пайплайны - runner, выполняющий недоверенный код, отдаёт root на хосте.
- Не применимо - удалённая эксплуатация. Вектор строго локальный (AV:L).
Три коммита, которые собрали девятилетнюю бомбу
Copy Fail - не баг в одной функции. Это результат пересечения трёх независимых изменений в ядре, каждое из которых по отдельности было безобидным. Классическая «матрёшка» - три слоя, и только вместе они взрываются.authencesn: scratch-запись в destination buffer (2011)
Алгоритмauthencesn добавлен для поддержки IPsec Extended Sequence Numbers (ESN). Его архитектурная особенность: при дешифровании он использует буфер назначения (destination buffer) как scratch pad для перестановки данных последовательности. Конкретно - 4 байта seqno_lo (младшая половина номера последовательности) записываются по фиксированному смещению, обрабатываются и должны быть восстановлены. Пока source и destination - разные буферы, всё безопасно.AF_ALG получает AEAD (2015)
Интерфейс AF_ALG (userspace crypto API) получил поддержку AEAD-операций. Теперь любой непривилегированный пользователь мог через стандартный socket API обращаться к ядерным криптоалгоритмам - включаяauthencesn. Появился достижимый путь из userspace. Дверь приоткрылась.In-place-оптимизация (2017, commit 72548b093ee3)
А вот это - фатальное изменение. Для повышения производительности AEAD-операцийreq->src и req->dst стали указывать на единый (combined) scatterlist. Из-за этого page-cache-страницы, переданные через splice(), попали напрямо в записываемый destination scatterlist. До этого коммита source и destination были разными: authencesn писал scratch-байты в отдельный буфер, page cache оставался нетронутым. После - scratch-байты стали писаться прямо в page-cache-страницы целевого файла.Результат пересечения трёх изменений:
authencesn пишет 4 scratch-байта в destination → destination = source (page-cache-страницы) → непривилегированный пользователь контролирует цель записи через splice(). Девять лет это никто не замечал, потому что для срабатывания нужна именно цепочка AF_ALG + splice + authencesn - маршрут, который не покрывается стандартными тестами ядра и не встречается в обычных рабочих нагрузках. Буквально - никому в голову не приходило так ходить.Исправление - mainline commit
a664bf3d603d (1 апреля 2026): возврат к out-of-place-операции. Source и destination scatterlists снова разделены, page-cache-страницы остаются строго в read-only-источнике.NVD классифицирует корневую причину как CWE-669 (Incorrect Resource Transfer Between Spheres) - read-only-ресурс (page-cache-страницы) некорректно перемещён в записываемый контекст. Red Hat дополнительно указывает CWE-1288 (Improper Validation of Consistency within Input) - отсутствие проверки того, что source и destination должны быть разными буферами. Оба CWE описывают разные грани одной и той же проблемы.
Механика эксплойта: от AF_ALG сокета до uid=0
Требования к окружению
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Криптографическая операция завершается с ошибкой (
recvmsg() вернёт error), но повреждение page cache уже произошло и сохраняется. Атакующему error-код безразличен - дело сделано.Почему эксплойт невидим на диске
Все изменения существуют только в RAM. Файл на диске не изменён, VFS-пути не затронуты. Ядро не помечает модифицированную страницу как dirty - поэтому изменения не сбрасываются обратно на диск. После перезагрузки или вытеснения страницы из кэша данные загружаются с чистого файла. Никаких forensic-артефактов на носителе.По данным Picus Security, традиционные методы обнаружения, полагающиеся на проверку целостности файлов, не зафиксируют разницу - даже экспертиза диска покажет оригинальный неизменённый файл. Красота, если ты атакующий. Кошмар, если ты защитник.
Linux privilege escalation без race condition: Copy Fail vs Dirty COW vs Dirty Pipe
Все три уязвимости - local privilege escalation через повреждение page cache. Но эволюция механики и надёжности показательна.| Характеристика | Dirty COW (CVE-2016-5195) | Dirty Pipe (CVE-2022-0847) | Copy Fail (CVE-2026-31431) |
|---|---|---|---|
| Корневая причина | Race condition в mm/gup.c (CWE-362) | Неинициализированные flags в pipe buffer (CWE-665) | Логическая ошибка in-place-оптимизации (CWE-669) |
| CVSS | 7.0 (AC:H - высокая сложность) | 7.8 (AC:L) | 7.8 (AC:L) |
| Детерминированность | Нет - нужно выиграть гонку | Да | Да, 100% с первой попытки |
| Формат эксплойта | Компилируемый C-бинарь | Компилируемый C-бинарь | 732-байтный Python-скрипт |
| Кросс-дистрибутивность | Перекомпиляция под каждое ядро | Перекомпиляция | Работает без модификации |
| Затронутые ядра | 2.6.22 - 4.8.3 | 5.8 - 5.16.11 | 4.14 - 6.19.12 |
| Период экспозиции | ~9 лет | ~1 год | ~9 лет |
| CISA KEV | Да (03.03.2022) | Да (25.04.2022) | Да (01.05.2026) |
| EPSS (2026-05-21) | 0.9418 (Top 1%) | 0.8121 (Top 1%) | 0.0257 (85-й перцентиль) |
| CISA SSVC | Act | Act | Act |
Dirty COW требовал AC:H - гонка между двумя потоками, нестабильность, потенциальный kernel panic при неудаче. Dirty Pipe убрал race condition, но оставил зависимость от компилятора и узкий диапазон ядер. Copy Fail снимает оба ограничения: работает детерминированно, компилятор не нужен, покрывает 9 лет ядер. Каждое поколение page-cache-багов проще и надёжнее предыдущего - и это тревожная тенденция.
Низкий EPSS у Copy Fail (0.0257 vs 0.94 у Dirty COW) - артефакт модели, а не показатель реальной опасности. EPSS оценивает текущий объём эксплуатации; Dirty COW эксплуатируется с 2016 года (3500+ дней). По решению CISA SSVC все три получили одинаковый вердикт: exploitation active, technical impact total, решение Act.
Порты эксплойта Copy Fail на другие языки тоже не заставили себя ждать: Rust-реализация с поддержкой кастомного shellcode включая Meterpreter (
iss4cf0ng/CVE-2026-31431-Linux-Copy-Fail, 50 stars), минимальный no-libc ELF-бинарь для x86_64 (Crihexe/copy-fail-tiny-elf-CVE-2026-31431, 61 stars). По данным ThreatLocker, есть ещё порты на Go.Контейнерный побег: почему shared page cache меняет правила
Page cache Linux - ресурс уровня ноды. Все контейнеры на одном хосте работают с одним ядром и одним кэшем. Модификация page-cache-копии setuid-бинаря из контейнера с минимальными привилегиями затрагивает весь узел. Изоляция? Какая изоляция?Сценарии для Kubernetes и Docker:
- Pod → node root - атакующий из непривилегированного пода модифицирует page-cache-копию
/usr/bin/suна уровне ноды, затем выполняет побег. - Shared base image layers - несколько подов с общим base image загружают затронутый бинарь из того же page cache. Один отравленный бинарь - все поды скомпрометированы.
- CI/CD runners - pipeline, выполняющий недоверенный код, получает root на runner-хосте.
socket(AF_ALG, ...) - seccomp-фильтр на этот вызов отсекает всю цепочку.NVD оценивает Scope как Unchanged (S:U), что формально не отражает контейнерный побег. На практике в мультитенантных Kubernetes-кластерах это де-факто Scope Changed - из изолированного контейнера в root ноды. Формальная метрика и реальность тут расходятся.
Что видит защита при эксплуатации Copy Fail
Эксплойт использует исключительно стандартные syscalls:socket, setsockopt, splice, sendmsg, recvmsg - вызовы, задокументированные в kernel docs. Полезная нагрузка - Python-скрипт из стандартных библиотек. Нет инъекции шеллкода в userspace, нет подозрительных файлов, нет сетевых соединений. Для большинства защитных решений - легитимная программа, использующая задокументированное ядерное API. По сути, эксплойт маскируется под нормальную работу.Слепые зоны стандартных средств защиты
File integrity monitoring (AIDE, OSSEC, Tripwire) - полностью слепы. Файл на диске не изменён; FIM проверяет содержимое дискового файла, а не его page-cache-копию. Можно хоть каждую минуту проверять - разницы не увидишь.Auditd - по умолчанию не отслеживает
socket(AF_ALG). Для детекта нужно явное правило на syscall socket с фильтром по AF_ALG family. Без этого правила эксплуатация проходит бесшумно.Стандартные AppArmor/SELinux-профили - не блокируют AF_ALG. Это легитимный ядерный интерфейс для аппаратного ускорения криптографии; заблокировать его по умолчанию нельзя без разрыва совместимости.
Дефолтный seccomp-профиль Docker - не блокирует AF_ALG. Нужен кастомный профиль с явным запретом этого socket family.
Cortex XDR / XSIAM (Palo Alto Networks) - Unit 42 опубликовала XQL-запросы для threat hunting. Ищут корреляцию: создание AF_ALG-сокета → splice к setuid-бинарю → выполнение этого бинаря в короткий временной промежуток. Без корреляции трёх событий отдельные шаги неотличимы от легитимной активности.
Общий подход для SIEM - мониторинг
socket(AF_ALG) от процессов, не являющихся криптографическими приложениями. В нормальных условиях AF_ALG используется редко: OpenSSL, GnuTLS, NSS и SSH работают через userspace-реализации. Если Python-процесс от www-data вдруг создаёт AF_ALG-сокет - это повод насторожиться.Чеклист митигации Copy Fail
Немедленные действия (до патча ядра)
- Заблокировать загрузку модуля
algif_aeadи выгрузить его, если загружен:
Bash:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
- Убедиться, что модуль не загружен:
Bash:
grep -qE '^algif_aead ' /proc/modules \
&& echo "VULNERABLE: module loaded" \
|| echo "OK: module not loaded"
- Для контейнерных сред - добавить seccomp-профиль, блокирующий
socket(AF_ALG). Защищает даже непропатченные ядра. - Оценить влияние на приложения:
lsof | grep AF_ALG. По данным CERT-EU, митигация не затрагивает dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, SSH. Риск регрессии - только для приложений с явным использованием engine afalg или прямым биндом aead/skcipher/hash-сокетов. На практике таких приложений в типовой инфраструктуре я не встречал ни разу.
Патчи ядра по дистрибутивам
| Дистрибутив | Версия пакета | Advisory |
|---|---|---|
| Debian bookworm | linux 6.1.170-1 | DSA-6243-1 (01.05.2026) |
| Debian trixie | linux 6.12.85-1 | DSA-6238-1 (30.04.2026) |
| RHEL 10 | kernel 6.12.0-124.55.1.el10_1 | RHSA-2026:13566 (04.05.2026) |
| Ubuntu noble (NVIDIA) | linux-nvidia 6.8.0-1054.57 | USN-8289-1 (20.05.2026) |
| Ubuntu focal | linux 5.4.0-230.250 | USN-8280-1 (19.05.2026) |
| Ubuntu bionic | linux 4.15.0-250.262 | USN-8281-1 (19.05.2026) |
Не затронуты: RHEL 6/7 (ядро старше затронутого диапазона), Ubuntu 26.04 (Resolute) и новее.
После установки патча
- Проверить версию ядра:
uname -rи сверить с таблицей. - Перезагрузить систему - даже после
rmmodзапущенные процессы могут удерживать ссылки на модуль. - При необходимости восстановить
algif_aead: закомментировать строку в/etc/modprobe.d/disable-algif.confи перезагрузить.
Баг из commit
72548b093ee3 пережил 9 лет code review, сотни тысяч коммитов, переход на Rust-компоненты - и был найден AI за час. На практике: на трёх пентестах за первую неделю после раскрытия Copy Fail оказался применим на всех целевых хостах, потому что ни один не успел обновить ядро, а митигацию через modprobe.d никто не накатил. При этом ни одна из защитных систем на этих хостах - auditd с дефолтными правилами, AIDE, штатный AppArmor-профиль - не зафиксировала эксплуатацию. Ноль алертов.В ближайшие пару лет стоит ожидать роста числа подобных логических багов на стыке подсистем ядра - syzkaller и LLM-фаззеры находят маршруты, которые человеческий аудит пропускает десятилетиями. Кто ещё не выстроил процесс отключения ядерных модулей в первые часы после disclosure - самое время начать. Следующий Copy Fail с AC:L и 100%-reliability уже лежит в коде, ожидая своего часа. Проверьте
grep -qE '^algif_aead ' /proc/modules на своих хостах прямо сейчас - если увидите «VULNERABLE: module loaded», вы знаете, что делать.